Spaces:
Sleeping
Sleeping
| """B6 - Cortex with a trained LLM brain-selector router. | |
| The Phase 3 router is trained to emit exactly one of: | |
| ``{"brain": "epi" | "logistics" | "governance"}``. | |
| That is a direct specialist-selection policy, not the older B3 | |
| metacognition router shape. This agent keeps the B3 public surface | |
| (``run_episode``) but swaps the deterministic all-brain Council pass for | |
| a learned brain selector followed by one frozen specialist Brain pass. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import re | |
| import sys | |
| import textwrap | |
| from typing import Any, Dict, List, Optional, Protocol | |
| from baselines.flat_agent import B1StepEvent, ErrorKind, StepCallback, parse_failure_marker | |
| from cortex.brains import EpiBrain, GovernanceBrain, LogisticsBrain | |
| from CrisisWorldCortex.models import ( | |
| CrisisworldcortexAction, | |
| CrisisworldcortexObservation, | |
| NoOp, | |
| ) | |
| class _EnvLike(Protocol): | |
| def reset(self) -> CrisisworldcortexObservation: ... | |
| def step(self, action: CrisisworldcortexAction) -> CrisisworldcortexObservation: ... | |
| _BRAIN_ALIASES = { | |
| "epi": "epidemiology", | |
| "epidemiology": "epidemiology", | |
| "logistics": "logistics", | |
| "governance": "governance", | |
| } | |
| _ROUTER_SYSTEM_PROMPT = textwrap.dedent( | |
| """ | |
| You are the Cortex brain selector. Read one CrisisWorld observation and | |
| choose exactly one specialist brain to act next. | |
| Brain options: | |
| - epi: epidemiology, surveillance, case growth, and outbreak spread | |
| - logistics: scarce resources, hospital beds, mobile units, allocation | |
| - governance: legal constraints, compliance, escalation, restrictions | |
| Respond with exactly one JSON object and no prose: | |
| {"brain": "epi" | "logistics" | "governance"} | |
| """ | |
| ).strip() | |
| def _extract_json(raw_text: str) -> Optional[Dict[str, Any]]: | |
| text = raw_text.strip() | |
| text = re.sub(r"```(?:json)?\s*", "", text) | |
| text = re.sub(r"```\s*$", "", text).strip() | |
| candidates = [text] | |
| start = text.find("{") | |
| if start >= 0: | |
| depth = 0 | |
| for index, char in enumerate(text[start:], start): | |
| if char == "{": | |
| depth += 1 | |
| elif char == "}": | |
| depth -= 1 | |
| if depth == 0: | |
| candidates.append(text[start : index + 1]) | |
| break | |
| for candidate_text in candidates: | |
| try: | |
| candidate = json.loads(candidate_text) | |
| except json.JSONDecodeError: | |
| continue | |
| if isinstance(candidate, dict): | |
| return candidate | |
| return None | |
| def parse_router_choice(raw_text: str) -> Optional[str]: | |
| data = _extract_json(raw_text) | |
| if data is None: | |
| return None | |
| return _BRAIN_ALIASES.get(str(data.get("brain", "")).strip().lower()) | |
| def _action_summary(action: Any) -> str: | |
| kind = action.kind | |
| if kind == "deploy_resource": | |
| return f"({action.region}, {action.resource_type}, qty={action.quantity})" | |
| if kind == "request_data": | |
| return f"({action.region}, {action.data_type})" | |
| if kind == "restrict_movement": | |
| return f"({action.region}, {action.severity})" | |
| if kind == "escalate": | |
| return f"({action.to_authority})" | |
| if kind == "reallocate_budget": | |
| return f"({action.from_resource} -> {action.to_resource}, amount={action.amount})" | |
| return "" | |
| def serialize_observation(obs: CrisisworldcortexObservation, last_reward: float = 0.0) -> str: | |
| parts: List[str] = [ | |
| f"Tick {obs.tick} | Ticks remaining: {obs.ticks_remaining} | Last reward: {last_reward:.2f}", | |
| ( | |
| "=== Resources ===\n" | |
| f"test_kits={obs.resources.test_kits} " | |
| f"hospital_beds_free={obs.resources.hospital_beds_free} " | |
| f"mobile_units={obs.resources.mobile_units} " | |
| f"vaccine_doses={obs.resources.vaccine_doses}" | |
| ), | |
| ] | |
| region_lines = ["=== Regions ==="] | |
| for region in obs.regions: | |
| region_lines.append( | |
| f"- {region.region}: cases_d_ago={region.reported_cases_d_ago} " | |
| f"hospital_load={region.hospital_load:.2f} " | |
| f"compliance_proxy={region.compliance_proxy:.2f}" | |
| ) | |
| parts.append("\n".join(region_lines)) | |
| if obs.legal_constraints: | |
| parts.append( | |
| "=== Legal constraints ===\n" | |
| + "\n".join( | |
| f"- {lc.rule_id}: blocks {lc.blocked_action} (unlock via {lc.unlock_via})" | |
| for lc in obs.legal_constraints | |
| ) | |
| ) | |
| if obs.recent_action_log: | |
| parts.append( | |
| "=== Recent actions ===\n" | |
| + "\n".join( | |
| f"- tick={e.tick} {e.action.kind}{_action_summary(e.action)} accepted={e.accepted}" | |
| for e in obs.recent_action_log[-8:] | |
| ) | |
| ) | |
| return "\n\n".join(parts) | |
| class _LocalRouter: | |
| """Lazy local LoRA inference wrapper. | |
| Tests can instantiate B6 without downloading models. The actual | |
| Transformers/PEFT load happens on the first episode tick, which is the | |
| path used by the human's smoke test after a trained adapter exists. | |
| """ | |
| def __init__( | |
| self, | |
| repo_id: str, | |
| *, | |
| base_model: str = "Qwen/Qwen2.5-1.5B-Instruct", | |
| hf_token: Optional[str] = None, | |
| load_in_4bit: bool = True, | |
| max_prompt_len: int = 2048, | |
| max_new_tokens: int = 32, | |
| ) -> None: | |
| self.repo_id = repo_id | |
| self.base_model = base_model | |
| self.hf_token = hf_token or os.getenv("HF_TOKEN") | |
| self.load_in_4bit = load_in_4bit | |
| self.max_prompt_len = max_prompt_len | |
| self.max_new_tokens = max_new_tokens | |
| self._model: Any = None | |
| self._tokenizer: Any = None | |
| def _ensure_loaded(self) -> None: | |
| if self._model is not None: | |
| return | |
| import torch | |
| from peft import PeftModel | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| kwargs: Dict[str, Any] = {"token": self.hf_token} | |
| if torch.cuda.is_available(): | |
| kwargs["torch_dtype"] = torch.bfloat16 | |
| if self.load_in_4bit and torch.cuda.is_available(): | |
| from transformers import BitsAndBytesConfig | |
| kwargs["quantization_config"] = BitsAndBytesConfig(load_in_4bit=True) | |
| kwargs["device_map"] = "auto" | |
| base = AutoModelForCausalLM.from_pretrained(self.base_model, **kwargs) | |
| if not torch.cuda.is_available(): | |
| base = base.to("cpu") | |
| tokenizer = AutoTokenizer.from_pretrained(self.base_model, token=self.hf_token) | |
| if tokenizer.pad_token_id is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| self._model = PeftModel.from_pretrained(base, self.repo_id) | |
| self._model.eval() | |
| self._tokenizer = tokenizer | |
| def select_brain( | |
| self, obs: CrisisworldcortexObservation, last_reward: float | |
| ) -> tuple[Optional[str], str]: | |
| import torch | |
| self._ensure_loaded() | |
| prompt = self._tokenizer.apply_chat_template( | |
| [ | |
| {"role": "system", "content": _ROUTER_SYSTEM_PROMPT}, | |
| {"role": "user", "content": serialize_observation(obs, last_reward)}, | |
| ], | |
| tokenize=False, | |
| add_generation_prompt=True, | |
| ) | |
| device = next(self._model.parameters()).device | |
| encoded = self._tokenizer( | |
| prompt, | |
| return_tensors="pt", | |
| truncation=True, | |
| max_length=self.max_prompt_len, | |
| ).to(device) | |
| prompt_len = int(encoded["input_ids"].shape[1]) | |
| with torch.no_grad(): | |
| out = self._model.generate( | |
| **encoded, | |
| do_sample=False, | |
| max_new_tokens=self.max_new_tokens, | |
| pad_token_id=self._tokenizer.pad_token_id, | |
| eos_token_id=self._tokenizer.eos_token_id, | |
| ) | |
| raw = self._tokenizer.decode(out[0][prompt_len:], skip_special_tokens=True).strip() | |
| return parse_router_choice(raw), raw | |
| class B6CortexTrainedRouter: | |
| """Cortex specialist selector driven by a trained router LoRA.""" | |
| CALLER_ID_PREFIX = "b6" | |
| def __init__( | |
| self, | |
| env: _EnvLike, | |
| llm: Any, | |
| *, | |
| router_repo: str, | |
| router_base_model: str = "Qwen/Qwen2.5-1.5B-Instruct", | |
| ) -> None: | |
| self._env = env | |
| self._llm = llm | |
| self._router = _LocalRouter(router_repo, base_model=router_base_model) | |
| self._brains = { | |
| "epidemiology": EpiBrain(llm), | |
| "logistics": LogisticsBrain(llm), | |
| "governance": GovernanceBrain(llm), | |
| } | |
| def run_episode( | |
| self, | |
| task: str, | |
| seed: int, | |
| max_ticks: int = 12, | |
| *, | |
| step_callback: Optional[StepCallback] = None, | |
| ) -> Dict[str, Any]: | |
| if hasattr(self._llm, "reset_counters"): | |
| self._llm.reset_counters(caller_id_prefix=f"{self.CALLER_ID_PREFIX}:") | |
| self._llm.reset_counters(caller_id_prefix="cortex:") | |
| obs = self._env.reset() | |
| last_reward = 0.0 | |
| rewards: List[float] = [] | |
| action_history: List[Dict[str, Any]] = [] | |
| steps_taken = 0 | |
| parse_failure_count = 0 | |
| for tick in range(1, max_ticks + 1): | |
| steps_taken = tick | |
| tick_error: Optional[ErrorKind] = None | |
| raw_router = "" | |
| parse_failure = False | |
| try: | |
| brain_id, raw_router = self._router.select_brain(obs, last_reward) | |
| if brain_id is None: | |
| parse_failure = True | |
| parse_failure_count += 1 | |
| tick_error = "parse_failure" | |
| wire_action = CrisisworldcortexAction(action=parse_failure_marker()) | |
| else: | |
| recommendation = self._brains[brain_id].run_tick(obs, last_reward, tick) | |
| wire_action = CrisisworldcortexAction(action=recommendation.top_action) | |
| except Exception as exc: | |
| print( | |
| f"[WARN] b6: trained router/brain failed at tick={tick}: {exc!r}", | |
| file=sys.stderr, | |
| flush=True, | |
| ) | |
| tick_error = "llm_call_failed" | |
| wire_action = CrisisworldcortexAction(action=NoOp()) | |
| obs = self._env.step(wire_action) | |
| current_reward = obs.reward if obs.reward is not None else 0.0 | |
| rewards.append(current_reward) | |
| if step_callback is not None: | |
| step_callback( | |
| B1StepEvent( | |
| tick=tick, | |
| action=wire_action.action, | |
| reward=current_reward, | |
| done=obs.done, | |
| error=tick_error, | |
| parse_failure=parse_failure, | |
| raw_llm=raw_router, | |
| ) | |
| ) | |
| action_history.append({"tick": tick, "kind": wire_action.action.kind, "accepted": True}) | |
| if obs.done: | |
| break | |
| last_reward = current_reward | |
| return { | |
| "task": task, | |
| "seed": seed, | |
| "rewards": rewards, | |
| "action_history": action_history, | |
| "steps_taken": steps_taken, | |
| "parse_failure_count": parse_failure_count, | |
| } | |