--- title: "Creating prompt wraps" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Creating prompt wraps} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ### Creating prompt wraps Using `prompt_wrap()`, you can create your own prompt wraps. An input for `prompt_wrap()` wrap may be string or a tidyprompt object. If you pass a string, it will be automatically turned into a tidyprompt object. Under the hood, a tidyprompt object is just a list with a base prompt (a string) and a series of prompt wraps. `prompt_wrap()` adds a new prompt wrap to the list of prompt wraps. Each prompt wrap is a list with a modification function, an extraction function, and/or a validation function (at least one of these functions must be present). The modification function alters the prompt text, the extraction function applies a transformation to the LLM's response, and the validation function checks if the (transformed) LLM's response is valid. Both extraction and validation functions can return feedback to the LLM, using `llm_feedback()`. When an extraction or validation function returns this, a message is sent back to the LLM, and the LLM can retry answering the prompt according to the feedback. Feedback messages may be a reiteration of instruction or a specific error message which occured during extraction or validation. When all extractions and validations have been applied without resulting in feedback, the LLM's response (after transformations by the extraction functions) will be returned. (`send_prompt()` is responsible for executing this process.) Below is a simple example of a prompt wrap, which just adds some text to the base prompt: ```{r, eval=FALSE} prompt <- "Hi there!" |> prompt_wrap( modify_fn = function(base_prompt) { paste(base_prompt, "How are you?", sep = "\n\n") } ) ``` Shorter notation of the above would be: ```{r, eval=FALSE} prompt <- "Hi there!" |> prompt_wrap(\(x) paste(x, "How are you?", sep = "\n\n")) ``` Often times, it may be preferred to make a function which takes a prompt and returns a wrapped prompt: ```{r, eval=FALSE} my_prompt_wrap <- function(prompt) { modify_fn <- function(base_prompt) { paste(base_prompt, "How are you?", sep = "\n\n") } prompt_wrap(prompt, modify_fn) } prompt <- "Hi there!" |> my_prompt_wrap() ``` Take look at the source code of `answer_as_boolean()`, which also uses extraction: ```{r, eval=FALSE} answer_as_boolean <- function( prompt, true_definition = NULL, false_definition = NULL, add_instruction_to_prompt = TRUE ) { instruction <- "You must answer with only TRUE or FALSE (use no other characters)." if (!is.null(true_definition)) instruction <- paste(instruction, glue::glue("TRUE means: {true_definition}.")) if (!is.null(false_definition)) instruction <- paste(instruction, glue::glue("FALSE means: {false_definition}.")) modify_fn <- function(original_prompt_text) { if (!add_instruction_to_prompt) { return(original_prompt_text) } glue::glue("{original_prompt_text}\n\n{instruction}") } extraction_fn <- function(x) { normalized <- tolower(trimws(x)) if (normalized %in% c("true", "false")) { return(as.logical(normalized)) } return(llm_feedback(instruction)) } prompt_wrap(prompt, modify_fn, extraction_fn) } ``` Take a look at the source code of, for instance, `answer_as_integer()`, `answer_by_chain_of_thought()`, and `answer_using_tools()` for more advanced examples of prompt wraps. #### Breaking out of the evaluation loop In some cases, you may want to exit the extraction or validation process early. For instance, your LLM may indicate that it is unable to answer the prompt. In such cases, you can have your extraction or validation function return `llm_break()`. This will cause the evaluation loop to break, forwarding to the return statement of `send_prompt()`. See `quit_if()` for an example of this. #### Extraction versus validation functions Both extraction and validation functions can return `llm_break()` or `llm_feedback()`. The difference between extraction and validation functions is only that an extraction may transform the LLM response and pass it on to the next extraction and/or validation functions, while a validation function only checks if the LLM response passes a logical test (without altering the response). Thus, if you wish, you can perform validations in an extraction function. #### Prompt wrap types and order of application When constructing the prompt text and when evaluating a prompt, prompt wraps are applied prompt wrap after prompt wrap (e.g., first the extraction and validation functions of one wrap, then of the other). The order in which prompt wraps are applied is important. Currently, four types of prompt wraps are distinguished: 'unspecified', 'break', 'mode', and 'tool'. When constructing the prompt text, prompt wraps are applied in the order of these types. Prompt wraps will be automatically reordered if necesarry (keeping intact the order of prompt wraps of the same type). When evaluating the prompt, prompt wraps are applied in the reverse order of types (i.e., first 'tool', then 'mode', then 'break', and finally 'unspecified'). This is because 'tool' prompt wraps may return a value to be used in the final answer, 'mode' prompt wraps alter how a LLM forms a final answer, 'break' prompt wraps quit evaluation early based on a specific final answer, and 'unspecified' prompt wraps are the most general type of prompt wraps which force a final answer to be in a specific format. #### Configuring a LLM provider with a prompt wrap Advanced prompt wraps may wish to configure certain settings of a LLM provider. For instance, `answer_as_json()` configures certain parameters of the LLM provider, based on which LLM provider is being used (Ollama and OpenAI have different API parameters available for JSON output). This is done by defining a `parameter_fn` within the prompt wrap; `parameter_fn` is a function which takes the LLM provider as input and returns a list of parameters, which will be set as the parameters of the LLM provider before sending the prompt. See `prompt_wrap()` documentation and `answer_as_json()` for an example. #### Configuring a prompt wrap based on the LLM provider or HTTP responses `modify_fn`, `extraction_fn`, and `validation_fn` may take the LLM provider as the second argument and the `http_list` (a list of all HTTP responses made during `send_prompt()`) as the third argument. This allows for advanced configuration of the prompt and the extraction and validation logic based on the LLM provider and any data available the HTTP responses. #### Configuring a prompt wrap based on other prompt wraps `modify_fn`, `extraction_fn`, and `validation_fn` all have access to the `self` object, which represents the `tidyprompt-class` object that they are a part of. This allows for advanced configuration of the prompt and the extraction and validation logic based on other prompt wraps. Inside these functions, simply use `self$` to access the `tidyprompt-class` object. #### Handler functions Prompt wraps may also include 'handler functions'. These are functions which are called upon every received chat completion. This allows for advanced processing of the LLM's responses, such as logging, tracking tokens, or other custom processing. See the `prompt_wrap()` documentation for more information; see the source code of `answer_using_tools()` for an example of a handler function.