LLM Clients & Dry Runs¶
In SocioVerse the LLM sits in exactly one place: the decision model.
decide_batch(observations, memories) is where B = f(P, E) gets computed,
and whether that computation is a rule, a statistical model, or a batched LLM
call is the study's choice. Everything else — environment, population,
store, reports — runs identically either way.
Configuration¶
LLM-driven decision models read their settings from the environment
(via the gitignored .env):
| variable | meaning |
|---|---|
SV_LLM_API_KEY |
primary API key (OPENAI_API_KEY honored as fallback) |
SV_LLM_BASE_URL |
optional endpoint override — any OpenAI-compatible gateway |
SV_LLM_MODEL |
optional model selection |
Batching is the contract¶
decide_batch receives all observations for the step (or for the
cohort — Persona.group_key defines batching cohorts). A well-behaved
LLM decision model:
- renders each observation's 4-quadrant view into the prompt
(
Observation.renderedis the conventional slot for the prepared text); - conditions on the agent's
AgentMemory(a rolling window of its own past actions — "I already moved twice, I'll stay"); - issues batched calls, never one request per agent per step;
- returns typed
Actions thatapply()can fold back into E.
Deterministic dry runs¶
You should be able to prove the plumbing — loop, artifacts, store, metrics — without spending a token. Two equivalent patterns:
A rule-based decision model (the shipped from-scratch template works this way): the decision is a pure function of the observation, so runs are exactly reproducible from the seed.
A deterministic stand-in client: keep your LLM decision model, but inject
a client whose complete() is a fixed function of its input. A minimal
stub:
class DeterministicLLMClient:
"""Stands in for the real client: same input → same output, no network."""
def complete(self, prompt: str, **kwargs) -> str:
# pick a stable, boring policy — e.g. always the first listed option
return "stay"
Inject it wherever your decision model accepts its client
(decision_args in simulation/simulation.json is the conventional place
for such wiring). Bundled studies that wrap legacy engines ship their own
deterministic client for exactly this purpose — check the study's README.
Dry runs are how the repository's parity tests work: wrapped legacy studies assert they reproduce the original trajectory on the same seed, with the deterministic client removing the only source of nondeterminism.
Reproducibility, honestly stated¶
Live LLM runs are not reproducible
Real LLM calls sample at nonzero temperature: two live runs of the same study will differ. The reproducible objects are (a) dry runs — seeded and deterministic end to end, and (b) any recorded run — its panel store is permanent, and warm-started iterations replay stored actions exactly rather than re-asking the LLM.
Cost control¶
/sv-runshows the run configuration and asks for confirmation before spending — the spend gate.- Prefer dry runs while you iterate on artifacts; go live once per meaningful configuration.
- Warm starts inherit a parent version's
steps
0..Kby replay (no LLM), spending only on the new steps.