Returns the lines of a session file: one JSON object per line, each one
row of a table of the result, so that writeLines(format(result), "session.fg.jsonl") saves the session and focus_group_from_transcript()
reads it back.
Arguments
- x
A
focus_group_result, or a FocusGroup.- ...
Unused.
Value
A character vector of JSON lines, one per row, in the order session, participants, items, messages, routing, probes, injections, calls.
Details
Each line has a type, and its other fields are the columns of the table
it comes from:
sessionOne line, first: the fields of
metadata(topic, purpose, the turn-taking rule and its parameters, the settings, the status, the versions), withsummaryandusage, andformatandversion, which name the file format.metadata$handlesandmetadata$guideare written in the lines below. A field ofmetadatathe format does not define, such as a researcher'smetadata$condition, is written with the prefixx_("x_condition"), and the reader restores it under its name.participantOne line per row of
participants, the moderator's included.itemOne line per guide item, in order:
item(its number) and the item'sphase,text, andverbatim, where set; any other field of the item, such as alabel, with the prefixx_.messageOne line per row of
transcript.routing,probe,injection,callOne line per row of
routing,probes,injections, andcalls. A call line leaves outmessages, the messages the call sent, which grow with the square of the session's length and are not needed to read or continue it.
The writing is canonical, so that a file read back and written again is
the same file. A missing value is null. A whole number stored as a
double is written with a decimal point (160.0) and an integer without
one, and every double is written with as many digits as it needs to be
read back exactly. A named vector, such as the desire scores of a pick,
is an object; candidates and tied are always arrays. An empty list
is {}. Times are written in UTC to the microsecond
("2026-10-24T14:30:00.25Z"). Every character outside ASCII is written as
a \uXXXX escape, so the file is plain ASCII.
The file holds no model configuration and no runner, and so no key or
endpoint from a configuration: metadata holds none, and the recorded
call settings are the allowlisted model parameters. The message of a
failed call is written as recorded, except that key-like values in it
(the values of query parameters such as key=, bearer tokens, the
values of x-api-key: and api-key: headers and of JSON fields such as
"api_key", "apiKey", "token", and "secret", and strings of the
forms of OpenAI, Groq, xAI, Hugging Face, and Google API keys) are
replaced by <redacted>. saveRDS() of the result, by contrast,
saves focus_group, which holds the configuration (and so any literal
key or private endpoint in it) and the runner; set result$focus_group <- NULL first to save the record alone. analyze_focus_group() and the
Studio rebuild the session object of such a result from its tables.
For a result saved by FocusGroup 0.5.5 or earlier, the guide, the flow
parameters, and the communication styles, which it lacks, are read from
the fields of its focus_group, when it has one; without one,
focus_group_from_transcript() rebuilds them from the record as its
help page describes.
format() of a FocusGroup, such as one read by
focus_group_from_transcript(), gives the lines of its session file in
the same way, from its log and records. A focus_group_arms object holds
several sessions and has no one file: format an arm,
format(x$arms[["name"]]), or the shared prefix, format(x$prefix).
See also
focus_group_from_transcript() to read the lines back, and
vignette("architecture", package = "FocusGroup").
Examples
cfg <- LLMR::llm_config("groq", "openai/gpt-oss-20b",
api_key = LLMR::llm_api_key_env("GROQ_API_KEY"))
scripted <- function(experiments, ...) {
experiments$response_text <- "Evening hours matter most to me."
experiments
}
result <- run_focus_group("Library hours", cfg, n_participants = 2,
guide = list(Engagement = "Which hours matter?",
Closing = "Close the session."),
flow = "round_robin", verbose = FALSE,
.runner = scripted)
path <- tempfile(fileext = ".fg.jsonl")
writeLines(format(result), path)
substr(readLines(path)[1:3], 1, 70)
#> [1] "{\"type\":\"session\",\"format\":\"fg-session\",\"version\":1,\"topic\":\"Library h"
#> [2] "{\"type\":\"participant\",\"id\":\"P1\",\"handle\":\"P1\",\"role\":\"participant\",\"is"
#> [3] "{\"type\":\"participant\",\"id\":\"P2\",\"handle\":\"P2\",\"role\":\"participant\",\"is"
fg <- focus_group_from_transcript(path)
fg$conversation_log[[3]]$text
#> [1] "Evening hours matter most to me."
