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:
parent
ecfbc86676
commit
cf42951b77
@ -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,7 +80,13 @@ 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)
|
||||||
log.info(f"[engine] {role} = {impl_name} ({cls.__name__})")
|
# 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__})")
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -173,57 +173,76 @@ 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}.
|
||||||
|
|
||||||
self._begin_trace(text)
|
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
|
||||||
|
|
||||||
# Handle ACTION: prefix
|
try:
|
||||||
if text.startswith("ACTION:"):
|
self._begin_trace(text)
|
||||||
return await self._handle_action(text, dashboard)
|
|
||||||
|
|
||||||
# Setup
|
# Handle ACTION: prefix
|
||||||
envelope = Envelope(
|
if text.startswith("ACTION:"):
|
||||||
text=text, user_id=self.identity,
|
return await self._handle_action(text, dashboard)
|
||||||
session_id="test", timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
)
|
|
||||||
self.sensor.note_user_activity()
|
|
||||||
if dashboard is not None:
|
|
||||||
self.sensor.update_browser_dashboard(dashboard)
|
|
||||||
self.history.append({"role": "user", "content": text})
|
|
||||||
|
|
||||||
# --- Frame 1: Input ---
|
# Setup
|
||||||
mem_ctx = self._build_context(dashboard)
|
envelope = Envelope(
|
||||||
rec = self._begin_frame(1, "input", input_summary=text[:100])
|
text=text, user_id=self.identity,
|
||||||
|
session_id="test", timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
self.sensor.note_user_activity()
|
||||||
|
if dashboard is not None:
|
||||||
|
self.sensor.update_browser_dashboard(dashboard)
|
||||||
|
self.history.append({"role": "user", "content": text})
|
||||||
|
|
||||||
command = await self.nodes["input"].process(
|
# --- Frame 1: Input ---
|
||||||
envelope, self.history, memory_context=mem_ctx,
|
mem_ctx = self._build_context(dashboard)
|
||||||
identity=self.identity, channel=self.channel)
|
rec = self._begin_frame(1, "input", input_summary=text[:100])
|
||||||
|
|
||||||
a = command.analysis
|
command = await self.nodes["input"].process(
|
||||||
cmd_summary = f"intent={a.intent} language={a.language} tone={a.tone} complexity={a.complexity}"
|
envelope, self.history, memory_context=mem_ctx,
|
||||||
|
identity=self.identity, channel=self.channel)
|
||||||
|
|
||||||
# Check reflex condition
|
a = command.analysis
|
||||||
is_reflex = self._check_condition("reflex", command=command)
|
cmd_summary = f"intent={a.intent} language={a.language} tone={a.tone} complexity={a.complexity}"
|
||||||
if is_reflex:
|
|
||||||
self._end_frame(rec, output_summary=cmd_summary,
|
|
||||||
route="output (reflex)", condition="reflex=True")
|
|
||||||
await self._send_hud({"node": "runtime", "event": "reflex_path",
|
|
||||||
"detail": f"{a.intent}/{a.complexity}"})
|
|
||||||
return await self._run_reflex(command, mem_ctx)
|
|
||||||
else:
|
|
||||||
next_node = "pa" if self.has_pa else ("director" if self.has_director else "thinker")
|
|
||||||
self._end_frame(rec, output_summary=cmd_summary,
|
|
||||||
route=next_node, condition=f"reflex=False")
|
|
||||||
|
|
||||||
# --- Frame 2+: Pipeline ---
|
# Check reflex condition
|
||||||
if self.has_pa:
|
is_reflex = self._check_condition("reflex", command=command)
|
||||||
return await self._run_expert_pipeline(command, mem_ctx, dashboard)
|
if is_reflex:
|
||||||
elif self.has_director:
|
self._end_frame(rec, output_summary=cmd_summary,
|
||||||
return await self._run_director_pipeline(command, mem_ctx, dashboard)
|
route="output (reflex)", condition="reflex=True")
|
||||||
else:
|
await self._send_hud({"node": "runtime", "event": "reflex_path",
|
||||||
return await self._run_thinker_pipeline(command, mem_ctx, dashboard)
|
"detail": f"{a.intent}/{a.complexity}"})
|
||||||
|
return await self._run_reflex(command, mem_ctx)
|
||||||
|
else:
|
||||||
|
next_node = "pa" if self.has_pa else ("director" if self.has_director else "thinker")
|
||||||
|
self._end_frame(rec, output_summary=cmd_summary,
|
||||||
|
route=next_node, condition=f"reflex=False")
|
||||||
|
|
||||||
|
# --- Frame 2+: Pipeline ---
|
||||||
|
if self.has_pa:
|
||||||
|
return await self._run_expert_pipeline(command, mem_ctx, dashboard)
|
||||||
|
elif self.has_director:
|
||||||
|
return await self._run_director_pipeline(command, mem_ctx, dashboard)
|
||||||
|
else:
|
||||||
|
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 ---
|
||||||
|
|
||||||
|
|||||||
@ -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 = {}
|
||||||
|
|||||||
@ -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 = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user