--- title: "FocusGroup Architecture" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{FocusGroup Architecture} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include=FALSE} knitr::opts_chunk$set(eval = FALSE) ``` # Architecture FocusGroup supports the design and examination of moderated discussions before fieldwork. Researchers can compare moderator guides, participant sources, and speaker-selection rules while retaining one transcript format for analysis. A generated transcript describes the specified session and does not estimate population attitudes. Most studies can begin with `run_focus_group()`. This article explains the underlying objects for researchers who need direct construction, offline execution, or custom turn-taking rules. `FGAgent` represents a participant or moderator and generates that agent's turns. Its model configuration is stored in the `config` field. `ConversationFlow` is the base class for custom turn-taking rules. `FocusGroup` advances the moderator guide and records each message. LLMR supplies model configurations and provider calls. ## Main functions `run_focus_group()` is the single high-level session runner. It requires an explicit `config`, constructs the roster and flow, performs a call-count preflight, runs the guide, and returns a `focus_group_result`. Its main arguments are consistent with the other package entry points: - `n_participants` is the participant count, excluding the moderator. - `guide` is a named vector or list of phase counts or moderator scripts. - `flow` selects the speaker-selection rule. - `message_mode` selects role-flipped or flat message construction. - `config` is the model configuration, and `admin_config` is the group-level configuration on direct `FocusGroup` construction. - `.runner`, when supplied, is an optional function used instead of live model calls. The package supplies three built-in flow modes: `"round_robin"`, `"probabilistic"`, and `"desire_based"`. Construct them with `create_conversation_flow()`. Their implementation classes are internal. `ConversationFlow` remains public so packages and researchers can define new selection rules. `create_agents()`, `create_agents_from_data()`, and `create_agents_from_survey()` return agent-ID-keyed lists that include the moderator `MOD`. Each requires an explicit `config` because the returned agents can generate model output. `focus_group_from_transcript()` creates a `FocusGroup` from an existing transcript without generating new turns. `moderator_id` identifies the moderator when known. If it is omitted, the importer falls back to finding `"mod"` within a speaker identifier. ## Offline execution and replay The `.runner` argument accepts an optional function for offline tests or replay that uses the request and response data-frame format used across LLMR. The package calls `.runner(experiments, ...)`, where `experiments` is a data frame with `config` and `messages` list-columns. The function returns those rows with at least `response_text`; it may also return `response_id`, token counts, `success`, and related diagnostics. The function handles moderator, participant, desire-scoring, and summary requests. This format permits an archive replay function to execute a full session without a live provider. A configuration remains explicit and is recorded with the experiment so readers can see how it was run. ## Running a session `FocusGroup$run_simulation()` first records the participant roster as a `System` message. It then reads `question_script` in order. For each entry, the moderator generates one message. Question phases call `select_next_speaker()` and collect no more than `max_participant_responses` participant messages. Opening and closing phases do not collect participant responses. The round-robin mode cycles through participants. The probabilistic mode samples from speaking propensities. The desire-based mode asks eligible participants for model-produced scores and selects among eligible candidates. Provider failures in desire scoring are recorded in the message metadata and trigger one message to the user. Selection then uses a neutral draw among the candidates; a failure is never recorded as a score of zero. The loop stops when the script is exhausted, `num_rounds` is reached, or the last closing entry is processed. It stores a final summary in `final_summary`. The summary is not appended to `conversation_log`. A `FocusGroup` object runs once. Calling `run_simulation()` on an object that has already run raises an error. Construct a new object for another full session. Use the continuation experiment in `run_focus_studio()` to compare a next message under original and edited histories. ## Messages and prompts The default `message_mode = "roleflip"` presents an agent's prior messages as assistant messages and labels other speakers' messages as user messages. `message_mode = "flat"` places the transcript in one user message. The selected mode is recorded with the session. `get_default_prompt_templates()` returns only templates supported by package operations. Pass named replacements through `prompt_templates` in `FocusGroup$new()`. A custom participant template containing `{{conversation_history}}` or `{{persona_description}}` uses the flat message path. ## Stored data `conversation_log` is an ordered list of message records. Each record has a unique integer `message_id`, the moderator cycle in `round`, and `phase`. Moderator and participant messages responding to the same question share a round. Response identifiers, finish reasons, token counts, provider and model labels, duration, and failure metadata accompany generated messages. `run_focus_group()` exposes this log as the `transcript` data frame in its result. A `focus_group_result` always has these fields: - `focus_group`: the underlying R6 object. - `transcript`: one row per logged message. - `summary`: the final model summary. - `participants`: a data frame describing the roster. - `usage`: token use in LLMR's `sent`, `rec`, and `total` vocabulary. - `metadata`: `topic`, `purpose`, `flow`, `message_mode`, `n_participants`, `estimated_calls`, `provider`, and `model`, with no credentials. The class has a concise print method. `FocusGroup` also has a concise S3 print method for direct R6 objects. ## Analysis `FocusGroup$analyze()` and the descriptive text methods operate on the stored log. `analyze_focus_group()` collects the principal analyses in a `focus_group_analysis` with these returned components: `basic_stats`, `topics`, `tfidf`, `readability`, `themes`, `model_summary`, `plots`, and `issues`. Missing optional packages and unsuitable data produce empty components with consistent columns where the result is tabular, and an explanatory row in `issues`. `plots` is an empty list when ggplot2 is unavailable. Descriptive analyses run offline. Thematic analysis and model summaries run only when `analyze_focus_group()` receives an explicit `config`. A requested model analysis that encounters a provider failure raises the provider condition. ## Extending the package To add a turn-taking rule, subclass `ConversationFlow` and implement `select_next_speaker()`. Override `update_state_post_selection()` when the rule keeps state. An extension can construct its class directly; built-in modes remain available through `create_conversation_flow()`. See the function and class help pages for argument defaults and return values.