Skip to contents

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

topic

Character. The main topic of the focus group discussion.

purpose

Character. The primary purpose or research objective of conducting the focus group.

agents

Named list. A list of FGAgent objects participating in the simulation, indexed by their IDs.

moderator_id

Character. The ID of the agent designated as the moderator.

conversation_log

List. 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, and metadata, along with call metadata: response_id, finish_reason, sent_tokens, rec_tokens, total_tokens, duration_s, provider, and model. message_id is unique message order; moderator and participant messages from one moderator cycle share round, and item is the guide item the message belongs to (0 for the roster row). speaker_id is the agent ID and handle the label the agents saw.

handles

Named character vector. The label under which each agent appears to the other agents, named by agent ID; see the handles argument of new().

call_on_silent

List. 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>") and items (the item numbers). Both are empty when call-ons are off.

call_on_text

Character. The moderator's call-on line; {participant} is replaced by the participant's handle.

turn_taking_flow

A ConversationFlow object dictating participant turn-taking.

prompt_templates

List. Holds prompt templates for agent/moderator actions.

question_script

List. 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 optionally text (for specific questions).

current_question_text

Character. Text of the current question being discussed.

current_conversation_summary

Character. An LLM-generated summary of earlier parts of the conversation, used for managing context length in prompts.

final_summary

Character. Final LLM-generated summary from the most recent simulation run.

message_mode

Character. 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_config

An llm_config object for group-level model tasks.

max_tokens_utterance

Integer. Default max tokens for participant utterances.

max_tokens_moderator

Integer. Default max tokens for moderator utterances.

max_tokens_desire

Integer. Default max tokens for desire-to-talk queries.

max_tokens_desire_retry

Integer. Max tokens for the one retry of a desire query whose reply was empty or cut off.

max_tokens_summary

Integer. Max tokens for the final summary.

max_tokens_interim_summary

Integer. Max tokens for an interim summary.

max_tokens_probe

Integer or NULL. Max tokens for a probe answer; NULL uses the agent configuration's limit, or 300 when it sets none.

reasoning_effort

NULL, 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's reasoning_effort for those calls; NULL leaves the configurations as they are.

max_participant_responses

Integer. 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_sent

Numeric. Tokens sent across all LLM calls in the group, as reported; calls that reported no count add nothing.

total_tokens_received

Numeric. Tokens received across all LLM calls, as reported.

total_tokens

Numeric. Total tokens across all LLM calls: each call's reported total, or its sent and received counts when it reported no total.

calls_usage_unknown

Numeric. Number of calls whose usage was incomplete, so that the three token fields are lower bounds when it is positive; NA when that number cannot be determined, as after a call that raised an error.

records

List. The record of the most recent session: calls, one entry per model call the session made; routing, one entry per speaker selection; probes and injections, described in run_focus_group(); and info, 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

topic

Character. The main discussion topic.

purpose

Character. The primary purpose of the focus group.

agents

Named list of initialized FGAgent objects, each named by its id.

moderator_id

Character. The ID of the agent acting as moderator.

turn_taking_flow

An initialized ConversationFlow object.

question_script

List. Moderator's script defining phases and questions. If empty, a minimal default script (opening, generic discussion, closing) is used. An item with verbatim = TRUE is delivered as its text reads, with no model call; it must have a text.

prompt_templates

List. Custom prompt templates. Defaults are used if not provided.

admin_config

An llm_config object for administrative model tasks. If NULL, the moderator's explicitly supplied config is used.

max_tokens_config

List. Optional. Named list with utterance, moderator, desire, desire_retry, summary, interim_summary, and probe to 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 needs desire and desire_retry large enough to hold its reasoning and the score.

max_participant_responses

Integer. 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_effort

Optional reasoning-effort setting, as described for the reasoning_effort field.

handles

The 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_silent

Where the moderator calls on the participants who have not spoken since the previous such point: any of "opening" (the first discussion item), "midpoint" (discussion item ceiling(D / 2) of D), and "end" (the last discussion item), or guide item numbers, which count every item, as fg_after_item() does, and must be items participants answer. TRUE gives the three named points; "none", FALSE, or NULL turns call-ons off. Points falling on the same item count once.

call_on_text

The call-on line, delivered as written with no model call. It must contain {participant}, which is replaced by the participant's handle. NULL uses "{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_rounds

Integer. Optional. Maximum number of moderator cycles to run. If NULL (default), the simulation runs until the question_script is exhausted or the moderator decides to end. If both num_rounds and question_script are provided, the simulation stops at whichever condition is met first.

verbose

Logical. If TRUE, prints progress and utterances to the console.

injections

Optional fg_injection() or list of injections to place into the session at their points.

probes

Optional fg_probe() or list of probes to ask at their points; the answers go to records$probes, never to the log.

final_summary

Logical. TRUE (the default) ends the session with a model-written summary in final_summary; FALSE makes no summary call and leaves final_summary NULL.

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

arms

A named list of arms, each NULL, an fg_injection(), or a list of injections; the first injection of every arm that has one must name the same point.

probes

Optional fg_probe() or list of probes.

num_rounds

Integer. Optional maximum number of moderator cycles.

verbose

Logical. Print progress.

final_summary

Logical. 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

config

An explicit llm_config object for the summarization model.

summary_level

Integer (1-3). 1: Prose overview, 2: Detailed bulleted, 3: Short bulleted takeaways.

max_tokens

Integer. Optional. Max tokens for the summary.

internal_call

Logical. 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_override

Character. Optional. If provided, this transcript is summarized instead of self$conversation_log.

.runner

Optional function used instead of live model calls. It receives a data frame with config and messages list-columns and returns the rows with at least response_text. NULL makes a live call through config, 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.

Usage

FocusGroup$analyze(message_ids = NULL, speaker_ids = NULL)

Arguments

message_ids

Integer vector. Optional message_id values to analyze. If NULL, all messages are analyzed.

speaker_ids

Character vector. Optional. Specific speakers to analyze. If NULL, analyzes all speakers.

Returns

A list with speaker_stats (a tibble: speaker_id, utterance_count, total_words, avg_words_per_utterance) and full_transcript (character string).


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_topics

Integer. Number of topics to identify.

min_doc_length

Integer. Minimum words for a text to count as a document.

top_n_terms

Integer. Number of top terms per topic to return.

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

seed

Integer or NULL. Seed for the LDA control (default 110); NULL leaves it unset.

unit

Character. 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().

Returns

A list with LDA model, topic terms, and document-topic proportions. NULL on failure.


Method analyze_tfidf()

Calculate TF-IDF scores for terms per participant.

Usage

FocusGroup$analyze_tfidf(
  top_n_terms = 10,
  message_ids = NULL,
  speaker_ids = NULL,
  ...
)

Arguments

top_n_terms

Integer. Number of top TF-IDF terms per participant.

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

...

Additional arguments to tidytext::unnest_tokens.

Returns

A tibble with TF-IDF scores.


Method analyze_readability()

Calculate readability scores for each participant's aggregated text.

Usage

FocusGroup$analyze_readability(
  measures = "Flesch",
  message_ids = NULL,
  speaker_ids = NULL
)

Arguments

measures

Character vector. Readability measure(s) from quanteda.textstats::textstat_readability.

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

Returns

A tibble with readability scores.


Method analyze_themes()

Perform LLM-assisted thematic analysis on the transcript.

Usage

FocusGroup$analyze_themes(
  config,
  message_ids = NULL,
  speaker_ids = NULL,
  .runner = NULL
)

Arguments

config

An explicit llm_config object for thematic analysis.

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

.runner

Optional function used instead of live model calls. It receives a data frame with config and messages list-columns and returns the rows with at least response_text. NULL makes a live call through config, also on a session that ran with a runner.

Returns

The thematic summary as a character string. An empty transcript returns character(0). Provider failures are propagated.


Method analyze_statistics()

Perform statistical analysis on conversation patterns.

Usage

FocusGroup$analyze_statistics(message_ids = NULL, speaker_ids = NULL)

Arguments

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

Returns

A list with ANOVA results, phase participation stats, and correlations.


Method analyze_participation_balance()

Analyze participation balance and dominance patterns.

Usage

FocusGroup$analyze_participation_balance(
  message_ids = NULL,
  speaker_ids = NULL
)

Arguments

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

Returns

A list with participation statistics and balance metrics. Every agent in the roster has a row, an agent who did not speak in the analyzed messages with zero messages and words and an NA average. The called_on column counts the turns that answered the moderator's call-on line.


Method analyze_response_patterns()

Analyze response patterns and interaction behaviors.

Usage

FocusGroup$analyze_response_patterns(message_ids = NULL, speaker_ids = NULL)

Arguments

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

Returns

A list with response and interaction pattern metrics.


Method analyze_question_patterns()

Analyze question asking patterns during the conversation.

Usage

FocusGroup$analyze_question_patterns(message_ids = NULL, speaker_ids = NULL)

Arguments

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

Returns

A list with question pattern analysis.


Method analyze_key_phrases()

Extract and analyze key phrases using n-grams.

Usage

FocusGroup$analyze_key_phrases(
  min_freq = 2,
  message_ids = NULL,
  speaker_ids = NULL
)

Arguments

min_freq

Integer. Minimum frequency for phrases to be considered key.

message_ids

Integer vector. Optional message_id values to analyze.

speaker_ids

Character vector. Optional. Specific speakers to analyze.

Returns

A list with bigram and trigram analysis.


Method plot_participation_timeline()

Create a participation timeline of cumulative messages by participant across phases.

Usage

FocusGroup$plot_participation_timeline()

Returns

ggplot object


Method plot_word_count_distribution()

Create word count distribution plot showing message length patterns.

Usage

FocusGroup$plot_word_count_distribution()

Returns

ggplot object


Method plot_participation_by_agent()

Create a participation by agent plot showing total messages per participant.

Usage

FocusGroup$plot_participation_by_agent()

Returns

ggplot object


Method plot_message_length_timeline()

Create a timeline showing message length over message order.

Usage

FocusGroup$plot_message_length_timeline()

Returns

ggplot object


Method clone()

The objects of this class are cloneable with this method.

Usage

FocusGroup$clone(deep = FALSE)

Arguments

deep

Whether to make a deep clone.

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"