LLMR

LLMR logo

CRAN status CRAN downloads License: MIT R-CMD-check Website Lifecycle: experimental GitHub issues

LLMR is an R package for running large language model workflows as ordinary R code. It provides one interface for hosted and local providers, with tidy helpers for repeated calls, structured JSON, XML-like tags, embeddings, chat sessions, retries, and parallel experiments.

Installation

install.packages("LLMR") # CRAN
# Development:
# remotes::install_github("asanaei/LLMR")

Quick start

Configure a model

library(LLMR)

cfg <- llm_config(
  provider = "openai",
  model = "gpt-4.1-nano",
  temperature = 0.2,
  max_tokens = 256
)

Store keys in environment variables such as OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY. Reasoning models such as gpt-5-nano often reject custom temperature values. Omit temperature unless the selected model accepts it.

For Gemini on Vertex AI, keep provider = "gemini" and opt in to Vertex routing:

cfg_vertex <- llm_config(
  provider = "gemini",
  model = "gemini-2.5-flash-lite",
  vertex = TRUE,
  project = "my-gcp-project",
  location = "us-central1",
  api_key = "VERTEX_ACCESS_TOKEN"
)

One-shot generation

r <- call_llm(
  config = cfg,
  messages = c(
    system = "You are a branding expert.",
    user = "Six-word catch-phrase for eco-friendly balloons."
  )
)

print(r) # text + status line
as.character(r) # just the text
finish_reason(r)
tokens(r)
is_truncated(r)

Structured output (JSON with schema)

schema <- list(
  type = "object",
  properties = list(
    label = list(type = "string"),
    score = list(type = "number")
  ),
  required = list("label", "score"),
  additionalProperties = FALSE
)

cfg_s <- enable_structured_output(cfg, schema = schema)

resp <- call_llm(cfg_s, c(system = "Reply JSON only.", user = "Label and score for 'MNIST'."))
parsed <- llm_parse_structured(resp)
str(parsed)

Or use higher-level helpers:

words <- c("excellent", "awful", "fine")

out <- llm_fn_structured(
  x = words,
  prompt = "Classify '{x}' and output {label, score in [0,1]} as JSON.",
  .config = cfg,
  .schema = schema,
  .fields = c("label", "score")
)
out

Tidy helpers

Use llm_fn() for vectors and llm_mutate() inside data-frame pipelines. The shorthand form puts the output column and prompt in one argument.

words <- c("excellent", "awful", "fine")
llm_fn(words, "Classify '{x}'.", .config = cfg, .return = "text")

df <- tibble::tibble(
  city = c("Cairo", "Lima"),
  text = c("Cairo is large.", "Lima is coastal.")
)

df |>
  llm_mutate(
    country = "Where is {city}? Answer with only the country.",
    .config = cfg
  )

Strict JSON extraction can be used directly in llm_mutate():

df |>
  llm_mutate(
    out = "{text}",
    .config = cfg,
    .structured = TRUE,
    .schema = schema
  )

For lighter extraction, use XML-like tags:

df |>
  llm_mutate(
    geo = "Where is {city}? Give country and continent in their own tags.",
    .config = cfg,
    .system_prompt = paste(
      "Use XML tags to specify different parts of the answer, but do not nest tags.",
      "Return <country>...</country> and <continent>...</continent>."
    ),
    .tags = c("country", "continent")
  )

Embeddings

sentences <- c(
  one = "Quiet rivers mirror bright skies.",
  two = "Thunder shakes the mountain path."
)

emb_cfg <- llm_config(
  provider = "voyage",
  model = "voyage-3.5-lite",
  embedding = TRUE
)

emb <- call_llm(emb_cfg, sentences) |> parse_embeddings()
dim(emb)

Batch embeddings:

emb <- get_batched_embeddings(
  texts = sentences,
  embed_config = emb_cfg,
  batch_size = 8
)

Conversation with history

chat <- chat_session(cfg, system = "You teach statistics tersely.")
chat$send("Explain p-values in 12 words.")
chat$send("Now give a three-word analogy.")
print(chat)

Parallel runs

setup_llm_parallel(workers = 4)

experiments <- build_factorial_experiments(
  configs = list(cfg),
  user_prompts = c("Summarize in one sentence: The Apollo program."),
  system_prompts = "Be concise."
)

res <- call_llm_par(experiments, progress = TRUE)
reset_llm_parallel()

Which helper should I use?

Notes

Contributions and support

Bug reports, feature requests, and support questions should be opened as GitHub issues. Please include a minimal reproducible example when relevant. Pull requests are welcome for focused improvements. See CONTRIBUTING.md for contribution, support, and reporting guidance. See COPYING for the full MIT license text.