Spaces:
Sleeping
Sleeping
| """ | |
| backend/agents/orchestrator.py | |
| LangGraph workflow graph β connects all agents with conditional routing. | |
| Graph structure: | |
| START | |
| β | |
| βΌ | |
| memory_retrieve β Pull relevant past memories | |
| β | |
| βΌ | |
| planner β Decompose task into steps | |
| β | |
| βΌ | |
| executor ββββββββββββββ β Execute one step at a time | |
| β β (loops until all steps done) | |
| β all steps done β | |
| βΌ β | |
| critic βββββββββββββββββ β Evaluate quality | |
| β approve β needs_replanning β back to planner | |
| βΌ | |
| memory_store β Save learnings | |
| β | |
| βΌ | |
| END | |
| Conditional edges: | |
| executor β executor (more steps to run) | |
| executor β critic (all steps done) | |
| critic β planner (needs replanning) | |
| critic β memory_store (approved) | |
| """ | |
| from __future__ import annotations | |
| from typing import Literal | |
| from langgraph.graph import StateGraph, START, END | |
| from ..state.graph_state import WorkflowState, TaskStatus | |
| from .planner import planner_node | |
| from .executor import executor_node | |
| from .critic import critic_node | |
| from .memory_agent import memory_retrieve_node, memory_store_node | |
| from ..core.logger import get_logger | |
| log = get_logger(__name__) | |
| # ββ Routing functions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def route_after_executor(state: WorkflowState) -> Literal["executor", "critic", "end"]: | |
| """ | |
| After executor runs one step, decide what's next: | |
| - More pending steps β run executor again | |
| - All done β send to critic | |
| - Fatal failure β end | |
| """ | |
| if state["status"] == TaskStatus.FAILED: | |
| log.warning("Executor fatal failure, ending", task_id=state["task_id"]) | |
| return "end" | |
| if state["status"] == TaskStatus.REFLECTING: | |
| return "critic" | |
| if state["status"] == TaskStatus.COMPLETED: | |
| return "critic" | |
| # Check for any pending steps | |
| pending = [s for s in state["plan"] if s["status"] == "pending"] | |
| if pending: | |
| return "executor" | |
| return "critic" | |
| def route_after_critic(state: WorkflowState) -> Literal["planner", "memory_store"]: | |
| """ | |
| After critic evaluates: | |
| - needs_replanning β back to planner (with critique context) | |
| - approved β store memory and complete | |
| """ | |
| if state.get("needs_replanning") and state["status"] == TaskStatus.PLANNING: | |
| log.info("Critic requested replan", task_id=state["task_id"]) | |
| return "planner" | |
| return "memory_store" | |
| def route_after_planner(state: WorkflowState) -> Literal["executor", "end"]: | |
| """After planner creates plan, check it's valid before executing.""" | |
| if state["status"] == TaskStatus.FAILED: | |
| return "end" | |
| if not state.get("plan"): | |
| log.error("Planner returned empty plan") | |
| return "end" | |
| return "executor" | |
| # ββ Graph builder βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build_workflow() -> StateGraph: | |
| """Build and compile the LangGraph workflow.""" | |
| graph = StateGraph(WorkflowState) | |
| # ββ Add nodes ββ | |
| graph.add_node("memory_retrieve", memory_retrieve_node) | |
| graph.add_node("planner", planner_node) | |
| graph.add_node("executor", executor_node) | |
| graph.add_node("critic", critic_node) | |
| graph.add_node("memory_store", memory_store_node) | |
| # ββ Add edges ββ | |
| # Entry: always start with memory retrieval | |
| graph.add_edge(START, "memory_retrieve") | |
| # Memory β Planner | |
| graph.add_edge("memory_retrieve", "planner") | |
| # Planner β Executor or END (if plan failed) | |
| graph.add_conditional_edges( | |
| "planner", | |
| route_after_planner, | |
| {"executor": "executor", "end": END}, | |
| ) | |
| # Executor β Executor (more steps) or Critic (done) or END (fatal) | |
| graph.add_conditional_edges( | |
| "executor", | |
| route_after_executor, | |
| {"executor": "executor", "critic": "critic", "end": END}, | |
| ) | |
| # Critic β Planner (replan) or Memory store (done) | |
| graph.add_conditional_edges( | |
| "critic", | |
| route_after_critic, | |
| {"planner": "planner", "memory_store": "memory_store"}, | |
| ) | |
| # Memory store β END | |
| graph.add_edge("memory_store", END) | |
| return graph.compile() | |
| # ββ Singleton compiled graph ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _workflow = None | |
| def get_workflow(): | |
| global _workflow | |
| if _workflow is None: | |
| _workflow = build_workflow() | |
| log.info("Workflow graph compiled") | |
| return _workflow | |
| # ββ Run function ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def run_workflow(initial_state: WorkflowState) -> WorkflowState: | |
| """ | |
| Execute the full multi-agent workflow. | |
| Returns the final state with all results, events, and metrics. | |
| """ | |
| workflow = get_workflow() | |
| log.info("Workflow starting", | |
| task_id=initial_state["task_id"], | |
| task=initial_state["task"][:80]) | |
| try: | |
| # LangGraph invoke β runs the full graph | |
| final_state = await workflow.ainvoke(initial_state) | |
| log.info("Workflow completed", | |
| task_id=initial_state["task_id"], | |
| status=final_state.get("status"), | |
| score=final_state.get("quality_score"), | |
| tokens=final_state.get("total_tokens")) | |
| return final_state | |
| except Exception as e: | |
| log.error("Workflow crashed", error=str(e), task_id=initial_state["task_id"]) | |
| return { | |
| **initial_state, | |
| "status": TaskStatus.FAILED, | |
| "error_message": f"Workflow crashed: {e}", | |
| } | |
| async def stream_workflow(initial_state: WorkflowState): | |
| """ | |
| Stream workflow events as they happen. | |
| Yields state snapshots after each node execution. | |
| Used by SSE endpoint for real-time UI updates. | |
| """ | |
| workflow = get_workflow() | |
| async for chunk in workflow.astream(initial_state, stream_mode="values"): | |
| yield chunk | |