This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/models.py
Adam Moussa 366d7247da Stabilize router and consolidate agent registry
Phase 1 stabilization. Removes the four-copy prompt/agent-description drift
surface and the silent router fallback.

- models.py: hoist model IDs to module-level constants; add with_retries()
  helper (2 retries on Anthropic+OpenAI transient errors via with_retry).
- agents.py: single AGENTS dict (model_fn, prompt, description) and a
  make_agent_node() factory that collapses six near-identical node functions.
- graph.py: router prompt is generated from AGENTS; router_node uses
  with_structured_output(RouteDecision) and returns an explicit "unknown"
  route instead of the silent "researcher" fallback. New unknown_node wires
  to END. All LLM invocations go through with_retries.
- state.py: add "unknown" to the route Literal.
- run.py: --route-only now imports router_node from graph.py, killing the
  fourth prompt copy.
- tests/: pytest golden-set (20 labelled tasks + size guard). Skips cleanly
  without ANTHROPIC_API_KEY or COMPOSIO_API_KEY. Validated 21/21 passing.
2026-05-15 11:20:28 -04:00

90 lines
2.3 KiB
Python

import os
from langchain_anthropic import ChatAnthropic
from langchain_core.runnables import Runnable
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_openai import ChatOpenAI
# Model IDs — single source of truth. Bump here when families ship new revs.
CLAUDE_SONNET = "claude-sonnet-4-20250514"
CLAUDE_HAIKU = "claude-haiku-4-5-20251001"
OPENAI_CROSS_REVIEWER = "gpt-4.1"
GEMINI_SCANNER = "gemini-2.5-pro"
DEEPSEEK_FAST_CODER = "deepseek-coder"
DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
def _collect_retriable_exceptions() -> tuple[type[BaseException], ...]:
excs: list[type[BaseException]] = []
try:
import anthropic
excs.extend(
[
anthropic.APIConnectionError,
anthropic.RateLimitError,
anthropic.InternalServerError,
]
)
except ImportError:
pass
try:
import openai
excs.extend(
[
openai.APIConnectionError,
openai.RateLimitError,
openai.InternalServerError,
]
)
except ImportError:
pass
return tuple(excs)
RETRIABLE_EXCEPTIONS = _collect_retriable_exceptions()
def with_retries(runnable: Runnable) -> Runnable:
"""Wrap an LLM runnable with up to 2 retries on transient provider errors."""
if not RETRIABLE_EXCEPTIONS:
return runnable
return runnable.with_retry(
retry_if_exception_type=RETRIABLE_EXCEPTIONS,
stop_after_attempt=3,
wait_exponential_jitter=True,
)
def get_orchestrator():
return ChatAnthropic(model=CLAUDE_SONNET, temperature=0)
def get_implementer():
return ChatAnthropic(model=CLAUDE_SONNET, temperature=0)
def get_reviewer():
return ChatAnthropic(model=CLAUDE_SONNET, temperature=0)
def get_researcher():
return ChatAnthropic(model=CLAUDE_HAIKU, temperature=0)
def get_cross_reviewer():
return ChatOpenAI(model=OPENAI_CROSS_REVIEWER, temperature=0.2)
def get_scanner():
return ChatGoogleGenerativeAI(model=GEMINI_SCANNER, temperature=0)
def get_fast_coder():
return ChatOpenAI(
model=DEEPSEEK_FAST_CODER,
base_url=DEEPSEEK_BASE_URL,
api_key=os.getenv("DEEPSEEK_API_KEY"),
temperature=0,
)