""" Agent Orchestrator — 多 Agent 编排引擎。 支持三种协作模式: - route: Router Agent 分析问题 → 分发到最合适的 Specialist Agent - sequential: Agent 流水线执行,前者输出作为后者输入 - debate: 多个 Agent 独立回答 → Aggregator 汇总为最终答案 """ from __future__ import annotations import json import logging import uuid from typing import Any, Callable, Dict, List, Optional from pydantic import BaseModel, Field from app.agent_runtime import ( AgentRuntime, AgentConfig, AgentLLMConfig, AgentToolConfig, AgentResult, ) from app.agent_runtime.core import _LLMClient logger = logging.getLogger(__name__) class OrchestratorAgentConfig(BaseModel): """编排中单个 Agent 的配置""" id: str = Field(..., description="Agent 标识") name: str = Field(default="Agent", description="显示名称") system_prompt: str = Field(default="你是一个有用的AI助手。") model: str = Field(default="deepseek-v4-flash") provider: str = Field(default="deepseek") temperature: float = 0.7 max_iterations: int = 10 tools: List[str] = Field(default_factory=list, description="工具白名单,空=全部") description: str = Field(default="", description="Agent 专长描述(路由模式用)") class OrchestratorStep(BaseModel): """编排中的单步执行记录""" agent_id: str agent_name: str input: str = "" output: str = "" iterations_used: int = 0 tool_calls_made: int = 0 error: Optional[str] = None class OrchestratorResult(BaseModel): """编排执行结果""" mode: str final_answer: str steps: List[OrchestratorStep] = Field(default_factory=list) agent_results: List[Dict[str, Any]] = Field(default_factory=list) _ROUTER_SYSTEM_PROMPT = """你是一个路由调度员。你的任务是从以下 Specialist Agent 中选择一个最适合处理用户问题的 Agent。 可用的 Specialist Agent: {agent_list} 请返回 JSON 格式(不要 markdown 包裹),包含: 1. "selected_agent": 选中的 Agent ID 2. "reason": 选择理由(一句话) 规则: - 选择与问题最匹配的 Agent - 如果问题涉及多个领域,选择最相关的那个 - 必须从上述列表中选择,不能编造 Agent ID""" _AGGREGATOR_SYSTEM_PROMPT = """你是一个回答汇总员。多个 AI Agent 对同一个问题给出了不同的回答。 请分析所有回答,输出一份综合的最终答案。 - 如果各 Agent 回答一致,合并要点 - 如果有分歧,指出不同观点并给出你的判断 - 以专业、清晰的格式输出最终答案""" class AgentOrchestrator: """ 多 Agent 编排器。 用法: orch = AgentOrchestrator() result = await orch.run("route", question, [agent1, agent2, agent3]) """ def __init__(self, default_llm_config: Optional[AgentLLMConfig] = None): self._default_llm = default_llm_config or AgentLLMConfig( model="deepseek-v4-flash", temperature=0.3, ) async def run( self, mode: str, question: str, agents: List[OrchestratorAgentConfig], on_llm_call: Optional[Callable[[Dict[str, Any]], Any]] = None, ) -> OrchestratorResult: """执行多 Agent 编排。""" mode = mode.lower() if mode == "route": return await self._route(question, agents, on_llm_call) elif mode == "sequential": return await self._sequential(question, agents, on_llm_call) elif mode == "debate": return await self._debate(question, agents, on_llm_call) else: raise ValueError(f"不支持的编排模式: {mode},可选: route, sequential, debate") async def _route( self, question: str, agents: List[OrchestratorAgentConfig], on_llm_call: Optional[Callable] = None, ) -> OrchestratorResult: """路由模式:Router → Specialist。""" # 构建 Agent 列表描述 agent_lines = [] for a in agents: desc = a.description or a.name agent_lines.append(f"- id: {a.id}, name: {a.name}, description: {desc}") agent_list_str = "\n".join(agent_lines) router_prompt = _ROUTER_SYSTEM_PROMPT.format(agent_list=agent_list_str) # 创建 Router Agent router_runtime = AgentRuntime( AgentConfig( name="router", system_prompt=router_prompt, llm=AgentLLMConfig( model=self._default_llm.model, temperature=0.1, # 低温度确保确定性 ), tools=AgentToolConfig( include_tools=[], # Router 不需要工具 ), ), on_llm_call=on_llm_call, ) router_result = await router_runtime.run(question) if not router_result.success: return OrchestratorResult( mode="route", final_answer=f"路由决策失败: {router_result.content}", steps=[], ) # 解析 Router 的输出 selected_agent_id = None try: parsed = json.loads(router_result.content.strip().removeprefix("```json").removesuffix("```").strip()) selected_agent_id = parsed.get("selected_agent", "") except (json.JSONDecodeError, AttributeError): # 尝试从文本中提取 for a in agents: if a.id in router_result.content: selected_agent_id = a.id break if not selected_agent_id: # 取第一个 selected_agent_id = agents[0].id if agents else "" # 找到对应的 Specialist Agent specialist = next((a for a in agents if a.id == selected_agent_id), agents[0] if agents else None) if not specialist: return OrchestratorResult( mode="route", final_answer="没有可用的 Specialist Agent", steps=[], ) # 运行 Specialist Agent specialist_runtime = AgentRuntime( AgentConfig( name=specialist.name, system_prompt=specialist.system_prompt, llm=AgentLLMConfig( model=specialist.model, provider=specialist.provider, temperature=specialist.temperature, max_iterations=specialist.max_iterations, ), tools=AgentToolConfig( include_tools=specialist.tools, ), ), on_llm_call=on_llm_call, ) specialist_result = await specialist_runtime.run(question) return OrchestratorResult( mode="route", final_answer=specialist_result.content, steps=[ OrchestratorStep( agent_id="router", agent_name="Router", input=question, output=f"选择: {specialist.name} ({specialist.id})", ), OrchestratorStep( agent_id=specialist.id, agent_name=specialist.name, input=question, output=specialist_result.content[:300], iterations_used=specialist_result.iterations_used, tool_calls_made=specialist_result.tool_calls_made, ), ], agent_results=[ {"agent_id": specialist.id, "agent_name": specialist.name, "output": specialist_result.content}, ], ) async def _sequential( self, question: str, agents: List[OrchestratorAgentConfig], on_llm_call: Optional[Callable] = None, ) -> OrchestratorResult: """顺序模式:Agent A 输出 → Agent B 输入。""" if not agents: return OrchestratorResult(mode="sequential", final_answer="无 Agent 可执行") steps: List[OrchestratorStep] = [] current_input = question for i, agent_cfg in enumerate(agents): runtime = AgentRuntime( AgentConfig( name=agent_cfg.name, system_prompt=agent_cfg.system_prompt, llm=AgentLLMConfig( model=agent_cfg.model, provider=agent_cfg.provider, temperature=agent_cfg.temperature, max_iterations=agent_cfg.max_iterations, ), tools=AgentToolConfig( include_tools=agent_cfg.tools, ), ), on_llm_call=on_llm_call, ) # 第一个 Agent 接收原始问题,后续 Agent 接收前一个的输出 agent_input = current_input if i > 0: agent_input = ( f"这是前一个 Agent 的处理结果,请在此基础上继续处理。\n\n" f"原始问题: {question}\n\n" f"前序输出:\n{current_input}" ) result = await runtime.run(agent_input) step = OrchestratorStep( agent_id=agent_cfg.id, agent_name=agent_cfg.name, input=agent_input[:200], output=result.content[:500], iterations_used=result.iterations_used, tool_calls_made=result.tool_calls_made, error=None if result.success else result.error, ) steps.append(step) if not result.success: break current_input = result.content final_answer = steps[-1].output if steps else "无输出" return OrchestratorResult( mode="sequential", final_answer=final_answer, steps=steps, agent_results=[ {"agent_id": s.agent_id, "agent_name": s.agent_name, "output": s.output} for s in steps ], ) async def _debate( self, question: str, agents: List[OrchestratorAgentConfig], on_llm_call: Optional[Callable] = None, ) -> OrchestratorResult: """辩论模式:多 Agent 独立回答 → Aggregator 汇总。""" if not agents: return OrchestratorResult(mode="debate", final_answer="无 Agent 可执行") steps: List[OrchestratorStep] = [] agent_outputs: List[Dict[str, Any]] = [] # 第一阶段:所有 Agent 独立回答 for agent_cfg in agents: runtime = AgentRuntime( AgentConfig( name=agent_cfg.name, system_prompt=agent_cfg.system_prompt, llm=AgentLLMConfig( model=agent_cfg.model, provider=agent_cfg.provider, temperature=agent_cfg.temperature, max_iterations=agent_cfg.max_iterations, ), tools=AgentToolConfig( include_tools=agent_cfg.tools, ), ), on_llm_call=on_llm_call, ) result = await runtime.run(question) step = OrchestratorStep( agent_id=agent_cfg.id, agent_name=agent_cfg.name, input=question, output=result.content[:500], iterations_used=result.iterations_used, tool_calls_made=result.tool_calls_made, error=None if result.success else result.error, ) steps.append(step) agent_outputs.append({ "agent_id": agent_cfg.id, "agent_name": agent_cfg.name, "output": result.content, }) # 第二阶段:Aggregator 汇总所有回答 if len(agent_outputs) >= 2: outputs_text = "\n\n---\n\n".join( f"## {ao['agent_name']} 的回答\n{ao['output']}" for ao in agent_outputs ) aggregator_prompt = ( f"用户问题: {question}\n\n" f"以下是多个 AI Agent 对该问题的回答:\n\n{outputs_text}\n\n" "请综合所有回答,输出一份完整、准确的最终答案。" ) aggregator_runtime = AgentRuntime( AgentConfig( name="aggregator", system_prompt=_AGGREGATOR_SYSTEM_PROMPT, llm=AgentLLMConfig( model=self._default_llm.model, temperature=0.3, ), tools=AgentToolConfig(include_tools=[]), ), on_llm_call=on_llm_call, ) final_result = await aggregator_runtime.run(aggregator_prompt) final_answer = final_result.content steps.append(OrchestratorStep( agent_id="aggregator", agent_name="Aggregator", input="汇总各 Agent 回答", output=final_answer[:500], )) else: final_answer = agent_outputs[0]["output"] if agent_outputs else "无回答" return OrchestratorResult( mode="debate", final_answer=final_answer, steps=steps, agent_results=agent_outputs, )