--- title: "LLMRagent in 10 minutes" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{LLMRagent in 10 minutes} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = identical(tolower(Sys.getenv("LLMRAGENT_RUN_VIGNETTES", "false")), "true") ) ``` An agent is three things: a model (an `LLMR::llm_config()`), a persona (a system prompt), and machinery around them: memory, tools, and budgets. This vignette builds one of each component. Examples use the open-weight `gpt-oss-20b` on Groq; set `GROQ_API_KEY` and `LLMRAGENT_RUN_VIGNETTES=true` to run them. ```{r setup} library(LLMRagent) cfg <- LLMR::llm_config("groq", "openai/gpt-oss-20b", temperature = 0.7) ``` ## A first agent ```{r first} ada <- agent( "Ada", cfg, persona = "You are Ada, a meticulous statistician. Answer in one or two sentences." ) ada$chat("What is overfitting?") ada$chat("How would I detect it in practice?") # remembers the thread ada$usage() ``` `chat()` is stateful: the agent keeps its own memory (last 40 messages by default; see `?memory` for summarizing and retrieval policies). `reply()` is the stateless sibling, used internally by conversations. For long answers, `chat(stream = TRUE)` prints tokens as they are generated: ```{r stream} ada$chat("Explain cross-validation to a newcomer, in one paragraph.", stream = TRUE) ``` ## Tools: let the agent call your R functions Anything you can write as an R function can become a tool. The agent decides when to call it; LLMRagent executes the call and feeds the result back. ```{r tools} lookup_gdp <- LLMR::llm_tool( function(country) { gdp <- c(chile = 335, uruguay = 81, bolivia = 47) # USD bn, illustrative val <- gdp[tolower(country)] if (is.na(val)) "unknown" else paste0("$", val, " billion") }, name = "lookup_gdp", description = "Look up a country's GDP in USD billions.", parameters = list(country = list(type = "string", description = "Country name")) ) analyst <- agent("Analyst", cfg, tools = lookup_gdp, persona = "A careful economic analyst. Use tools for any figure.") analyst$chat("Compare the GDPs of Chile and Uruguay using the lookup tool.") analyst$trace() # every model call and tool call, with tokens and timing ``` ## Budgets: spend ceilings the agent cannot cross Budgets are checked before each call; the call that would exceed a limit is refused with a typed error, so a loop cannot spend without you seeing it. ```{r budgets} frugal <- agent("Frugal", cfg, budget = budget(max_calls = 2)) frugal$chat("one") frugal$chat("two") tryCatch(frugal$chat("three"), llmragent_budget_error = function(e) "stopped by budget, as designed") ``` ## Structured answers ```{r structured} schema <- list( type = "object", properties = list(stance = list(type = "string", enum = list("support", "oppose", "unsure")), reason = list(type = "string")), required = list("stance", "reason") ) ada$ask_structured("Should small samples use t or z intervals?", schema) ``` ## Agents calling agents `agent_as_tool()` turns an agent into a tool, so another agent can consult it. The supervisor decides for itself when to delegate; each consultation runs on the specialist's own meter (its `usage()`, its `budget()`). ```{r delegation} stat <- agent("Stat", cfg, persona = "A PhD statistician. Precise about assumptions.") hist <- agent("Hist", cfg, persona = "An economic historian. Institutional context.") lead <- agent("Lead", cfg, persona = "A research lead. Consult specialists, then synthesize.", tools = list(agent_as_tool(stat), agent_as_tool(hist))) lead$chat("Crime fell while policing budgets rose, across many cities. What would it take to argue causality?") stat$usage() # the consultation showed up here ``` ## Pipelines: a fixed sequence of specialists When the routing is fixed rather than model-chosen, chain agents with `agent_pipeline()`: each stage transforms the previous stage's output, and every intermediate product is kept. ```{r pipeline} run <- agent_pipeline( list( agent("Extractor", cfg, persona = "Extract every factual claim as a numbered list. Nothing else."), agent("Checker", cfg, persona = "Mark each numbered claim VERIFIABLE or VAGUE, one line each."), agent("Editor", cfg, persona = "Rewrite the original passage keeping only VERIFIABLE claims.") ), input = "Our app doubled retention, won three design awards, and users love it." ) run$output run$steps # step, agent, input, output -- the full audit trail ``` ## A two-agent conversation Conversations run over a shared, speaker-attributed transcript: every agent sees the full dialogue each turn, and the transcript comes back as a tidy tibble, ready for text analysis. ```{r conversation} rosa <- agent("Rosa", cfg, persona = "A pragmatic city planner. Concrete and brief.") hugo <- agent("Hugo", cfg, persona = "A skeptical economist. Numbers first. Brief.") conv <- conversation( list(rosa, hugo), topic = "Should the city pedestrianize its center?", max_turns = 4, instruction = "At most three sentences per turn." ) conv$transcript ``` From here: `vignette("designed-conversations")` tours the ready-made study formats (debates, focus groups, interviews, deliberations); `vignette("deliberation-experiment")` runs a complete factorial study with `agent_experiment()`; and `vignette("super-brain")` shows strong-plus-cheap model orchestration with `think_harder()`. For a per-call audit file of everything an agent did, turn on `LLMR::llm_log_enable()` before running.