agent_urban_planning.AhlfeldtHierarchicalLLMEngine

class AhlfeldtHierarchicalLLMEngine(params, llm_client, *, cluster_k=50, clustering_algo='kmeans', zone_name_map=None, cache_dir='.cache/llm_v5_hierarchical', softmax_T=1.0, max_retries=3, prompt_version='v5-hierarchical-v2', llm_concurrency=15, progress_callback=None, seed=None, prompt_builder_stage1=None, response_validator_stage1=None, stage2_top_k_residences=None, **parent_kwargs)[source]

Bases: AhlfeldtABMEngine

LLM-as-decision-maker engine with clustering and two-stage prompts.

The full V5 / V5 engine. Replaces V[i, j] entirely with an LLM call made per cluster per market iteration. Stage 1 selects a residence; stage 2 selects a workplace conditional on residence. Returned scores become sampling probabilities (via _scores_to_probs) and are sampled M = num_agents times to build the empirical last_choice_probabilities matrix consumed by AhlfeldtMarket.

Cache namespace: .cache/llm_v5_hierarchical/. Most users should configure this through LLMDecisionEngine.

Parameters:
  • params (AhlfeldtParams) – Structural Ahlfeldt parameters.

  • llm_client – An LLM client object (.complete(user, system="") returning a string).

  • cluster_k (int) – Number of clusters used in stage-1 prompt grouping.

  • clustering_algo (str) – Clustering algorithm; only "kmeans" is currently supported.

  • zone_name_map (Optional[dict[str, str]]) – Optional synthetic_id -> real_name mapping for prompt-side zone naming.

  • cache_dir (str | Path) – Directory where stage-1 / stage-2 LLM call results are persisted.

  • softmax_T (float) – Temperature applied to LLM-returned scores.

  • max_retries (int) – Retry budget on parse errors per LLM call.

  • prompt_version (str) – String identifier baked into cache keys.

  • llm_concurrency (int) – Max parallel LLM calls.

  • progress_callback (Optional[Callable[[str, int, int, int], None]]) – Optional (stage, done, total, retries) callback for progress reporting.

  • seed (Optional[int]) – Optional integer seed.

  • prompt_builder_stage1 (Optional[Callable[..., tuple[str, str]]]) – Optional override for the stage-1 prompt builder.

  • response_validator_stage1 (Optional[Callable[..., list[tuple[str, float]]]]) – Optional override for the stage-1 response validator.

  • stage2_top_k_residences (Optional[int]) – Optional cap on stage-2 fan-out (used in V5 score-all mode).

  • **parent_kwargs – Forwarded to AhlfeldtABMEngine.

Examples

>>> import agent_urban_planning as aup
>>> # Prefer the public wrapper:
>>> # engine = aup.LLMDecisionEngine(params, llm_client=client,
>>> #                                response_format="score_all")
ensure_clustering(agents)[source]

Build (or reuse) the cluster assignments for the given agent set.

Idempotent: re-calling with the same agent set is a no-op. On first call the agents are one-hot encoded and clustered with the configured algorithm (currently only kmeans); self._cluster_labels, self._cluster_personas, and self._cluster_weights are populated.

Parameters:

agents (list[Agent]) – List of Agent instances.

Return type:

None

Returns:

None.

Raises:
  • ValueError – If self.clustering_algo is not supported.

  • RuntimeError – If the cluster weights sum to zero.

Examples

>>> import agent_urban_planning as aup
>>> # engine = aup.LLMDecisionEngine(params, llm_client=client)
>>> # engine.ensure_clustering(list(population))
decide_batch(agents, environment, zone_options, prices)[source]

Issue stage-1 + stage-2 LLM calls and sample per-agent (R, W) choices.

For each cluster, issues one stage-1 LLM call (residence ranking / scoring) plus up to stage2_top_k_residences stage-2 calls (workplace conditional on residence). Cached results are reused across iterations via the in-memory and on-disk caches keyed by (cluster, stage, residence, prompt_version, price_bucket, wage_bucket). Returned scores are converted to sampling probabilities and sampled num_agents times to populate self.last_choice_probabilities.

Parameters:
  • agents (list[Agent]) – List of Agent instances.

  • environment – The Environment carrying zones.

  • zone_options (list[str]) – Allowed zone names.

  • prices (dict) – Mapping zone -> Q_i.

Return type:

list[LocationChoice]

Returns:

List of LocationChoice, one per input agent and in the same order.

Examples

>>> import agent_urban_planning as aup
>>> # engine = aup.LLMDecisionEngine(params, llm_client=client)
>>> # choices = engine.decide_batch(agents, env, zones, prices)
cluster_personas()[source]

Return a copy of the per-cluster persona strings.

Each persona is a one-line persona_summary() of the highest-weight agent in the cluster, used as the persona block in stage-1 prompts. Useful for diagnostic logging.

Return type:

list[str]

Returns:

List of persona strings, one per cluster.

Examples

>>> import agent_urban_planning as aup
>>> # engine.cluster_personas()  # one persona per cluster