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.
44 lines
1.1 KiB
Python
Executable file
44 lines
1.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""CLI entry point for the orchestration graph.
|
|
|
|
Usage:
|
|
python3 run.py "Write a function that validates emails"
|
|
python3 run.py --route-only "Send a Slack message to #general"
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
|
load_dotenv(".env")
|
|
|
|
from graph import app, router_node # noqa: E402 (env must be loaded before graph imports composio)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
|
|
print("Usage: python3 run.py [--route-only] <task description>")
|
|
sys.exit(1)
|
|
|
|
route_only = "--route-only" in sys.argv
|
|
args = [a for a in sys.argv[1:] if a != "--route-only"]
|
|
task = " ".join(args)
|
|
|
|
if route_only:
|
|
out = router_node({"task": task, "messages": []})
|
|
print(out["route"])
|
|
return
|
|
|
|
result = app.invoke({"task": task, "messages": []})
|
|
print(f"[{result['route']}]")
|
|
print()
|
|
output = result.get("result", "")
|
|
if isinstance(output, list):
|
|
output = "\n".join(str(c) for c in output)
|
|
print(output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|