| """ |
| Supervisor (orchestrator) agent built with LangGraph. |
| |
| Routes requests to: |
| β’ "summariser" β PaperSummarizerAgent |
| β’ "code_explainer" β CodeExplainerAgent |
| β’ "FINISH" β return final answer |
| """ |
|
|
| from __future__ import annotations |
|
|
| import operator |
| from typing import Annotated, Literal, TypedDict |
|
|
| from langchain_core.messages import AIMessage, BaseMessage, SystemMessage |
| from langchain_openai import ChatOpenAI |
| from langchain_groq import ChatGroq |
| from langgraph.graph import END, StateGraph |
|
|
| from agents import build_code_explainer, build_paper_summariser |
|
|
|
|
| class SupervisorState(TypedDict): |
| messages: Annotated[list[BaseMessage], operator.add] |
| next_agent: str |
| iteration: int |
|
|
|
|
| _SUPERVISOR_SYSTEM = """ |
| You are a research orchestrator that coordinates two specialist agents: |
| |
| β’ summariser β fetches and summarises research papers (arXiv, PDF) |
| β’ code_explainer β finds GitHub repos linked in the paper, explains algorithms, |
| and produces how-to-run guides |
| β’ FINISH β use this when the task is fully complete |
| |
| Given the conversation history, decide which agent to call next, OR output |
| FINISH if the task is fully complete. |
| |
| Reply with EXACTLY one of: summariser | code_explainer | FINISH |
| No other text. |
| """.strip() |
|
|
| |
|
|
| _supervisor_llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0) |
|
|
| def supervisor_node(state: SupervisorState) -> SupervisorState: |
| messages = [SystemMessage(content=_SUPERVISOR_SYSTEM)] + state["messages"] |
| response = _supervisor_llm.invoke(messages) |
| next_agent = response.content.strip() |
|
|
| iteration = state.get("iteration", 0) + 1 |
| if iteration > 6 or next_agent not in {"summariser", "code_explainer"}: |
| next_agent = "FINISH" |
|
|
| return {**state, "next_agent": next_agent, "iteration": iteration} |
|
|
|
|
| _summariser = build_paper_summariser() |
| _code_explainer = build_code_explainer() |
|
|
|
|
| def _run_agent(agent, state: SupervisorState) -> SupervisorState: |
| result = agent.invoke({"messages": state["messages"]}) |
| agent_messages = result.get("messages", []) |
| last = next( |
| (m for m in reversed(agent_messages) if isinstance(m, AIMessage)), |
| AIMessage(content="(no response)"), |
| ) |
| return {**state, "messages": state["messages"] + [last]} |
|
|
|
|
| def summariser_node(state: SupervisorState) -> SupervisorState: |
| return _run_agent(_summariser, state) |
|
|
|
|
| def code_explainer_node(state: SupervisorState) -> SupervisorState: |
| return _run_agent(_code_explainer, state) |
|
|
|
|
| def route(state: SupervisorState) -> Literal["summariser", "code_explainer", "__end__"]: |
| nxt = state.get("next_agent", "FINISH") |
| if nxt == "summariser": |
| return "summariser" |
| if nxt == "code_explainer": |
| return "code_explainer" |
| return END |
|
|
|
|
| def build_supervisor_graph() -> StateGraph: |
| graph = StateGraph(SupervisorState) |
|
|
| graph.add_node("supervisor", supervisor_node) |
| graph.add_node("summariser", summariser_node) |
| graph.add_node("code_explainer", code_explainer_node) |
|
|
| graph.set_entry_point("supervisor") |
| graph.add_conditional_edges("supervisor", route) |
| graph.add_edge("summariser", "supervisor") |
| graph.add_edge("code_explainer", "supervisor") |
|
|
| return graph.compile() |
|
|