Main R6 class for managing and running a focus group simulation. It orchestrates the simulation through distinct phases (Opening, Icebreaker, Engagement, Exploration, Closing) guided by a question script, manages agent interactions via a turn-taking flow, and provides methods for analysis and visualization of the conversation.
Value
An R6 generator. FocusGroup$new() returns a session object;
most analyses run through run_focus_group(), which builds, runs, and
returns one. Construction alone makes no model call.
Details
The group's token fields count every model call made on its behalf: the
moderator's and participants' turns with their retries, desire scores,
interim and final summaries, and model analyses run later, such as
analyze_themes(). run_focus_group() describes how unreported usage is
handled. A transcript imported with focus_group_from_transcript() adds
no calls. Objects saved by FocusGroup 0.5.2 and earlier lack
total_tokens and calls_usage_unknown.
A session run by run_focus_group() carries a session_status attribute,
a list with complete and failure as in that function's metadata.
Every prompt shows the agents under their handles, never under their IDs: the participants' turns, the moderator's lines, desire scores, probes, and the interim summary that stands in for messages the prompt window no longer holds. The researcher's records, the log and the routing record among them, keep the IDs. Each participant's system message names its handle ("In this discussion you are P3.").
The moderator calls on silent participants at the guide items given by
call_on_silent. When the discussion of such an item ends, each
participant who has not spoken since the previous call-on point (or the
start of the session) is asked, in roster order, by the line in
call_on_text, delivered as written with no model call; the participant
then answers, without a desire score, and the routing record gives the
turn the rule "called_on". With the default guide of
run_focus_group() the points are items 2, 4, and 7. Krueger (2002)
describes the opening question of a focus group as a round robin; the
first point gives every participant a turn early in the session.
A moderator line whose last question opens or closes with one
participant's handle ("P3, do you agree?", "P3: what do you think?",
"Could you say more, P3?"), or is a short follow-up naming one ("And
P3?", "What about P3?", "How about P3?"), and does not also ask the
group, gives that participant the next turn, with the rule "addressed".
References
Krueger, R. A. (2002). Designing and Conducting Focus Group Interviews. University of Minnesota.
Public fields
topicCharacter. The main topic of the focus group discussion.
purposeCharacter. The primary purpose or research objective of conducting the focus group.
agentsNamed list. A list of
FGAgentobjects participating in the simulation, indexed by their IDs.moderator_idCharacter. The ID of the agent designated as the moderator.
conversation_logList. A chronological log of all messages. Each message is a list that includes at least
message_id,round,item,speaker_id,handle,is_moderator,text,timestamp,phase, andmetadata, along with call metadata:response_id,finish_reason,sent_tokens,rec_tokens,total_tokens,duration_s,provider, andmodel.message_idis unique message order; moderator and participant messages from one moderator cycle shareround, anditemis the guide item the message belongs to (0 for the roster row).speaker_idis the agent ID andhandlethe label the agents saw.handlesNamed character vector. The label under which each agent appears to the other agents, named by agent ID; see the
handlesargument ofnew().call_on_silentList. The guide items at which the moderator calls on the participants who have not spoken, as
points(the name of each point:"opening","midpoint","end", or"item:<k>") anditems(the item numbers). Both are empty when call-ons are off.call_on_textCharacter. The moderator's call-on line;
{participant}is replaced by the participant's handle.turn_taking_flowA
ConversationFlowobject dictating participant turn-taking.prompt_templatesList. Holds prompt templates for agent/moderator actions.
question_scriptList. A structured list defining phases and specific questions/actions for the moderator. Each element is a list with
phase(e.g., "opening", "icebreaker_question") and optionallytext(for specific questions).current_question_textCharacter. Text of the current question being discussed.
current_conversation_summaryCharacter. An LLM-generated summary of earlier parts of the conversation, used for managing context length in prompts.
final_summaryCharacter. Final LLM-generated summary from the most recent simulation run.
message_modeCharacter. The message construction used by the most recent
run_simulation()("roleflip" or "flat"), recorded so a saved object can be replayed (e.g. in the GUI continuation experiment) with the same construction.admin_configAn
llm_configobject for group-level model tasks.max_tokens_utteranceInteger. Default max tokens for participant utterances.
max_tokens_moderatorInteger. Default max tokens for moderator utterances.
max_tokens_desireInteger. Default max tokens for desire-to-talk queries.
max_tokens_desire_retryInteger. Max tokens for the one retry of a desire query whose reply was empty or cut off.
max_tokens_summaryInteger. Max tokens for the final summary.
max_tokens_interim_summaryInteger. Max tokens for an interim summary.
max_tokens_probeInteger or
NULL. Max tokens for a probe answer;NULLuses the agent configuration's limit, or 300 when it sets none.reasoning_effortNULL, one reasoning-effort setting for every call type, or a named list of settings by call type ("moderator","participant","desire","summary","probe"). A setting is written over the configuration'sreasoning_effortfor those calls;NULLleaves the configurations as they are.max_participant_responsesInteger. Maximum number of participant exchanges per round before the moderator can intervene. Can also be set globally via
options(focusgroup.max_participant_responses = N).total_tokens_sentNumeric. Tokens sent across all LLM calls in the group, as reported; calls that reported no count add nothing.
total_tokens_receivedNumeric. Tokens received across all LLM calls, as reported.
total_tokensNumeric. Total tokens across all LLM calls: each call's reported total, or its sent and received counts when it reported no total.
calls_usage_unknownNumeric. Number of calls whose usage was incomplete, so that the three token fields are lower bounds when it is positive;
NAwhen that number cannot be determined, as after a call that raised an error.recordsList. The record of the most recent session:
calls, one entry per model call the session made;routing, one entry per speaker selection;probesandinjections, described inrun_focus_group(); andinfo, the versions, standing rules, and settings in force when the session started.run_focus_group()returns the first four as data frames. Objects saved by FocusGroup 0.5.3 and earlier lack it.
Methods
Method new()
Initialize a new FocusGroup simulation.
Usage
FocusGroup$new(
topic,
purpose,
agents,
moderator_id,
turn_taking_flow,
question_script = list(),
prompt_templates = list(),
admin_config = NULL,
max_tokens_config = list(),
max_participant_responses = NULL,
reasoning_effort = NULL,
handles = NULL,
call_on_silent = c("opening", "midpoint", "end"),
call_on_text = NULL
)Arguments
topicCharacter. The main discussion topic.
purposeCharacter. The primary purpose of the focus group.
agentsNamed list of initialized
FGAgentobjects, each named by itsid.moderator_idCharacter. The ID of the agent acting as moderator.
turn_taking_flowAn initialized
ConversationFlowobject.question_scriptList. Moderator's script defining phases and questions. If empty, a minimal default script (opening, generic discussion, closing) is used. An item with
verbatim = TRUEis delivered as itstextreads, with no model call; it must have a text.prompt_templatesList. Custom prompt templates. Defaults are used if not provided.
admin_configAn
llm_configobject for administrative model tasks. IfNULL, the moderator's explicitly suppliedconfigis used.max_tokens_configList. Optional. Named list with
utterance,moderator,desire,desire_retry,summary,interim_summary, andprobeto override the default max token limits (160, 400, 16, 256, 400, 250, and the configuration's own limit or 300) for these call types. A model that reasons before answering needsdesireanddesire_retrylarge enough to hold its reasoning and the score.max_participant_responsesInteger. Optional. Maximum participant exchanges per round before moderator intervention. Defaults to
getOption("focusgroup.max_participant_responses", 2L). It must be one positive whole number.reasoning_effortOptional reasoning-effort setting, as described for the
reasoning_effortfield.handlesThe labels under which the agents see each other.
NULL(the default) shows the participants as"P1","P2", ... in roster order and the moderator as"Moderator", so the IDs given to the agents never reach a prompt; a participant whose ID is already"P1"to"P99"keeps it, and the others take the numbers that are no agent's ID, the moderator's included, so that a neutral handle never names a different agent's ID."ids"shows the agent IDs, with a message listing them. A character vector named by agent ID sets handles of your own, such as invented first names, and the agents it does not name keep their neutral handles. A handle must be non-empty and distinct, cannot be"System"or the name of an injected voice, and cannot contain a standalone number from 0 to 10, which a desire score could be mistaken for. No participant's ID may be the moderator's handle (in any case), which would make the two indistinguishable.call_on_silentWhere the moderator calls on the participants who have not spoken since the previous such point: any of
"opening"(the first discussion item),"midpoint"(discussion itemceiling(D / 2)ofD), and"end"(the last discussion item), or guide item numbers, which count every item, asfg_after_item()does, and must be items participants answer.TRUEgives the three named points;"none",FALSE, orNULLturns call-ons off. Points falling on the same item count once.call_on_textThe call-on line, delivered as written with no model call. It must contain
{participant}, which is replaced by the participant's handle.NULLuses "{participant}, I'd like to hear from you as well. What are your thoughts?".
Method run_simulation()
Run the full focus group simulation.
Iterates through the question_script phases or a specified number of rounds.
Usage
FocusGroup$run_simulation(
num_rounds = NULL,
verbose = FALSE,
injections = NULL,
probes = NULL,
final_summary = TRUE
)Arguments
num_roundsInteger. Optional. Maximum number of moderator cycles to run. If
NULL(default), the simulation runs until thequestion_scriptis exhausted or the moderator decides to end. If bothnum_roundsandquestion_scriptare provided, the simulation stops at whichever condition is met first.verboseLogical. If
TRUE, prints progress and utterances to the console.injectionsOptional
fg_injection()or list of injections to place into the session at their points.probesOptional
fg_probe()or list of probes to ask at their points; the answers go torecords$probes, never to the log.final_summaryLogical.
TRUE(the default) ends the session with a model-written summary infinal_summary;FALSEmakes no summary call and leavesfinal_summaryNULL.
Returns
Invisibly returns the conversation_log. When a call fails,
the error propagates and the messages completed so far remain in
conversation_log. A failed final summary is not an error: it warns
and leaves final_summary as NA. Every model call the session
makes, and every speaker selection, is recorded in records. An
injection or probe whose point the session never reaches is reported
with a warning.
Method run_arms()
Run the session up to a branch point once, then continue
a copy of it once per arm. run_focus_group_arms() describes the
design and builds results from the objects returned here.
Usage
FocusGroup$run_arms(
arms,
probes = NULL,
num_rounds = NULL,
verbose = FALSE,
final_summary = TRUE
)Arguments
armsA named list of arms, each
NULL, anfg_injection(), or a list of injections; the first injection of every arm that has one must name the same point.probesOptional
fg_probe()or list of probes.num_roundsInteger. Optional maximum number of moderator cycles.
verboseLogical. Print progress.
final_summaryLogical. Whether each arm ends with a final summary, as in
run_simulation().
Returns
A list with prefix, this object, stopped at the branch point,
and arms, a named list of FocusGroup objects, each a copy of the
prefix continued to the end of the session under one arm. A failure
in an arm raises an error of class focusgroup_branch_failure whose
fields arm, branch (the failed arm's object), completed,
phase, and parent describe it. A session that ends before the
branch point raises an error of class
focusgroup_injection_unreached.
Method summarize()
Generate a summary of the conversation using an LLM.
Usage
FocusGroup$summarize(
config,
summary_level = 1,
max_tokens = NULL,
internal_call = FALSE,
transcript_override = NULL,
.runner = NULL
)Arguments
configAn explicit
llm_configobject for the summarization model.summary_levelInteger (1-3). 1: Prose overview, 2: Detailed bulleted, 3: Short bulleted takeaways.
max_tokensInteger. Optional. Max tokens for the summary.
internal_callLogical. Marks a summary made for context management during the session. It does not change the call: the call's usage is added to the group's token fields either way, and to no agent's. A session's call record lists such a summary as
"interim_summary".transcript_overrideCharacter. Optional. If provided, this transcript is summarized instead of
self$conversation_log..runnerOptional function used instead of live model calls. It receives a data frame with
configandmessageslist-columns and returns the rows with at leastresponse_text.NULLmakes a live call throughconfig, also on a session that ran with a runner.
Returns
Character string containing the generated summary. The
transcript made from the log shows each speaker under its handle,
with the agent ID beside it when the two differ, and leaves out the
roster row; such a summary is for the researcher. The interim summary
the agents read is made from a transcript_override that shows
handles only. A log or transcript with nothing to summarize is an
error, as is an empty reply; its usage is recorded first.
Method analyze()
Basic analysis of the conversation log.
Method analyze_topics()
Perform LDA topic modeling on the conversation.
Usage
FocusGroup$analyze_topics(
num_topics = 5,
min_doc_length = 20,
top_n_terms = 10,
message_ids = NULL,
speaker_ids = NULL,
seed = 110,
unit = c("message", "speaker"),
...
)Arguments
num_topicsInteger. Number of topics to identify.
min_doc_lengthInteger. Minimum words for a text to count as a document.
top_n_termsInteger. Number of top terms per topic to return.
message_idsInteger vector. Optional
message_idvalues to analyze.speaker_idsCharacter vector. Optional. Specific speakers to analyze.
seedInteger or NULL. Seed for the LDA control (default 110);
NULLleaves it unset.unitCharacter. What counts as a document:
"message"(default) treats each utterance as a document;"speaker"pools every turn by the same speaker, which leaves as many documents as there are speakers and tends to recover who spoke rather than what was discussed....Additional arguments to
topicmodels::LDA().
Method analyze_tfidf()
Calculate TF-IDF scores for terms per participant.
Method analyze_readability()
Calculate readability scores for each participant's aggregated text.
Method analyze_themes()
Perform LLM-assisted thematic analysis on the transcript.
Arguments
configAn explicit
llm_configobject for thematic analysis.message_idsInteger vector. Optional
message_idvalues to analyze.speaker_idsCharacter vector. Optional. Specific speakers to analyze.
.runnerOptional function used instead of live model calls. It receives a data frame with
configandmessageslist-columns and returns the rows with at leastresponse_text.NULLmakes a live call throughconfig, also on a session that ran with a runner.
Method analyze_statistics()
Perform statistical analysis on conversation patterns.
Method analyze_participation_balance()
Analyze participation balance and dominance patterns.
Method analyze_response_patterns()
Analyze response patterns and interaction behaviors.
Method analyze_question_patterns()
Analyze question asking patterns during the conversation.
Method analyze_key_phrases()
Extract and analyze key phrases using n-grams.
Method plot_participation_timeline()
Create a participation timeline of cumulative messages by participant across phases.
Method plot_word_count_distribution()
Create word count distribution plot showing message length patterns.
Method plot_participation_by_agent()
Create a participation by agent plot showing total messages per participant.
Examples
cfg <- LLMR::llm_config("groq", "openai/gpt-oss-20b",
api_key = LLMR::llm_api_key_env("GROQ_API_KEY"))
agents <- create_agents(
n_participants = 2, config = cfg,
direct_persona_descriptions = c("A retired teacher.",
"A night-shift nurse."))
flow <- create_conversation_flow("round_robin", agents, "MOD")
fg <- FocusGroup$new(topic = "Neighborhood safety",
purpose = "Pilot the moderator guide.",
agents = agents,
moderator_id = "MOD",
turn_taking_flow = flow)
#> No question_script provided. Using a minimal default script (opening, generic discussion, closing).
fg$topic
#> [1] "Neighborhood safety"
