Skip to contents

Adds one or more columns to .data that are produced by a Large-Language-Model.

Usage

llm_mutate(
  .data,
  output,
  prompt = NULL,
  .messages = NULL,
  .config,
  .system_prompt = NULL,
  .before = NULL,
  .after = NULL,
  .return = c("columns", "text", "object"),
  .na_action = c("send", "skip", "error"),
  .structured = FALSE,
  .schema = NULL,
  .fields = NULL,
  .tags = NULL,
  .rows_per_prompt = 1L,
  .rowpack_payload = c("user", "system"),
  .rowpack_recovery = c("halve_recursive", "halve_once", "singletons", "retry_same",
    "none"),
  ...
)

Arguments

.data

A data.frame / tibble.

output

Unquoted name that becomes the new column (generative) or the prefix for embedding columns. In shorthand form, omit this argument and pass newcol = "<glue prompt>" or newcol = c(system = "...", user = "...") through ....

prompt

Optional glue template string for a single user turn; reference any columns in .data (e.g. "{id}. {question}\nContext: {context}"). Ignored if .messages is supplied.

.messages

Optional named character vector of glue templates to build a multi-turn message, using roles in c("system","user","assistant","file"). Values are glue templates evaluated per-row; all can reference multiple columns. For multimodal, use role "file" with a column containing a path template.

.config

An llm_config object (generative or embedding).

.system_prompt

Optional system message sent with every request when .messages does not include a system entry.

.before, .after

Standard dplyr::relocate helpers controlling where the generated column(s) are placed.

.return

One of c("columns","text","object"). For generative mode, controls how results are added. "columns" (default) adds text plus diagnostic columns; "text" adds a single text column; "object" adds a list-column of llmr_response objects named <output>_obj. Ignored (with a warning) when .structured = TRUE or .tags is supplied, which always return parsed columns.

.na_action

What to do with rows whose template references an NA value. "send" (default) renders NA as an empty string and sends the prompt anyway; "skip" does not call the API for those rows (the output column is NA and finish_reason is "skipped"); "error" stops before any call is made. With .structured or .tags, only "send" and "error" are available.

.structured

Logical. If TRUE, enables structured JSON output with automatic parsing. When enabled, this is equivalent to calling llm_mutate_structured(). Default is FALSE.

.schema

Optional JSON Schema (R list). When .structured = TRUE, this schema is sent to the provider for validation and used for local parsing. When NULL, only JSON mode is enabled (no strict schema validation).

.fields

Optional character vector of fields to extract from parsed JSON or tag output. In JSON mode, supports nested paths (e.g., "user.name" or "/data/items/0"). When NULL and .schema is provided, auto-extracts all top-level schema properties. In tag mode, NULL extracts all .tags. Set to FALSE to skip field extraction entirely.

.tags

Optional character vector of XML-like tag names to request and parse, such as c("age", "job"). When supplied, llm_mutate() delegates to llm_mutate_tags() and adds tags_ok, tags_data, and one column per tag unless .fields = FALSE.

.rows_per_prompt

Integer scalar, or Inf. Number of rows packed into a single generative request. The default, 1, sends one request per row (the historical behaviour). When greater than 1, rows are grouped and sent in one call wrapped in numbered <row_1>...</row_1> tags (see Row batching below); Inf sends all rows at once. Works in generative, tag, and structured modes; not applicable to embedding configurations.

.rowpack_payload

One of c("user","system"). Channel to which the <row_i> data block is appended when batching. The default "user" keeps a static system prompt cacheable; the imperative instruction is always placed in the system message.

.rowpack_recovery

How to handle rows a batched call leaves unresolved. One of "halve_recursive" (default), "halve_once", "singletons", "retry_same", or "none"; see llm_fn() for the precise meaning of each.

...

Passed to the underlying calls: call_llm_broadcast() in generative mode, get_batched_embeddings() in embedding mode.

Value

.data with the new column(s) appended.

Details

  • Multi-column injection: templating is NA-safe (NA -> empty string).

  • Multi-turn templating: supply .messages = c(system=..., user=..., file=...). Duplicate role names are allowed (e.g., two user turns).

  • Generative mode: one request per row via call_llm_broadcast().

  • Parallelism: calls call_llm_broadcast(), which uses call_llm_robust() under the hood. If no future plan is active, workers are auto-configured; call setup_llm_parallel() to set worker count explicitly.

  • Embedding mode: the per-row text is embedded via get_batched_embeddings(). Result expands to numeric columns named paste0(<output>, 1:N). If all rows fail to embed, a single <output>1 column of NA is returned.

  • Diagnostic columns use suffixes: _finish, _sent, _rec, _tot, _reason, _ok, _err, _id, _status, _ecode, _param, _t.

  • Row packing: with .rows_per_prompt > 1, three further columns are added (_rowpack, _rpn, _rpi: the row-pack identifier, the size of the resolving call, and the within-call position). They appear only when packing actually groups rows, so the default schema is unchanged at .rows_per_prompt = 1.

Row batching

With .rows_per_prompt > 1, several rows travel in one generative request. LLMR wraps each row's prompt in a numbered tag, <row_1>...</row_1>, <row_2>...</row_2>, and so on, appends that block to the message (see .rowpack_payload), and instructs the model to answer each item inside a matching numbered tag; the reply is split back into rows by those numbers. This also composes with .tags (each <row_i> then wraps the requested field tags) and with .structured = TRUE (rows are returned as one JSON object {"results":[{"row":i, ...}]}, de-multiplexed by the integer row field; a one-time warning notes that this relies on the model honouring the protocol and that strict provider-side schema validation is replaced by local parsing). Batching is most useful with capable models at temperature = 0 and is a net loss when the model ignores the wrapping. Dropped, reordered, duplicated, or truncated rows are detected and re-issued per .rowpack_recovery; token counts are reported once per batch so that summing token columns stays correct.

Shorthand

You can supply the output column and prompt in one argument:


df |> llm_mutate(answer = "{question} (hint: {hint})", .config = cfg)
df |> llm_mutate(answer = c(system = "One word.", user = "{question}"), .config = cfg)
df |> llm_mutate(country = "Where is {city}? Answer with only the country.", .config = cfg)

This is equivalent to:


df |> llm_mutate(answer, prompt = "{question} (hint: {hint})", .config = cfg)
df |> llm_mutate(answer, .messages = c(system = "One word.", user = "{question}"), .config = cfg)

Structured modes

Examples

if (FALSE) { # \dontrun{
library(dplyr)

df <- tibble::tibble(
  id       = 1:2,
  question = c("Capital of France?", "Author of 1984?"),
  hint     = c("European city", "English novelist")
)

cfg <- llm_config("openai", "gpt-4.1-nano",
                  temperature = 0)

# Generative: single-turn with multi-column injection
df |>
  llm_mutate(
    answer,
    prompt = "{question} (hint: {hint})",
    .config = cfg,
    .system_prompt = "Respond in one word."
  )

# Generative: multi-turn via .messages (system + user)
df |>
  llm_mutate(
    advice,
    .messages = c(
      system = "You are a helpful zoologist. Keep answers short.",
      user   = "What is a key fact about this? {question} (hint: {hint})"
    ),
    .config = cfg
  )

# Multimodal: include an image path with role 'file'
pics <- tibble::tibble(
  img    = c("path/to/cat.png", "path/to/dog.jpg"),
  prompt = c("Describe the image.", "Describe the image.")
)
pics |>
  llm_mutate(
    vision_desc,
    .messages = c(user = "{prompt}", file = "{img}"),
    .config = llm_config("openai","gpt-4.1-mini")
  )

# Embeddings: output name becomes the prefix of embedding columns
emb_cfg <- llm_config("voyage", "voyage-3.5-lite",
                      embedding = TRUE)
df |>
  llm_mutate(
    vec,
    prompt  = "{question}",
    .config = emb_cfg,
    .after  = id
  )

# Structured output: using .structured = TRUE (equivalent to llm_mutate_structured)
schema <- list(
  type = "object",
  properties = list(
    answer = list(type = "string"),
    confidence = list(type = "number")
  ),
  required = list("answer", "confidence")
)

df |>
  llm_mutate(
    result,
    prompt = "{question}",
    .config = cfg,
    .structured = TRUE,
    .schema = schema
  )

# Structured with shorthand
df |>
  llm_mutate(
    result = "{question}",
    .config = cfg,
    .structured = TRUE,
    .schema = schema
  )

# Soft structured output with XML-like tags
df |>
  llm_mutate(
    result = "Extract the person's age and job from: {question}",
    .config = cfg,
    .tags = c("age", "job")
  )

cities <- tibble::tibble(city = c("Cairo", "Lima"))
cities |>
  llm_mutate(
    geo = "Where is {city}? Give country and continent in their own tags.",
    .config = cfg,
    .system_prompt = paste(
      "Use XML tags for different parts of the answer, but do not nest tags.",
      "Return <country>...</country> and <continent>...</continent>."
    ),
    .tags = c("country", "continent")
  )
} # }