Skip to contents

While the pal package provides a number of pre-engineered pals for R package development, one can use pals for all sorts of coding tasks in R, from interactive data analysis to authoring with Quarto, or even for coding tasks in languages other than R! All you need to set up your own pal is a markdown file. This vignette provides guidance on how to write your own pals to help you with repetitive, hard-to-automate tasks.

What are pals for?

When building a custom pal, you should first ask yourself a few questions to help decide whether a pal is the right tool for the job.

Can this be easily automated without an LLM? LLMs are quite good at edge-case rich tasks with “squishy,” hard-to-evaluate output. Compared to many other pieces of software, LLMs are incredibly flexible and able to handle edge cases without extensive engineering. At the same time, their output is not deterministic and always requires verification. For your given problem, would you be better off spending the time you’d use to write and revise a prompt and then verifying every output it makes from then on instead just writing some R code and unit tests?

Users interface with pals via the active selection. Would a selection provide enough context for the pal to do its job? That is, besides the fixed system prompt attached to your pal, the only information the LLM has about your problem is the text that you’ve selected with your cursor. Would the model need access to a whole file, or a whole project, in order to do its job? In that case, some interface other than a pal may be a better fit.

Pals return output by replacing, prefixing, or suffixing the current selection. Is this the right place for output in your use case? If output ultimately needs to be cut and pasted into a different file, for example, pals may not be the right tool.

Finally, is this kind of output immediately verifiable? The first element to consider here is how much stuff the model ultimately generates. A few sentences or a couple lines of code can be quickly audited and confirmed to be sound. The second element to consider is whether the output can be programmatically checked. For example, does generated code run without error (or, at least, is it syntactically valid)?

Answering these questions can help you better understand whether your problem is best solved with a pal or, instead, by a human or “normal” code or some other LLM interface.

The prompt directory

The easiest way to write a new pal that’s loaded every time you load R is to add a markdown file to the prompt directory. The prompt directory is a folder of markdown files that serves as a library of possible pals. By default, the prompt directory lives at ~/.config/pal, but that default can be changed by adding a .pal_dir option in your .Rprofile using options(pal_dir = some/dir/). directory_path() returns the path to the directory, directory_set() changes it, directory_list() enumerates all of the prompts that currently live in it, and directory_load() registers/refreshes all of the prompts in the directory with the pal package.

To create a new pal, add a prompt to pal’s prompt directory with prompt_new(). You’ll need to supply a role (a single keyword describing what the pal does, like "roxygen") and an interface describing how the pal will interact with the selection (one of "replace", "prefix", or "suffix"). You can also “pre-fill” the contents of the prompt by supplying a file path with the contents argument.

For example, running:

prompt_new("proofread", "replace")
prompt_new("summarize", "prefix")

Would result in a prompt directory that looks like:

/
├── .config/
│   └── pal/
│       ├── proofread-replace.md
│       └── summarize-prefix.md

In that case, pal would register two custom pals when you call library(pal) (or directory_load(). One of them has the role "proofread" and will replace the selected text with a proofread version (according to the instructions contained in the markdown file itself). The other has the role "summarize" and will prefix the selected text with a summarized version (again, according to the markdown file’s instructions).

  • Files without a .md extension are ignored.
  • Files with a .md extension must contain only one hyphen in their filename, and the text following the hyphen must be one of replace, prefix, or suffix.

The best way to register pals for your own personal use is via the prompt directory. However, if you intend to share pals with others, you may be interested in creating a pal extension package.

Extension packages

Pal extension packages allow you to more flexibly share pal prompts with others. Putting together a pal extension package is straightforward:

  • Place one markdown file per new pal role in inst/prompts/. This folder will take the same format as the prompt directory described above.
  • Place a call to pal::directory_load() in the package’s .onLoad(), referencing the extension package’s system.file("prompts", package = "yourpackage"). This will automatically register your package’s prompts with pal when the extension is loaded.

For an example pal extension, see simonpcouch/palpable.

Pal extension packages also allow you to document what your pals are for and how they tend to behave in context; situate your documentation files at ?pal_role, replacing role with your new pal role. Then, with your package loaded, users can view a high-level description of the pal’s behavior and a gallery of examples. See ?pal_cli for an example pal help-page, with source code here.

Using others’ custom pals

Pal is designed to make it as easy as possible to always have the pals you need on hand.

To use others’ custom pals that aren’t situated in extension packages, use prompt_new() with a contents argument. For example, Hannah Frick wrote a pal to transition R code chunks from R Markdown-style chunk headers to Quarto’s yaml syntax and uploaded it as a GitHub Gist here. To use her pal, we could write:

prompt_new(
  "quartochunk", 
  "replace", 
  contents = "https://gist.githubusercontent.com/hfrick/1ca8fc2cb2a4409b743e8120c6cc2223/raw/a9703edfbd4e83839af0278c33add1b33e243d02/quartochunk-replace.md"
)

After running that code, the quartochunk pal will be available to you every time you trigger the pal addin.

To use others’ custom pals from extension packages, simply load the pal extension in your .Rprofile. You might use usethis::edit_r_profile() to open the file, then drop in the following line:

library(palextensionname)

Then, restart R, and that package’s pals will always be available to you when you trigger the pal addin.