Configuration

API key

Both agents reach a model through OpenRouter, so TriageSim needs an API key before any simulation will run. It is read once, at import time, from the OPENROUTER_API_KEY environment variable.

export OPENROUTER_API_KEY=sk-or-...

Create a .env file at your project root:

.env
OPENROUTER_API_KEY=sk-or-...

TriageSim calls python-dotenv's find_dotenv(usecwd=True) on import, so the file is discovered by searching upward from the current working directory.

Do not commit your key

Add .env to your .gitignore. An OpenRouter key is a billing credential.

Because the key is resolved when triagesim.config is first imported, setting os.environ after importing the package has no effect. Set it before your first import, or use a .env file.

Choosing a model

Any OpenRouter model identifier works. The model is supplied per LLM backend, which means the nurse and the patient can run on different models:

from triagesim.agents import OpenRouterLLM
from triagesim.core import NurseOutput, PatientOutput

nurse_llm = OpenRouterLLM(model_name="anthropic/claude-sonnet-4-5",
                          output_type=NurseOutput)
patient_llm = OpenRouterLLM(model_name="google/gemini-3-pro-preview",
                            output_type=PatientOutput)

Each backend is bound to exactly one output schema, so a nurse backend cannot be handed to a patient agent. This is deliberate: it makes an invalid pairing a construction-time error rather than a parsing failure mid-run.

Structured output is enforced by pydantic-ai, so the model must support tool-calling or JSON mode. Models that only emit free text will fail validation.

Feature flags

Two settings live in triagesim.config:

NameDefaultEffect
OPENROUTER_API_KEYfrom environmentCredential used by OpenRouterLLM.
ENABLE_LLM_DETECTORSFalseEnables LLM fallback when the rule-based detectors cannot identify a requested vital or a belief update from the nurse's text.

By default TriageSim extracts requested vitals with keyword rules — "pulse" and "heart rate" both map to heartrate, "bp" to sbp, and so on. Turning on ENABLE_LLM_DETECTORS adds a model call whenever those rules find nothing, which is more robust but slower and more expensive.

A separate, per-run flag controls LLM-backed belief updates:

from triagesim import RunnerConfig

config = RunnerConfig(enable_llm=True)

Note

RunnerConfig.enable_llm defaults to False. The dialogue itself is always model-driven; this flag only governs whether the belief updater may call a model in addition to its deterministic rules.

Reproducibility

Pass a seed to RunnerConfig to seed Python's global RNG for the run, and to sample_patient_personas / sample_nurse_personas to fix persona selection:

config = RunnerConfig(max_turns=20, seed=42)

This makes persona sampling and any internal random choices deterministic. It does not make the language model deterministic — remote sampling is outside TriageSim's control, so repeated runs with the same seed will still differ in wording.