agent_urban_planning.LLMDecisionEngine

class LLMDecisionEngine(params, llm_client, *, response_format='score_all', rebalance_instruction=False, stage2_top_k_residences=None, prompt_builder=None, response_validator=None, **kwargs)[source]

Bases: object

Full LLM-as-decision-maker hierarchical engine (V5).

The LLM is queried per agent cluster per market iteration to make discrete location decisions directly: stage 1 selects a residence, stage 2 selects a workplace conditional on residence. This is the paper’s headline contribution and the aup library’s core extensibility point.

Configure via constructor kwargs to reproduce V5 from the paper: response_format="score_all" + rebalance_instruction=True + stage2_top_k_residences=10 keeps stage-2 fan-out tractable when the LLM scores all 96 zones in stage 1.

Parameters:
  • params (Any) – AhlfeldtParams instance carrying structural elasticities.

  • llm_client (Any) – An agent_urban_planning.llm.LLMClient instance (or any object with a .complete(user, system="") method).

  • response_format (Literal['top5', 'score_all']) – "score_all" (paper headline) asks the LLM to score every zone; "top5" is a legacy ablation where the LLM ranks only its top 5. Default "score_all".

  • rebalance_instruction (bool) – If True, the stage-1 prompt includes an explicit “weight affordability ≥ amenity” instruction. Default False (the V5 paper run sets this to True).

  • stage2_top_k_residences (Optional[int]) – When set to an int, stage-2 fan-out is capped at the top-K residences per cluster (by stage-1 score), preventing the cost blowup from score-all stage-1 producing 96 residences per cluster. None disables the cap. Default None; the V5 paper run uses 10.

  • prompt_builder (Optional[Callable]) – Optional override for the stage-1 prompt builder callable. When provided, takes precedence over the response_format selection.

  • response_validator (Optional[Callable]) – Optional override for the response validator callable. When provided, takes precedence over the format selection.

  • **kwargs (Any) – Forwarded to AhlfeldtHierarchicalLLMEngine. Common kwargs: cluster_k (default 50), clustering_algo, zone_name_map, cache_dir, softmax_T, num_agents, batch_size, seed, llm_concurrency, progress_callback, max_retries.

Raises:

ValueError – If response_format is not one of {"top5",     "score_all"}, or if llm_client is None.

Examples

V5 reproduction (paper’s headline LLM-ABM):

>>> import agent_urban_planning as aup
>>> engine = aup.LLMDecisionEngine(
...     params=scenario.ahlfeldt_params,
...     llm_client=aup.llm.CodexCliClient(),
...     response_format="score_all",
...     rebalance_instruction=True,
...     stage2_top_k_residences=10,
...     cluster_k=50,
...     num_agents=1_000_000,
...     seed=42,
... )

Custom prompt builder (research extensibility):

>>> def my_prompt(persona, zones_info, *, prompt_version):
...     return ("system", f"persona: {persona}; rank top 3.")
>>> engine = aup.LLMDecisionEngine(
...     params=scenario.ahlfeldt_params,
...     llm_client=aup.llm.CodexCliClient(),
...     prompt_builder=my_prompt,
...     response_validator=my_validator,
... )

Notes

The V5 score-all configuration produces 96-zone stage-1 distributions, which would cause stage-2 fan-out to explode (50 clusters × 96 residences = 4800 stage-2 LLM calls per market iteration). The stage2_top_k_residences kwarg caps this at top-K residences (default for V5 production: 10); residences outside the top-K fall back to the sampler’s uniform-workplace default in AhlfeldtHierarchicalLLMEngine.

See also

agent_urban_planning.UtilityEngine — closed-form V1/V2/V3 baselines. agent_urban_planning.HybridDecisionEngine — V4 (LLM elicits preferences, closed-form choice).

References

Ahlfeldt, G. M., Redding, S. J., Sturm, D. M., Wolf, N. (2015). The economics of density: Evidence from the Berlin Wall. Econometrica, 83(6), 2127-2189.

property response_format: str

"top5" or "score_all".

Type:

The configured response format

property rebalance_instruction: bool

Whether the stage-1 prompt includes the affordability rebalance instruction.

decide_batch(*args, **kwargs)[source]

Forward to the underlying implementation’s decide_batch.

Transparently forwards to AhlfeldtHierarchicalLLMEngine.decide_batch(). Each call issues one stage-1 LLM call per agent cluster (residence) plus up to stage2_top_k_residences stage-2 calls per cluster (workplace conditional on residence), with caching on price buckets to amortize costs across market iterations.

Parameters:
  • *args (Any) – Positional arguments forwarded unchanged.

  • **kwargs (Any) – Keyword arguments forwarded unchanged.

Return type:

Any

Returns:

List of agent_urban_planning.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)