Skip to content

Commit

Permalink
vignette on custom pals (closes simonpcouch#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonpcouch committed Oct 30, 2024
1 parent 7da14af commit aa476fe
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 22 deletions.
4 changes: 4 additions & 0 deletions R/directory.R
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
#' the prompt directory, provide a `dir` argument to `directory_load()`.
#' @name directory
#'
#' @seealso The "Custom pals" vignette, at `vignette("custom", package = "pal")`,
#' for more on adding your own pal prompts, sharing them with others, and
#' using prompts from others.
#'
#' @examplesIf FALSE
#' # print out the current prompt directory
#' directory_get()
Expand Down
3 changes: 2 additions & 1 deletion R/prompt.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
#' URL to a GitHub Gist or file in a GitHub repository, etc.
#'
#' @seealso The [directory] help-page for more on working with prompts in
#' batch using `directory_*()` functions.
#' batch using `directory_*()` functions, and `vignette("custom", package = "pal")`
#' for more on sharing pal prompts and using prompts from others.
#'
#' @returns
#' Each `prompt_*()` function returns the file path to the created, edited, or
Expand Down
5 changes: 5 additions & 0 deletions man/directory.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion man/prompt.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

105 changes: 105 additions & 0 deletions vignettes/custom.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
title: "Custom pals"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Custom pals}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```

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.

```{r setup}
library(pal)
```

## 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:

```{r}
#| eval: false
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.

<!-- TODO: add note on how to add to the pal gallery -->

## 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](https://github.com/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](https://github.com/simonpcouch/pal/blob/main/R/doc-pal-cli.R).

<!-- TODO: add note on how to add to the pal gallery -->

## 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](https://gist.github.com/hfrick/1ca8fc2cb2a4409b743e8120c6cc2223#file-quartochunk-replace-md). To use her pal, we could write:

```r
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:

```r
library(palextensionname)
```

Then, restart R, and that package's pals will always be available to you when you trigger the pal addin.
23 changes: 3 additions & 20 deletions vignettes/pal.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Pals are persistent, ergonomic LLM assistants designed to help you complete repe
library(pal)
```

The pal package ships with a number of pre-engineered "roles." A pal role is a descriptor that succinctly describes what the pal is intended to do and serves as an identifier to match the pal with its _prompt_ and _interface_. A pal's prompt is just a markdown file with enough context and examples to teach a model to carry out a given task well. A pal's interface determines whether it replaces, prefixes, or suffixes the selected code. For example:
The pal package ships with a number of pre-engineered "roles." A pal role is a keyword that succinctly describes what the pal is intended to do and serves as an identifier to match the pal with its _prompt_ and _interface_. A pal's prompt is just a markdown file with enough context and examples to teach a model to carry out a given task well. A pal's interface determines whether it replaces, prefixes, or suffixes the selected code. For example:

* The `"testthat"` pal helps you transition your R package's unit tests to the third edition of testthat. Its prompt shows the model how to convert to snapshot tests, disentangle nested expectations, and transition from deprecated functions. It replaces the selected code.
* The `"roxygen"` pal helps you quickly template out royxgen documentation for a function. Its prompt shows the model how to write high-quality stub `@param` and `@returns` entries that can be later extended by the developer. It prefixes the selected code.
Expand Down Expand Up @@ -87,23 +87,6 @@ Once those steps are completed, you're ready to use pals with a keyboard shortcu

## Adding custom pals

While the pal package comes with three pals for 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 pieces of code in languages other than R! All you need to set up your own pal is a markdown file.
While the pal package comes with three pals for 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.

The pal package looks for custom prompts in the "prompt directory", which can be located with `directory_path()` and defaults to `~/.config/pal`. Prompts are markdown files (likely created with `prompt_new()`) that live in the prompt directory with a name like `role-interface.md`. A prompt directory might look like:

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

In that case, pal will 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). Note:

* 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`.

Custom pals can also be situated in R package. See [palpable](https://github.com/simonpcouch/palpable) for an example pal extension package.

For more in-depth documentation on adding custom pals, see the documentation for `prompt_new()` and friends as well as `directory_load()` and friends.
To learn more about adding custom pals as well as how to share them with others, see the ["Custom pals" vignette](custom.html) with `vignette("custom", package = "pal")`.

0 comments on commit aa476fe

Please sign in to comment.