Implement config-driven models (Phase 1): graph MODELS dict, instantiate applies, per-request overrides

- Graph definitions (v3, v4) now declare MODELS mapping role → model string
- engine.py extracts MODELS and applies to nodes during instantiation
- frame_engine.process_message() accepts model_overrides for per-request swaps
  (restored via try/finally after processing)
- 11/11 engine tests green

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nico 2026-04-03 18:05:21 +02:00
parent ecfbc86676
commit cf42951b77
4 changed files with 87 additions and 43 deletions

View File

@ -63,6 +63,7 @@ def _graph_from_module(mod) -> dict:
"conditions": getattr(mod, "CONDITIONS", {}), "conditions": getattr(mod, "CONDITIONS", {}),
"audit": getattr(mod, "AUDIT", {}), "audit": getattr(mod, "AUDIT", {}),
"engine": getattr(mod, "ENGINE", "imperative"), "engine": getattr(mod, "ENGINE", "imperative"),
"models": getattr(mod, "MODELS", {}),
} }
@ -79,6 +80,12 @@ def instantiate_nodes(graph: dict, send_hud, process_manager: ProcessManager = N
nodes[role] = cls(send_hud=send_hud, process_manager=process_manager) nodes[role] = cls(send_hud=send_hud, process_manager=process_manager)
else: else:
nodes[role] = cls(send_hud=send_hud) nodes[role] = cls(send_hud=send_hud)
# Apply model from graph config (overrides class default)
model = graph.get("models", {}).get(role)
if model and hasattr(nodes[role], "model"):
nodes[role].model = model
log.info(f"[engine] {role} = {impl_name} ({cls.__name__}) model={model}")
else:
log.info(f"[engine] {role} = {impl_name} ({cls.__name__})") log.info(f"[engine] {role} = {impl_name} ({cls.__name__})")
return nodes return nodes

View File

@ -173,10 +173,23 @@ class FrameEngine:
# --- Main entry point --- # --- Main entry point ---
async def process_message(self, text: str, dashboard: list = None) -> dict: async def process_message(self, text: str, dashboard: list = None,
model_overrides: dict = None) -> dict:
"""Process a message through the frame pipeline. """Process a message through the frame pipeline.
Returns {response, controls, memorizer, frames, trace}.""" Returns {response, controls, memorizer, frames, trace}.
model_overrides: optional {role: model} to override node models for this request only.
"""
# Apply per-request model overrides (restored after processing)
saved_models = {}
if model_overrides:
for role, model in model_overrides.items():
node = self.nodes.get(role)
if node and hasattr(node, "model"):
saved_models[role] = node.model
node.model = model
try:
self._begin_trace(text) self._begin_trace(text)
# Handle ACTION: prefix # Handle ACTION: prefix
@ -224,6 +237,12 @@ class FrameEngine:
return await self._run_director_pipeline(command, mem_ctx, dashboard) return await self._run_director_pipeline(command, mem_ctx, dashboard)
else: else:
return await self._run_thinker_pipeline(command, mem_ctx, dashboard) return await self._run_thinker_pipeline(command, mem_ctx, dashboard)
finally:
# Restore original models after per-request overrides
for role, original_model in saved_models.items():
node = self.nodes.get(role)
if node:
node.model = original_model
# --- Pipeline variants --- # --- Pipeline variants ---

View File

@ -62,4 +62,13 @@ CONDITIONS = {
"has_tool_output": "thinker.tool_used is not empty", "has_tool_output": "thinker.tool_used is not empty",
} }
MODELS = {
"input": "google/gemini-2.0-flash-001",
"director": "anthropic/claude-haiku-4.5",
"thinker": "google/gemini-2.0-flash-001",
"interpreter": "google/gemini-2.0-flash-001",
"output": "google/gemini-2.0-flash-001",
"memorizer": "google/gemini-2.0-flash-001",
}
AUDIT = {} AUDIT = {}

View File

@ -68,4 +68,13 @@ CONDITIONS = {
"has_tool_output": "expert.tool_used is not empty", "has_tool_output": "expert.tool_used is not empty",
} }
MODELS = {
"input": "google/gemini-2.0-flash-001",
"pa": "anthropic/claude-haiku-4.5",
"expert_eras": "google/gemini-2.0-flash-001",
"interpreter": "google/gemini-2.0-flash-001",
"output": "google/gemini-2.0-flash-001",
"memorizer": "google/gemini-2.0-flash-001",
}
AUDIT = {} AUDIT = {}