anes_2024_personas, rebuilt from the ANES 2024 Time
Series Full Release of May 19, 2026, and create_agents() and Focus Studio
draw from it instead of LLMR's copy. The earlier build turned every
unlabeled code into a missing value, so the six seven-point scales that
label only their endpoints lost their middle answers; it kept four
immigration-policy labels cut at 80 characters, put spouses' answers into
two respondents' fields, dropped renters and people who never attend
religious services through skip patterns, and ordered answer categories
alphabetically when it chose the respondents and fit the ideology score.
The rebuilt data write every answer of those scales as "5 on a scale from 1
(...) to 7 (...)" and every thermometer as "70 on a 0-100 feeling
thermometer", give full labels from the codebook, replace items not in this
release (paper-only and GSS items) with all-mode items or drop them, and
carry verbatim question wording in the dictionary. Only 26 of the earlier
100 respondents remain, so results built on the old data will differ.
LLMRpanel 0.6.3 ships an identical copy. The data remain a diversity
sample, not representative, and its selection favors atypical respondents;
the help page gives figures.create_agents_from_data() renders columns with value labels by the rules
of create_agents_from_survey() (labels, the scale sentence, missing codes,
numeric fields, warnings), so a labelled frame and the same file give the
same personas, and gains na_strings and scale_template. Missing-data
labels such as "-9. Refused" in text and factor columns, and SPSS
user-defined missing values, are left out with the reason in
persona_rendering_report() and a message. create_agents() screens the
frames it is given the same way and gains na_strings; it no longer fails
on factor columns. It stops on columns with value labels, which it would
show as bare codes ("party: -9"), and names create_agents_from_data().
One rule now decides whether a value label or a text value is a
missing-data label: it matches na_strings and either carries a negative
code or one of 90 and above, as the labeled code or a leading code in the
forms "-9. ", "-9 ", "[-9] ", "(-9) ", "-9: ", and "99 - ", or says
nothing but the matched wording once interviewer markers such as "{VOL}"
and "(VOL)", asides in parentheses or brackets, and filler words are set
aside. So "[-9] Refused" from haven::as_factor(levels = "both"), "-9
Refused", "Don't know/Refused (VOL.)", and "Not asked in survey" are left
out, where 0.5.5 showed them as answers, while "2. Refused to take sides",
"5. Missing the old system", and "I keep missing the bus" are kept. A value
label of a code from 0 to 89 that uses such words in an answer is now
shown; it used to be left out.
A subset of anes_2024_personas taken with [ or subset(), which drop
its dictionary, has the dictionary and demographic fields of its columns
restored by create_agents_from_data() from the package's copy, with a
message, so its answers are shown under their questions rather than the
column handles.
Identifier columns are recognized in any case and in common variants (ID,
CaseID, case_id, respondent_id, respid, resp_id, persona_id, uid,
record_id, row_id, ResponseId, participant_id, subject_id, user_id,
PROLIFIC_PID, workerId, caseno, QKEY) and are never rendered into
personas. A column named pid, which in survey data usually holds party
identification, is rendered.
format() of a focus_group_result returns the lines of a session file,
writeLines(format(result), "session.fg.jsonl"): one JSON object per line,
one row of the record each, with no configuration, key, or runner; key-like
values in the messages of failed calls, in the forms of the common
providers' keys, headers, and JSON fields, are written as <redacted>.
focus_group_from_transcript() reads such a file, a vector of its lines, a
result, or a FocusGroup back into a new session, reports every problem it
finds at once in an error of class focusgroup_format_error (a negative
token count among them; a guide-item phase without a moderator template,
and templates FocusGroup does not use, draw warnings), and gains
personas. inst/fg-session-spec.md describes the format for converting a
raw transcript with a language model. Data-frame input works as before.
format() of a FocusGroup, such as one read from a CSV transcript, gives
its session file in the same way; format() of a focus_group_arms
object stops and says to format one arm or the prefix.
Fields of the metadata and of guide items that the format does not define,
such as metadata$condition or an item's label, are written with the
prefix x_ and read back under their names.
run_focus_group_arms() continues a recorded session when its first
argument is a FocusGroup or a result: each arm starts from the record's
state just before fg_before_turn(t), rebuilt from the record (messages,
guide item, turns taken, the turn-taking rule's state, the interim summary
in force). Settings the record fixes cannot be passed again. A probe at
the branch point is asked once before the arms, which share it. A transcript
without moderator lines, which has no guide items to continue, stops with
an error that says so and how to name or add the moderator.
An injection from a participant's ID or handle at fg_before_turn(t) is
that participant's turn t, recorded with the routing rule "injected"; it
takes the place of the turn the session was about to give, recorded in
injections$replaced. At a call-on turn, a line from someone other than
the participant called on takes no one's place, and the participant called
on still answers. Outside voices named like "P4" are refused, and so is a
from that is one participant's ID and another's handle, before any
call.
Results gain metadata$guide, metadata$flow_params,
participants$communication_style, and injections$as_turn and
$replaced. analyze_focus_group() and Focus Studio accept a result whose
focus_group is NULL by rebuilding it from the record. A 0.5.5 record
without its session object, which has none of these, is read with the
phases its moderator lines record and the on_desire_failure of its
session line; its guide items are rebuilt from the recorded moderator
lines and marked verbatim, with a warning naming the items whose guide
text is unknown, since a continuation delivers their lines as recorded.
seed now leaves the caller's random-number state as it was, and must be
one whole number: seed = 1.7, which was truncated to 1, is an error.
fg_probe() gains scale = c(lo, hi). Each answer is read as one whole
number on that scale into the new value column of probes (restated
ranges, fractions such as "5/7", and markup are handled; two numbers, a
number out of range, or a number in words give NA, and so does an answer
that negates, compares, or reports another's number, such as "not a 7",
"at least 6", "2+", "minus 2", or "the others said 4"); an answer that cannot
be read is asked again once through the new template
participant_probe_reask; attempts counts the calls; the estimate counts
the re-asks; an answer still unread draws a warning.summarize_focus_group() sends messages to the model as recorded. It used
to replace names inside the text, which credited statements to the wrong
participant under permuted handles and corrupted IDs and names that are
ordinary words ("teacher", "May"). A claim whose text names a speaker, by
label or by an imported speaker's name, must cite a message by that
speaker (check named_speakers), so a claim that says "Ben said ..." while
citing Ana's message is sent back.
Neutral handles never take another agent's ID, the moderator's included,
and FocusGroup$new() refuses a participant whose ID is the moderator's
handle ("Moderator").
analyze_themes() calls the provider unless given .runner, as
summarize() does; it no longer falls back to the session's runner.
A provider's retries no longer draw from R's random-number stream, so a retry does not change later speaker draws or the common random numbers of arms.
In flat message mode each participant is told its handle; a turn the moderator's question gave to a participant carries a cue in both modes; the flat template asks participants to address others by their labels.
Self-label removal, in the engine and in fg_player(), treats an agent's
handle as its own label, not its ID.
The reply cleaner no longer deletes a participant's words: it removes only
a short opening self-introduction ("As a retired nurse,"), marks it with
metadata$preamble_removed, and keeps longer clauses ("As a teacher I
grade papers at night, ...") and discourse openings ("As an illustration,",
"As an alternative,", "As a side note,") as written.
A custom flow that chooses no participant or the moderator records no routing row.
A moderator question that opens with a handle and a colon ("P2: what do you think?"), or a short follow-up naming one participant ("And P3?", "What about P3?"), gives that participant the floor.
FocusGroup$new() validates max_participant_responses.
summarize_focus_group() and fg_turns() accept the list returned by
FocusGroup$run_arms().
The moderator now calls on participants who have not spoken. The new
argument call_on_silent of run_focus_group(), run_focus_group_arms(),
and FocusGroup$new() defaults to c("opening", "midpoint", "end"): after
the discussion of the first, the middle, and the last discussion item
(items 2, 4, and 7 of the default guide), each participant who has not
spoken since the previous such point is asked by name, one at a time, with a
fixed line (call_on_text) and no model call, and answers next. The first
point follows the round-robin opening question of Krueger (2002),
Designing and Conducting Focus Group Interviews. "none" turns call-ons
off; guide item numbers choose other points. Called-on replies need no
desire score and are recorded with the routing rule "called_on" and the
trigger_message_id of the call-on line. Sessions under the default
settings therefore contain more turns than under 0.5.4.
The call estimate counts one reply per participant at each call-on point.
The default session plans 111 model outputs (93 without call-ons), and the
default max_calls rises from 100 to 120. The Studio's plan counts the same
reserve.
Agents no longer see the IDs a user gives them. Every prompt shows
participants as P1, P2, ... and the moderator as "Moderator", and each
participant is told its own handle ("In this discussion you are P3."). This
covers history lines, the roster line, moderator placeholders, desire and
probe prompts, and the interim summary that agents read. The records keep
the IDs; the transcript and participants gain a handle column and the
metadata a handles table. handles = "ids" shows the IDs, with a message;
a named vector sets handles of your own. focus_group_from_transcript()
keeps the speakers' own labels.
FocusGroup$new() now requires the names of agents to equal the agents'
IDs.
Identifier columns (id, respondent_id, persona_id) are no longer
rendered into persona text; they stay in source_ids and are reported as
dropped. create_agents_from_survey(survey_vars = NULL) leaves out a
variable that looks like a case ID, with a message.
moderator_opening no longer asks participants for their participant ID,
and moderator_probing_focused no longer names the first participant in
the roster.
Under "desire_based" turn-taking, the participant who closed one question
could not open the next, because the previous-speaker exclusion outlived the
moderator's line. The new ConversationFlow$note_moderator_turn() clears
it whenever the moderator speaks, so the exclusion bars only back-to-back
participant turns.
A moderator line whose last question opens or closes with one participant's
handle ("P3, do you agree?", "Could you say more, P4?") gives that
participant the next turn, recorded with the rule "addressed". The flow
used to choose, so someone else could answer. A line that names several
participants or asks the whole group addresses no one.
Round robin passes over a participant who has just answered the moderator, so no one speaks twice in a row.
New fg_player() writes a finished session to one self-contained HTML file
that replays it step by step, for a talk or a check by eye: a title page,
one page per participant with the persona as sent, then every logged
message in order. Before each turn it shows the speaker-selection record:
desire scores with the previous speaker marked as not asked and tie draws
stated, probabilistic chances, call-ons, and addressed turns. Private
answers appear where they were asked, and an end page gives turns per
participant and the final summary. mode = "static" shows every step at
once, and the result of run_focus_group_arms() gives one file per arm.
The page is built from the records alone, calls no model, and makes no
network request; a missing record stops with an error naming it.
The package includes inst/extdata/player-session.rds, a synthetic session
made with a scripted runner, for trying fg_player() offline.
New summarize_focus_group() summarizes a session, branched arms, or an
imported transcript by participant, guide item, disagreement, trajectory,
turn, or whole session, at the lengths "headline", "brief", and
"detailed", which come back together from one call per unit. The model
returns claims as JSON, and prose is rendered only from claims that pass
checks run in code: cited messages exist, lie in the unit, and come from the
speakers named; quotes are verbatim up to case, whitespace, and quotation
marks; wording that speaks for the whole group must cite every participant
who spoke; silent participants can be reported only as silent; private
probe wording may not appear unless include_probes = TRUE. A reply that
fails is sent back once with each failure stated; a second failure raises
focusgroup_summary_check_failure with the checks, the raw replies, and the
partial summary. on_fail = "drop" sets failing claims aside unless they
report dissent or change. The summarizing model sees handles, never user
IDs or personas. Calls are recorded in the summary's own calls table and
checked against max_calls.
New fg_turns() returns the turn table of a session without a model call:
each message's guide item, handle, injection status and visibility,
routing rule, and whether the speaker was addressed, called on, or followed
the previous message.
get_default_prompt_templates() gains summary_participant,
summary_item, summary_disagreements, summary_trajectory,
summary_turn, summary_session, and summary_reask.
final_summary = FALSE in run_focus_group(), run_focus_group_arms(),
and run_simulation() skips the final summary call and lowers the estimate
by one per arm.
A new point "before_closing" for fg_probe() and fg_injection() comes
after the last discussion item and before the closing line; "end" comes
after the closing line.
A label a speaker writes for itself at the start of a reply (**MOD:**,
P3:) is removed, and the message is flagged with self_label_removed; the
raw reply stays in calls.
Every message records its guide item (item); routing gains
trigger_message_id; analyze_participation_balance() gains called_on.
summarize() labels speakers by handle, with the ID beside it in the final
summary when the two differ, and leaves out the roster line; it stops when
there is nothing to summarize instead of returning a sentence that reads as
a summary; and it calls the provider unless given a .runner, instead of
reusing the runner of the session.
Message ids follow the largest id in the log, so an imported log with gaps never gets a duplicate id.
jsonlite is now imported, by fg_player() and summarize_focus_group().
create_agents_from_survey() renders answers the file leaves without a
value label; they used to be dropped without notice. A number between the
two labeled endpoints of a scale is shown on that scale ("On a scale of 1
to 7, where 1 is ... and 7 is ..., you place yourself at 5"); a number in a
numeric field is shown as the number ("age: 25"); any other unlabeled code
is shown as the code, with the warning "Shown as the bare code because the
file has no value label for it". A scale that also labels a midpoint (0, 5,
and 10, say) leaves an unlabeled answer as the bare code, with that
warning. Persona text changes for files such as the ANES, whose seven-point
scales label only their endpoints.
New argument scale_template sets the sentence for an answer placed on
such a scale, for all items or per item, using the placeholders {lo},
{lo_label}, {hi}, {hi_label}, and {value}. A template without
{value}, or with an unknown placeholder, is an error; the rendering
report records the template used.
New argument question_wording, a named character vector giving the
question as asked for each survey item. An item without a wording is shown
under its variable label, with the warning "No question wording was
supplied for N survey item(s)"; it used to be shown under the name given in
survey_vars (such as "scale_env_vs_business") without a warning. A name
is now used only for an item the file gives no label.
New argument value_labels (a data frame with variable, code, and
label, or haven's named-list form) replaces the file's label for a code
or labels a code the file leaves unlabeled. Labels exactly 80 characters
long, the most a Stata label holds, draw the warning "Labels exactly 80
characters long may have been cut short".
New function persona_rendering_report(): one row per requested field and
item of each persona, saying what text it was rendered as, or that it was
dropped and why. Rosters from create_agents_from_survey(),
create_agents_from_data(), and create_agents() carry it.
Other new warnings and messages: unlabeled negative codes are left out as
missing, as before, now with a warning; labels of negative codes are shown
as answers, as before, now with a warning; answers left out as missing
values are listed in a message; requested variables that are not in the
file are named in a warning (they used to be dropped silently); items
shown under the same text get " [VAR]" appended, with a warning (the
persona used to keep the first answer and lose the second). Tagged missing
values (haven NA(d)) are dropped; their labels used to be rendered as
answers unless they matched na_strings. Variables requested twice are
rendered once, with a warning, as are value_labels and scale_template
entries for variables not requested and variable labels exactly 80
characters long.
The default na_strings, now returned by the new
fg_default_na_strings(), also covers the labels the ANES 2024 release
gives its negative missing-data codes ("-3. RESTRICTED", "-6. No post
interview", "-7. Insufficient partial, interview deleted", "-2. DK/RF in
...", "-2. Data will be released separately when available", and others)
and the WVS labels "No answer", "Not applicable", and "Not available".
Such answers are now left out as missing; they used to be shown as answers
("Answer: -3. RESTRICTED"). "Error" and "RESTRICTED" count only as a whole
label, and "break off" and "insufficient" only in a label with a negative
code.
Numbers in personas are written in full ("income: 100000"); values of 100000 and above used to appear in scientific notation ("1e+05").
New fg_injection() puts a message into a session at a stated point,
fg_after_item(k) (after the moderator's line at guide item k) or
fg_before_turn(t), from the moderator or a named outside voice such as
"Guest expert", either into the shared log or only into the participants'
prompts. Injected messages are flagged in the log with their arm and cut
point.
New run_focus_group_arms() runs a session up to the injection point once
and then the full remainder once per arm, so the arms share every message
before the injection; each branch records the prefix it continued, and
each arm restores the random-number state of the branch point.
New fg_probe() asks each participant a question at stated points
(including "start" and "end"); the answers are saved in the result's
probes table and never enter the shared log or later prompts.
New moderator_verbatim (TRUE, or guide item numbers) delivers guide
items as written, without a model call.
run_focus_group() gains injections, probes, and moderator_verbatim.
New errors are raised before any call for a point beyond the guide's items
or turns, an injection from a participant or "System", arms that are
unnamed, not distinct, fewer than two, inject nothing, or name different
first points, and invalid moderator_verbatim values; a point the session
never reached draws a warning.
Under "desire_based" turn-taking, a desire score that cannot be obtained
after its retry now stops the session with an error of class
focusgroup_desire_failure, returned inside a
focusgroup_incomplete_session. It used to discard every agent's valid
score and draw the speaker uniformly at random, noted only in metadata.
on_desire_failure = "random" continues instead: it chooses among the
valid scores by the usual rule and draws at random only when none is
valid, recording the fallback.
Scores and utterances are read from the final answer only: reasoning in
<think>, <thinking>, or <reasoning> tags or in the gpt-oss channel
markup is removed, and a reasoning block that never closes counts as no
answer. A number inside the reasoning used to be read as the score.
Desire-scoring calls keep a configured temperature; it used to be removed, against a code comment saying it was kept. None is added when the configuration sets none.
run_focus_group() gains reasoning_effort (one setting for every call,
or one per call type: moderator, participant, desire, summary, probe) and
max_tokens_config, which now also sets desire_retry (default 256),
summary, interim_summary, and probe.
Results gain calls (every model call, including retries and failures:
messages sent, raw reply, reasoning text, cleaned text, score, settings,
and tokens including reasoning tokens) and routing (every speaker pick:
candidates, exclusions, scores, rule, and how a tie was broken).
Session metadata records the response limit, seed, on_desire_failure,
reasoning_effort, token limits, per-call settings, prompt templates,
standing rules, and the FocusGroup and LLMR versions. Only allowlisted
model parameters are recorded, so no key or endpoint enters a record.
Reply cleaning keeps discourse openings such as "As a result," and "As an aside,"; it still removes persona self-introductions such as "As a teacher,".
analyze_participation_balance() lists every participant; one who never
spoke has 0 messages. They used to be left out.
The interim summary covers the messages the prompt window drops, instead of the last 30 messages, and is not made while the window holds the whole discussion.
Moderator templates no longer assert history that may not exist, such as a completed icebreaker.
In the Studio's continuation tab, the edited message is the one previewed; with a System row in the roster they used to be one message apart.
create_agents_from_survey() applies rows to the rows of the file, so
rows = c(2, 3) returns the respondents in file rows 2 and 3, and a logical
selector or predicate marks file rows. The selector used to be applied after
respondents without any demographic value had been dropped, so when such a
respondent came before the selected rows, integer and logical selectors
returned respondents further down the file (in a file whose first respondent
gave no demographic values, rows = c(2, 3) returned file rows 3 and 4). A
predicate now also sees the respondents without demographic values; where it
gives NA for them, they count as not selected. A message says how many
respondents were left out for lack of demographic values. When rows
selects only such respondents, the call stops with "rows selected no
respondents with demographic values."; when the file has none with a
demographic value, it stops with "No respondent in the file has a value for
any of demographic_vars." Such a file used to give participants with
generic personas.
A sampling-weight column named in weights is no longer rendered into
personas by create_agents_from_survey() or create_agents_from_data(),
and create_agents_from_survey() leaves it out even when demographic_vars
or survey_vars name it. Personas used to state the weight as one of the
respondent's answers ("Question: Full sample weight Answer: 2.9"), so the
persona text of every weighted call that rendered the default columns
changes. When the weight column is the only demographic variable named,
create_agents_from_survey() stops with "No usable demographic_vars
remain after excluding the sampling-weight column."
create_agents_from_data() and create_agents_from_survey() refuse
sampling weights they used to repair without notice. Numeric and
haven-labelled weights draw the same respondents as before. These inputs are
now errors:
rows leaves eligible ("...
has no positive weight among the eligible respondents."); the draw used to
fall back to equal probabilities.A draw with fewer positive weights than participants to fill now stops with
"A draw of 3 respondents requires at least 3 eligible respondents with
positive weight; found 2." instead of the base R error "too few positive
probabilities". When exactly n_participants respondents are eligible, all
of them are used whatever their weights, as before.
rows is validated in the same way by both functions. Positions that are
fractional, missing, or out of range stop with "rows must be whole numbers
between 1 and the number of respondents ..."; both functions used to
truncate them (1.9 selected row 1), drop them, or fail with an unrelated
error. A logical selector or a predicate's result must have one logical
value per respondent ("... must have one logical value per respondent
..."); a vector of the wrong length used to be used as far as it went. NA
in such a vector counts as not selected, as before. A position given twice
selects its respondent once, where either function could return one
respondent as two participants, and asking for more participants than
there are distinct eligible respondents stops with "4 participants were
requested, but only 3 distinct respondents are eligible."
Rosters returned by create_agents_from_data() and
create_agents_from_survey(), and by create_agents() when it draws from
the bundled ANES records, carry a source_rows attribute giving the row
each participant was drawn from, and a source_ids attribute with the
data's id, respondent_id, or persona_id columns when it has any. The
rosters used to carry no record of the rows drawn.
Sessions run without an explicit max_participant_responses are shorter.
The option focusgroup.max_participant_responses is now set to 2 when the
package loads, as the run_focus_group() help said; it was set to 3, so
that documented default never applied, and FocusGroup$new() also fell
back to 3. With the
default guide and flow the estimate falls from 135 to 93 model outputs, and
run_focus_group(topic, config) no longer stops at the default max_calls
of 100. A call that raised max_calls or set confirm = TRUE now gets two
participant responses per question instead of three; pass
max_participant_responses = 3, or set
options(focusgroup.max_participant_responses = 3), to restore them.
run_focus_group() refuses a max_calls that is infinite or beyond the
integer range ("max_calls must be one finite positive integer."). Inf
used to switch the limit off; confirm = TRUE still does. A guide whose
estimate exceeds the integer range stops with "The model-output estimate
(...) exceeds the integer range ..." before the script is built, and an
invalid guide count is reported with its value as well as its phase.
When a model call fails partway through a session, run_focus_group()
raises an error of class focusgroup_incomplete_session instead of passing
the bare error on, and the completed turns are no longer lost. The error's
partial field holds the focus_group_result of the turns completed so
far, its phase field the script phase that was running (or
"interim_summary"), and its parent field the original error. Results
gain metadata$complete and metadata$failure, and printing a
FocusGroup from a session reports whether it completed. A failed final
summary still warns and returns the full transcript.
The timestamp column of a result's transcript is in UTC; it used to be
shown in the session's time zone. The instants are unchanged.
Token usage that a provider or runner did not report is recorded as
unknown instead of 0. A message's sent_tokens, rec_tokens, and
total_tokens are NA when a call behind the message reported none; they
used to read 0. run_focus_group() results gain usage$unknown, the number
of calls whose usage was incomplete, and sent, rec, and total are
lower bounds when it is positive. usage$total adds each call's reported
total, or its sent and received counts when it reported no total; it used
to be sent + rec, which leaves out tokens a provider counts only in its
total. The printed token count says "at least" or "unknown" when usage is
incomplete.
Group usage now includes interim summaries, which used to be left out, and
calls whose output is rejected, such as an unsuccessful runner row or a
first reply whose retry fails; their usage used to be dropped. A reported
count that is not a nonnegative whole number is treated as unknown with the
warning "Token count sent is not a finite nonnegative whole number; it is
treated as unknown." A response whose usage cannot be read at all warns
"Token usage could not be read from the response; its counts are treated as
unknown.", and a total beyond the numeric range warns "Accumulated token
usage exceeds the numeric range; completeness is unavailable."
Desire scores from a broadcast call are matched to agents by agent_id or
.experiment_id when the reply carries them, and by position otherwise.
When the reply cannot be matched to the agents scored, the warning
"Broadcast usage could not be matched to individual agent calls." is given
and those agents are scored again one at a time, which costs one call each.
Such a reply used to be read by position, and its usage was split evenly
among the agents.
An unsuccessful runner row (success = FALSE) raises an error of class
focusgroup_model_error whose message is the row's error_message, or its
error; only error used to be read.
FGAgent objects gain the fields tokens_total_agent and
usage_unknown_agent, and FocusGroup objects gain total_tokens and
calls_usage_unknown. Objects saved by earlier versions remain readable.
focus_group_from_transcript() says when no speaker id is a moderator alias
and every speaker is therefore analyzed as a participant; it used to add the
placeholder moderator without saying so. The help, README, and vignettes now
describe the exact-alias rule; they still described an earlier substring
match.
focus_group_from_transcript() keeps the fields a transcript supplies:
message_id, round, is_moderator, phase, response_id,
finish_reason, the three token counts, duration_s, provider, model,
timestamp, and metadata. It used to keep only the speaker and the text,
setting every phase to "imported", the timestamp, provider, and model to
NA, and the rounds from moderator turns, which left every round 0 when no
moderator was found. Token counts the transcript does not supply are NA;
they used to be 0.
Missing text among the imported turns becomes an empty utterance with the
warning "1 utterance(s) with missing text were imported as empty text."; the
conversion used to be silent. A supplied field that cannot be converted is
an error naming the column and rows ("Column 'round' contains invalid values
at source row(s) 4."). Timestamps are read in UTC, and timestamps that
cannot be parsed become NA with a warning giving their number.
A supplied is_moderator column now decides who the moderator is, and the
aliases are consulted only without one; the column used to be ignored, so a
speaker named "Moderator" was the moderator even where the column said
otherwise. A column that gives a speaker two roles, marks more than one
speaker, or disagrees with moderator_id is an error, and so is naming
"System" as the moderator ("moderator_id cannot be 'System'.", "'System'
cannot be the moderator."), which used to add a placeholder. When the
column marks no speaker, the placeholder message gives that as the reason.
If a participant is already named "MOD", the placeholder moderator is named
"MOD_2", or the next free suffix.
The FocusGroup methods plot_message_length_timeline() and
plot_participation_by_agent() count words with the rule the analysis
tables use, so a blank utterance has no words and surrounding whitespace
adds none. They used to split the raw text, which for imported text with
leading whitespace or blank text counted one word more per affected message
than the tables did (" hello world " counted 3 words, and " " counted
1).
The Studio requires LLMR.shiny 0.1.3 or later and stops with "FocusGroup Studio requires LLMR.shiny >= 0.1.3; installed version is ..." when an older version is installed. FocusGroup 0.5.2 suggested 0.1.2 and did not check the installed version.
The Studio's usage tile counts the calls each action made and their reported tokens, shown as lower bounds when a call reported none, and it records the calls an action made before failing. It used to record the planned number of calls for a continuation experiment, two calls with 0 tokens for model analysis, and nothing for a run that failed, and it showed exact counts when a reply reported no usage.
A Studio run that fails partway keeps its partial transcript for analysis
and download, with a notice naming the phase where it stopped; it used to
show only the error. Saved sessions keep the completion status and the
roster's source_rows and source_ids. A continuation from a session
saved by an earlier version rebuilds its agents with the current class,
keeping their saved persona and style.
The Studio's participant-source choices are labeled "ANES demographics" and "ANES respondent fields". They were labeled "Synthetic personas" and "ANES 2024 personas", although both draw respondents from the bundled ANES records.
The Studio's Analyze tab shows the transcript as imported by
focus_group_from_transcript(), one row per kept turn, and shows the
importer's warnings as notifications. It used to show the uploaded rows,
and restored the exported fields only when no row had been dropped.
The Studio's Analyze tab reads an uploaded transcript CSV with every value
as written, and gives the columns other than the speaker, the text, and
response_id their usual types only after the columns are mapped. Type
inference used to read the speaker ids 001 and 01 as the same number 1,
alter long numeric ids such as 12345678901234567890, and turn the text "NA"
into a missing utterance.
Corrections replacing 0.5.1, which was never published.
max_calls of 100. The old defaults planned 467 outputs and
always tripped their own gate.create_agents_from_data() and
create_agents_from_survey() remain the detailed routes, the first also
carrying respondents' stated answers.unit = "speaker" restores the old behavior. Fitting few
documents relative to num_topics now warns.message_id.NA, where it used to become a zero that
scored as real evidence. Unscored agents are rescored individually, flow
parameters are validated, and the selection metadata records
threshold-lowering and highest-below-threshold fallbacks.analyze_focus_group() gains speakers (default "participants", which
keeps the moderator out of the text statistics) and language
(English-specific methods skip, with a note, for other languages).default_llmr_config() and readr dependency;
R floor aligned with LLMR at 4.1.Initial CRAN release.
run_focus_group() is the single high-level session runner. It requires an
explicit config, accepts n_participants, guide, flow, and
message_mode, and performs a model-call cost preflight before live work.run_focus_group() returns a focus_group_result with the stable fields
focus_group, transcript, summary, participants, usage, and
metadata. Its print method gives a concise run summary.create_agents(), create_agents_from_data(), and
create_agents_from_survey() construct agent-ID-keyed rosters from supplied
personas, in-memory records, labeled survey files, and pre-rendered persona
panels.ConversationFlow is the public extension base. Built-in round-robin,
probabilistic, and desire-based flows are constructed with
create_conversation_flow().config; group-level configuration uses
admin_config. The FGAgent configuration field is config..runner seam accepts an experiments data frame with config and
messages list-columns and returns those rows with at least
response_text.message_id and record the
moderator cycle as round, while retaining phase.analyze_focus_group() returns a focus_group_analysis with stable fields,
typed empty components, and an issues table. Descriptive analysis runs
offline. Thematic analysis and model summaries require an explicit config.focus_group_from_transcript() accepts moderator_id; when it is omitted,
speaker identifiers containing "mod" provide the fallback.get_default_prompt_templates() contains only templates used by supported
package operations.run_focus_studio() starts the optional Shiny interface for running
sessions, reading transcripts, and comparing continuations from edited
histories..rds sessions and transcript-only
CSV files, and a finished run can be passed directly to the Analyze tab.analyze_focus_group() components, optional
model themes and summaries, package guidance, source provenance, and timing.LLMR.shiny.