---
title: "Extending gptr"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Extending gptr}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
Add a tool, a policy or a hook when an existing capability needs a small change. These features,
providers, commands and prompt sections share the extension registry with gptr's built-ins.
`gptr_api()$features` lists the supported extension kinds and services.
For reusable instructions and packaged extensions, see
[Skills and plugins](skills-and-plugins.html).
For existing helpers, see [Tools and artifacts](tools-and-artifacts.html); choose a model in
[Language models](language-models.html). The displayed responses below are scripted offline,
so building this guide needs no credentials or network.
``` r
library(gptr)
gptr_api()$version
#> [1] '1.0'
head(grep("^kind[.]", gptr_api()$features, value = TRUE))
#> [1] "kind.adapter" "kind.agent" "kind.artifact_type"
#> [4] "kind.backend" "kind.cache_policy" "kind.checkpointer"
```
## A tool the model calls as an R function
Most capabilities are members of the `peter$` namespace rather than separate model-visible tools:
the model calls them from R code, which costs a few tokens per tool instead of a full schema.
`gptr_check()` runs the conformance checks on a spec before you register it.
``` r
add_tool = gptr_tool(
"add", "Add two numbers",
parameters = list(
type = "object", required = I(c("a", "b")),
properties = list(a = list(type = "number"), b = list(type = "number"))
),
fun = function(a, b) a + b,
exposure = "r", namespace = "demo"
)
gptr_check(add_tool)
#> 4 check(s), 0 failed
#> ok tool:add spec.class
#> ok tool:add spec.fields
#> ok tool:add tool.schema
#> ok tool:add tool.empty_input: empty input fails schema validation before execute()
off_tool = gptr_register(add_tool)
peter$demo$add(2, 3)
#> [1] 5
```
Ask Peter to use it:
``` r
session = peter("Add 40 and 2 with the demo tool.")
session$text
```
```
#> [1] "The sum is 42."
```
``` r
off_tool()
```
`gptr_register()` returns a function that unregisters the record. To add a tool to one session
only, pass it as `peter(..., tools = list(add_tool))`.
`exposure = "r"` makes a callable `peter$demo$add()` member. A `"direct"` tool goes into the
model's tool declarations; give it an `execute = function(input, ctx)` handler. A `"deferred"`
tool is found through `peter$search()`, while `"hidden"` omits it from the model's catalog.
An R package's tools should use the package name as their namespace. Use
`peter$help("demo/add")` to inspect a registered member's schema.
An R helper runs in the current R process and can compute on already-loaded objects without a
file export. Its allocations follow ordinary R rules; an external backend may need its own
serialization. See [Execution model](execution-model.html) for these boundaries.
Choose `execution = "sequential"` for handlers that evaluate R or change files. A tool's
`risk = function(input, ctx)` classifies its actions; its result still passes the active
permission policies. A custom tool does not grant permission to run its own side effects.
## A permission policy
A policy sees every tool call before it runs and can deny, ask, modify or allow it. The strictest
answer of all policies wins, and a policy that throws an error denies.
``` r
keep_data = gptr_policy("keep_data", check = function(call, ctx) {
if (identical(call$name, "r") && grepl("rm(", call$input$code, fixed = TRUE)) {
list(decision = "deny", reason = "removing objects is not allowed in this session")
} else {
NULL
}
})
off_policy = gptr_register(keep_data)
```
``` r
session = peter("Remove the session's objects.", envir = new.env(), mode = auto)
session$text
```
The offline example requests `rm(list = ls())`, which this policy denies:
```
#> [1] "I was not allowed to remove the objects."
```
``` r
off_policy()
```
The denial reached the model as an error result, even in `auto` mode. This text match illustrates
the callback; it is not a general R code security filter. See
[Configuration](configuration.html) for modes, trust and permission rules.
## A hook on a session
`gptr_on()` attaches a handler to one session's events; `gptr_hook()` specs registered with
`gptr_register()` apply to every session.
This example uses an offline provider so the event sequence is reproducible:
``` r
fake = gptr_fake_provider(list("hello"))
session = peter("Say hello.", model = fake, .run = FALSE)
seen = new.env()
seen$roles = character()
off_hook = gptr_on(session, "message_end", function(event, ctx) {
seen$roles = c(seen$roles, event$message$role)
NULL
})
gptr_step(session)
off_hook()
seen$roles
#> [1] "user" "assistant"
```
## Prompt sections, context blocks and commands
A prompt section adds text to the system prompt of new sessions; a context block adds data to the
first message or to every turn; a command adds a slash command to the console.
``` r
rules = gptr_prompt_section(
"house_rules", "Report every quantity with its unit.", tier = "T1", order = 780L
)
notebook = gptr_context_block(
"lab_notebook", function(ctx, budget) "Experiment 12: cohort B.",
placement = "first", order = 650L
)
rows = gptr_command(
"rows", function(args, ctx) paste("rows:", nrow(mtcars)),
description = "Show the rows of mtcars"
)
offs = lapply(list(rules, notebook, rows), gptr_register)
reg = gptr_registry(c("prompt_section", "context_block", "command"))
reg[reg$name %in% c("house_rules", "lab_notebook", "rows"), c("kind", "name", "source")]
#> 3 record(s)
#> kind name source
#> prompt_section house_rules user
#> context_block lab_notebook user
#> command rows user
for (off in offs) off()
```
`gptr_prompt()` shows what a new session would freeze, section by section, with token estimates.
The command's optional `complete` callback is metadata for frontend integrations; the built-in
console does not currently invoke it for Tab completion.
## Other kinds
The eleven exported constructors cover the common kinds: `gptr_tool()`, `gptr_provider()`,
`gptr_adapter()`, `gptr_router()`, `gptr_hook()`, `gptr_policy()`, `gptr_agent()`,
`gptr_command()`, `gptr_prompt_section()`, `gptr_context_block()` and `gptr_backend()`. Every
other kind uses `gptr_spec()`:
``` r
gptr_spec("env_alias", "SLACK_BOT_TOKEN", aliases = "slack-token")
#>
#> aliases: slack-token
#> api_version: 1.0
```
A provider for an OpenAI-compatible endpoint is data:
``` r
gptr_register(gptr_provider(
"corp", api = "openai-completions",
base_url = "https://llm.corp.example/v1", auth = "CORP_LLM_KEY",
models = list(list(id = "corp-large", context = 128000))
))
peter("Summarize this table.", mtcars, model = "corp/corp-large")
```
## Extensions and plugins
An extension is a function of the extension API object. It registers records with
`gptr$register()` and can be passed to one call with `extensions =`, or saved in
`.gptr/extensions/` of a trusted project.
``` r
lab_rules = function(gptr) {
gptr$register(gptr_prompt_section(
"lab_rules", "Report concentrations in mmol/L.", tier = "T1", order = 790L
))
}
peter("Summarize the assay results.", assay, extensions = lab_rules)
```
In an extension file, the last expression must be this factory function. Review the whole file:
earlier expressions are evaluated when it loads. The API object has `$register(spec)`,
`$on(event, handler, matcher = NULL)`, `$require(">= 1.0, < 2")` and `$has(feature)`, along with
plugin-local `$state`. Check a factory with `gptr_check(lab_rules)` before enabling it.
Use `gptr_registry(kind = NULL, diagnostics = FALSE)` to inspect records, or
`gptr_registry(diagnostics = TRUE)` to see validation and collision messages. For the same kind
and name, session records win over trusted project records, user records, plugins and built-ins,
in that order. A tie keeps the first registration and reports a collision. Additive kinds such
as hooks keep multiple records. Removing a user record exposes the next matching record.
`gptr_reload()` rereads configuration and extensions for later use; existing sessions keep their
frozen prompt. Plugin manifests, activation and compatibility are covered in
[Skills and plugins](skills-and-plugins.html).
Packages can teach gptr about their classes through methods for `gptr_describe()` (compact object
descriptions) and `gptr_preimage()` (undo support). These are separate from a tool registration.