Skip to contents

Why structured output matters

Chat models are useful for summarizing messy text, but analysis usually needs columns. hf_extract() asks a chat model for JSON that conforms to a schema and returns one tidy row per input.

notes <- c(
  "Amelie is a chef in Paris who mentions burnout.",
  "Jordan is a nurse in Seattle who mentions staffing."
)

hf_extract(
  notes,
  c(name = "string", occupation = "string", city = "string", theme = "string"),
  max_tokens = 120
)
#> # A tibble: 2 × 4
#>   name   occupation city    theme   
#>   <chr>  <chr>      <chr>   <chr>   
#> 1 Amelie chef       Paris   burnout 
#> 2 Jordan nurse      Seattle staffing

The lightweight schema form is enough for many workflows. Use a full JSON Schema list when you need enums, descriptions, or nested objects.

schema <- list(
  type = "object",
  properties = list(
    name = list(type = "string"),
    sentiment = list(type = "string", enum = c("positive", "neutral", "negative")),
    urgency = list(type = "number")
  ),
  required = c("name", "sentiment", "urgency"),
  additionalProperties = FALSE
)

hf_extract("Priya needs help today but likes the program.", schema)
#> # A tibble: 1 × 3
#>   name  sentiment urgency
#>   <chr> <chr>       <dbl>
#> 1 Priya neutral         1

Tool definitions

hf_tool() builds OpenAI-compatible function definitions for models/providers that support tool calling. The default chat model is chosen for easy first calls; for tools, use a tool-capable model such as Qwen/Qwen2.5-72B-Instruct.

add_tool <- hf_tool(
  "add",
  "Add two numbers.",
  c(x = "number", y = "number")
)

res <- hf_chat(
  "Use the add tool to add x=2 and y=3.",
  model = "Qwen/Qwen2.5-72B-Instruct",
  tools = list(add_tool),
  tool_choice = "auto",
  temperature = 0
)

res$tool_calls
#> [[1]]
#> [[1]][[1]]
#> [[1]][[1]]$id
#> [1] "call_8qCkqrmAP5IVrtocIYB3Pin6"
#> 
#> [[1]][[1]]$type
#> [1] "function"
#> 
#> [[1]][[1]]$`function`
#> [[1]][[1]]$`function`$name
#> [1] "add"
#> 
#> [[1]][[1]]$`function`$arguments
#> [1] "{\"x\": 2, \"y\": 3}"

Running tools in a conversation

hf_run_tools() executes tool calls requested by the assistant, appends tool result messages, and asks the model for the final answer.

convo <- hf_conversation(model = "Qwen/Qwen2.5-72B-Instruct")
convo <- chat(
  convo,
  "Use the add tool to add x=2 and y=3, then tell me the answer.",
  tools = list(add_tool),
  tool_choice = "auto",
  temperature = 0
)

convo <- hf_run_tools(
  convo,
  list(add = function(x, y) x + y),
  temperature = 0
)

convo$history
#> [[1]]
#> [[1]]$role
#> [1] "user"
#> 
#> [[1]]$content
#> [1] "Use the add tool to add x=2 and y=3, then tell me the answer."
#> 
#> 
#> [[2]]
#> [[2]]$role
#> [1] "assistant"
#> 
#> [[2]]$content
#> [1] ""
#> 
#> [[2]]$tool_calls
#> [[2]]$tool_calls[[1]]
#> [[2]]$tool_calls[[1]]$id
#> [1] "call_F00eoclT1eQllhG9kAI21Kim"
#> 
#> [[2]]$tool_calls[[1]]$type
#> [1] "function"
#> 
#> [[2]]$tool_calls[[1]]$`function`
#> [[2]]$tool_calls[[1]]$`function`$name
#> [1] "add"
#> 
#> [[2]]$tool_calls[[1]]$`function`$arguments
#> [1] "{\"x\": 2, \"y\": 3}"
#> 
#> 
#> 
#> 
#> 
#> [[3]]
#> [[3]]$role
#> [1] "tool"
#> 
#> [[3]]$tool_call_id
#> [1] "call_F00eoclT1eQllhG9kAI21Kim"
#> 
#> [[3]]$name
#> [1] "add"
#> 
#> [[3]]$content
#> [1] "5"
#> 
#> 
#> [[4]]
#> [[4]]$role
#> [1] "assistant"
#> 
#> [[4]]$content
#> [1] "The answer is 5."

Streaming callbacks

Set stream = TRUE to receive deltas as they arrive. When callback is omitted, deltas print to the console.

pieces <- character()

answer <- hf_chat(
  "Reply with exactly: OK",
  stream = TRUE,
  callback = function(delta) pieces <<- c(pieces, delta),
  temperature = 0,
  max_tokens = 8
)

paste0(pieces, collapse = "")
#> [1] "OK"
answer
#> # A tibble: 1 × 5
#>   role      content model                            tokens_used tool_calls
#>   <chr>     <chr>   <chr>                                  <dbl> <list>    
#> 1 assistant OK      meta-llama/Llama-3.1-8B-Instruct           1 <list [0]>