Personas¶
Personas are how you control who is in the room. Both agents are conditioned on a persona object that shapes how they speak, what they remember, and how they reason. Because personas are data rather than code, you can build a pool once and slice it into experimental conditions.
Patient personas¶
PatientPersona is a Pydantic model with fourteen required string fields and
one optional free-text field.
| Field | Meaning |
|---|---|
age_group | Broad age band, e.g. child, adult, elderly. |
gender | Patient gender. |
ethnicity | Cultural and linguistic background. |
socioeconomic_status | e.g. low, middle, high. |
language_proficiency | Fluency in the language of the consultation. |
recall_accuracy | How reliably the patient remembers onset, timing and history. |
cognitive_state | e.g. clear, confused, drowsy. |
trust_in_healthcare | Willingness to disclose fully to a clinician. |
pain_expression | How strongly pain is verbalised — stoic through dramatic. |
reactivity_to_clinician_emotion | How much the nurse's tone shifts the patient's behaviour. |
emotion_regulation | e.g. stable, labile. |
disfluency_rate | Frequency of hesitations, restarts and fillers. |
topic_drift | Tendency to wander off the clinical question. |
verbosity | Typical response length. |
instruction | Optional. Free-text instruction describing how the patient should speak. |
verbosity, not response_length
Some older examples show a response_length field for patients. The schema
field is verbosity. A file using response_length will not populate the
field you expect.
- age_group: elderly
gender: male
ethnicity: Vietnamese-Australian
socioeconomic_status: low
language_proficiency: limited
recall_accuracy: low
cognitive_state: mildly confused
trust_in_healthcare: low
pain_expression: stoic
reactivity_to_clinician_emotion: high
emotion_regulation: stable
disfluency_rate: high
topic_drift: high
verbosity: short
instruction: >-
Speaks in short sentences with frequent pauses. Occasionally substitutes a
Vietnamese word when the English one does not come to mind. Downplays pain.
- age_group: adult
gender: female
ethnicity: Australian
socioeconomic_status: middle
language_proficiency: high
recall_accuracy: high
cognitive_state: clear
trust_in_healthcare: high
pain_expression: moderate
reactivity_to_clinician_emotion: low
emotion_regulation: stable
disfluency_rate: low
topic_drift: low
verbosity: medium
Nurse personas¶
NursePersona has eight required string fields plus the same optional
instruction.
| Field | Meaning |
|---|---|
gender | Nurse gender. |
ethnicity | Cultural and linguistic background. |
experience_level | e.g. graduate, mid-level, senior. |
risk_tolerance | Willingness to accept diagnostic uncertainty before deciding. |
guideline_adherence | How strictly the triage protocol is followed. |
communication_style | e.g. direct, warm, clipped. |
verbosity | Typical utterance length. |
emotional_expression | e.g. neutral, reassuring, brusque. |
instruction | Optional. Free-text instruction describing how the nurse should speak. |
- gender: female
ethnicity: Australian
experience_level: senior
risk_tolerance: low
guideline_adherence: high
communication_style: direct
verbosity: medium
emotional_expression: neutral
- gender: male
ethnicity: Indian-Australian
experience_level: graduate
risk_tolerance: high
guideline_adherence: medium
communication_style: warm
verbosity: high
emotional_expression: reassuring
instruction: >-
Checks understanding often and apologises for repeating questions.
Fields are free-form strings
None of these fields is an enum. experience_level: "12 years in a rural ED"
is as valid as "senior" — it is interpolated into the prompt as written.
That flexibility is deliberate, but it also means typos pass validation
silently.
Loading¶
from triagesim.personas import load_patient_personas, load_nurse_personas
patients = load_patient_personas("patient.yaml")
nurses = load_nurse_personas("nurse.yaml")
Each function reads a YAML file containing a list of mappings and returns a list of validated persona objects.
Missing required fields do not raise. The loader fills any absent required field with a default before validation, so a partial persona still constructs — with the unspecified traits taking a neutral value rather than the one you meant.
Note
Because absent fields are silently defaulted, a misspelled key (say
verbosty) will not error. It will be dropped and the real verbosity
field will be defaulted. Validate your persona files if they are
machine-generated.
Sampling¶
from triagesim.personas import sample_patient_personas, sample_nurse_personas
chosen = sample_patient_personas(patients, k=4, seed=42)
Sampling is without replacement and uses a local random.Random(seed)
instance, so it does not disturb the global RNG. The same seed and the same
input list always yield the same selection.
Requesting more personas than exist raises ValueError:
sample_patient_personas(patients, k=100)
# ValueError: Cannot sample k=100 personas from population of size 2
Filtering¶
filter_patient_personas and filter_nurse_personas take a criteria dict and
keep only personas where every field matches exactly:
from triagesim.personas import filter_patient_personas
low_proficiency = filter_patient_personas(
patients,
{"language_proficiency": "limited", "trust_in_healthcare": "low"},
)
Matching is exact string equality — "Limited" will not match "limited".
Passing a field name that does not exist on the model raises AttributeError,
which is a useful early failure when building experimental conditions.
A worked example¶
Filtering and sampling compose naturally into experimental arms:
from triagesim.personas import (
load_patient_personas,
filter_patient_personas,
sample_patient_personas,
)
patients = load_patient_personas("patients.yaml")
conditions = {
"high_proficiency": {"language_proficiency": "high"},
"limited_proficiency": {"language_proficiency": "limited"},
}
arms = {}
for name, criteria in conditions.items():
pool = filter_patient_personas(patients, criteria)
if len(pool) < 10:
raise ValueError(f"Condition {name!r} has only {len(pool)} personas")
arms[name] = sample_patient_personas(pool, k=10, seed=42)
Holding the seed fixed across arms keeps sampling noise from confounding the comparison, so any difference in outcomes is attributable to the condition rather than to which personas happened to be drawn.
Generating persona pools¶
Writing hundreds of personas by hand is impractical. The
repository includes
scripts/generate_personas.py, which produces persona YAML files
programmatically — a useful starting point for building a pool large enough to
filter meaningfully.
Next: Agents and LLM backends covers how a persona is turned into a prompt.