
Auto-code text with a codebook using structured LLM extraction
Source:R/llm_auto_code.R
llm_auto_code.RdSends each text to an LLM with a structured-output schema built from the
codebook, and adds one logical column per code to data. Each text is coded
in an independent conversation, so earlier texts never leak into the
context of later ones.
Arguments
- data
A data frame with at least an ID column and a text column. Rows may be participants or individual transcript segments.
- id_col
Column holding the participant/unit identifier (unquoted or a string).
- text_col
Column holding the qualitative text (unquoted or a string).
- codebook
Character vector of codes. Either unnamed labels (
c("UI_Confusion", "Feature_Request")) or named definitions (c(UI_Confusion = "The user reports being lost or confused")). Explicit definitions give far more reliable coding.- chat
An ellmer::Chat object, e.g.
ellmer::chat_openai()orellmer::chat_anthropic(). Required unlesscoderis supplied.- coder
Optional function
function(texts, codebook)returning a data frame of logical columns named after the codes, one row per text. Use this to plug in another backend, ormock_coder()for offline demos.- max_active
Maximum number of concurrent LLM requests.
Value
data with one logical column per code. Missing or empty texts get
NA. The codebook is stored in the "saydo_codebook" attribute so
cross_tab_themes() can find the code columns automatically.
Examples
# Offline, with the keyword-based demo coder:
df <- mock_mixed_data(30, seed = 1)
coded <- llm_auto_code(df, user_id, transcript_feedback,
codebook = c("UI_Confusion", "Positive_Experience"),
coder = mock_coder())
head(coded)
#> # A tibble: 6 × 7
#> user_id profile session_duration_sec errors_encountered transcript_feedback
#> <chr> <chr> <dbl> <int> <chr>
#> 1 USR-1001 frustrat… 470 11 Why is the checkou…
#> 2 USR-1002 casual 215 2 The application lo…
#> 3 USR-1003 frustrat… 536 4 The layout changed…
#> 4 USR-1004 efficient 190 0 Really clean UI. N…
#> 5 USR-1005 frustrat… 428 10 I got completely l…
#> 6 USR-1006 casual 218 1 I opened the setti…
#> # ℹ 2 more variables: UI_Confusion <lgl>, Positive_Experience <lgl>
if (FALSE) { # \dontrun{
# With a real LLM (needs an API key, e.g. OPENAI_API_KEY):
coded <- llm_auto_code(
df, user_id, transcript_feedback,
codebook = c(
UI_Confusion = "The user reports being lost, confused or unable to find something",
Feature_Request = "The user asks for a feature or capability that does not exist"
),
chat = ellmer::chat_openai(model = "gpt-4.1-mini")
)
} # }