from abc import ABC from collections.abc import AsyncGenerator, Callable, Mapping, Sequence from typing import TYPE_CHECKING, Any, cast from pydantic import BaseModel from zhenxun.services.ai.core.exceptions import AbortException from zhenxun.services.ai.core.messages import AgentMessage, LLMMessage from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent from zhenxun.services.ai.core.templates import PromptTemplate from zhenxun.services.ai.flow.agent.agent import Agent, ToolSource from zhenxun.services.ai.flow.agent.models import AgentConfig from zhenxun.services.ai.flow.team.models import ( CallAction, ConcurrentCallAction, FinishAction, TeamAction, ) from zhenxun.services.ai.flow.team.router import BaseRouter from zhenxun.services.ai.run import RunContext, Task from zhenxun.services.ai.tools.bridges.delegate import DelegateTool from zhenxun.services.log import logger if TYPE_CHECKING: from zhenxun.services.ai.flow.team.team import Team class BaseTeamStrategy(ABC): """多智能体团队协作策略基类""" default_system_prompt: str = "" def __init__(self, custom_prompt: str | None = None): """ 多智能体团队协作策略基类初始化。 参数: custom_prompt: 自定义系统提示词,用于覆盖默认的团队系统提示词模板。 """ self.custom_prompt = custom_prompt def get_prompt(self, **kwargs) -> str: template = self.custom_prompt or self.default_system_prompt return PromptTemplate(template).render(**kwargs) async def generate_plan( self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs ) -> AsyncGenerator[TeamAction, Any]: """ 核心决策生成器 (Action Yielding Pattern)。 第三方开发者只需重写此方法: 1. 使用 `yield CallAction(...)` 派发任务,系统会自动拦截并执行, 然后将 `AgentRunResult` 通过 .asend() 传回。 2. 使用 `yield FinishAction(...)` 结束团队协作。 """ yield FinishAction( result="The Strategy has not implemented generate_plan() yet." ) def _build_leader_agent( self, team: "Team", name: str, instruction: str, tools: list[ToolSource] ) -> Agent: """ 统一的团队 Leader / Planner 装配工厂。 自动处理无状态配置以及 HITL 状态继承。 """ leader_config = AgentConfig( stateless=team.runtime_config.stateless if team.runtime_config else True, enable_hitl=getattr(team.runtime_config, "leader_enable_hitl", False), ) target_model = getattr(self, "leader_model", None) or getattr( team, "model", None ) if not target_model: for m in team.members: if m_model := getattr(m, "model_name", None) or getattr( m, "model", None ): target_model = m_model break return Agent( name=name, instruction=instruction, model=target_model, tools=tools, config=leader_config, ) class RouteStrategy(BaseTeamStrategy): """路由策略:基于挂载的 Router 进行最合适的专家分发""" def __init__( self, state_flow: "Mapping[str, Sequence[str | Any]] | Callable | None" = None, selector_func: Callable[..., str | None] | None = None, router: BaseRouter | None = None, leader_model: str | None = None, leader_tools: list[ToolSource] | None = None, custom_prompt: str | None = None, ): """ 路由策略初始化,基于挂载的 Router 进行最合适的专家分发。 参数: state_flow: 状态流转规则字典或动态函数,定义成员之间控制流的物理走向。 selector_func: 极速硬路由的静态选择函数,返回目标智能体名称。 router: 自定义的动态路由器实例 (如 LLMRouter, RegexRouter 等)。 leader_model: 路由节点 (Leader) 使用的大模型名称,若为空则默认继承全局。 leader_tools: 挂载给路由节点 (Leader) 的专属工具列表。 custom_prompt: 自定义系统提示词,用于覆盖默认的路由系统提示词。 """ super().__init__(custom_prompt=custom_prompt) self.selector_func = selector_func self.router = router self.leader_model = leader_model self.leader_tools = leader_tools or [] if isinstance(state_flow, dict): from zhenxun.services.ai.flow.team.models import Transition normalized_flow = {} for k, targets in state_flow.items(): normalized_targets = [] for t in targets: if isinstance(t, str): normalized_targets.append(Transition(target=t)) else: normalized_targets.append(t) normalized_flow[k] = normalized_targets self.state_flow = normalized_flow else: self.state_flow = state_flow async def generate_plan( self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs ) -> AsyncGenerator[TeamAction, Any]: router = self.router if not router: from .router import ChainRouter, FunctionRouter, LLMRouter routers = [] if self.selector_func: routers.append(FunctionRouter(self.selector_func)) routers.append( LLMRouter( team_name=team.name, members=team.members, leader_model=self.leader_model, leader_tools=self.leader_tools, state_flow=self.state_flow, runtime_config=getattr(team, "runtime_config", None), custom_prompt=self.custom_prompt, ) ) router = ChainRouter(routers) cycle_count = 0 exec_config = kwargs.get("config") max_cycles = getattr(exec_config, "max_cycles", 15) if exec_config else 15 logger.info(f"🛣️ [RouteStrategy] '{team.name}' 正在获取初始路由决策...") decision = await router.route(context, [], prompt) if not decision: logger.warning( f"🚨 [RouteStrategy] Team '{team.name}' 的所有路由策略未能命中目标。" ) raise AbortException( reason=f"Team '{team.name}' 无法找到合适的路由节点处理该任务", display="🚨 团队协作失败,无法分配任务。", ) current_target = decision.target_name handoff_reason = decision.reason context_data = decision.context_data while True: cycle_count += 1 if cycle_count > max_cycles: logger.error( f"🚨 [RouteStrategy] Team '{team.name}' 路由陷入死循环!" f"已达到最大限制 {max_cycles} 次。" ) raise AbortException( reason=( f"Team '{team.name}' 路由流转超过最大次数限制" f" ({max_cycles}次)," "已强制熔断。" ), display="🚨 团队协作陷入死循环,已被系统强制中断。", ) handoff_history_messages: list[AgentMessage] = [] upstream_info = [] if handoff_reason: upstream_info.append(f"【移交说明】\n{handoff_reason}") if context_data: if isinstance(context_data, dict): import json formatted_data = json.dumps( context_data, ensure_ascii=False, indent=2 ) upstream_info.append( f"【结构化上下文载荷】\n```json\n{formatted_data}\n```" ) else: upstream_info.append(f"【核心上下文数据】\n{context_data}") combined_info = "\n\n".join(upstream_info) if combined_info: handoff_msg = LLMMessage.system( f"### 🔄 [来自上游节点的移交数据]\n{combined_info}" ) handoff_history_messages.append(handoff_msg) run_result = yield CallAction( agent=current_target, task=prompt, history=handoff_history_messages, kwargs=kwargs, ) if run_result.handoff: current_target = run_result.handoff.target handoff_reason = run_result.handoff.reason context_data = run_result.handoff.context_data continue output_str = str(run_result.output) fast_routed = False if isinstance(self.state_flow, dict) and current_target in self.state_flow: for t in self.state_flow[current_target]: trigger_regex = getattr(t, "trigger_regex", None) if trigger_regex: import re if re.search(trigger_regex, output_str): current_target = getattr(t, "target", current_target) handoff_reason = "" context_data = output_str fast_routed = True break trigger_func = getattr(t, "trigger_func", None) if trigger_func: try: if trigger_func(output_str): current_target = getattr(t, "target", current_target) handoff_reason = "" context_data = output_str fast_routed = True break except Exception: pass if fast_routed: logger.info( f"🛣️ **路由决策**: 委派给专员 👨💼`{current_target}`" "(系统拦截:正则/函数状态流发生转移)" ) continue yield FinishAction(result=run_result.output) break class CoordinateStrategy(BaseTeamStrategy): """协作策略:Leader 自主规划,委派任务给 Sub-Agents 并汇总结果""" default_system_prompt = """## 角色与目标 你是一个多智能体团队的协调者(Leader)。 你可以使用你自身携带的工具先查阅、收集资料;也可以分析用户的目标将其拆解为子任务,并委派给合适的下属专员。 当你收集齐所有需要的信息或专员报告后,请汇总生成最终回复向用户汇报。""" def __init__( self, leader_model: str | None = None, leader_tools: list[ToolSource] | None = None, custom_prompt: str | None = None, ): """ 协作策略初始化,Leader 主动拆解任务,委派给 Sub-Agents 并汇总结果。 参数: leader_model: 协调节点 (Leader) 使用的大模型名称,若为空则默认继承全局。 leader_tools: 挂载给协调节点 (Leader) 的专属工具列表。 custom_prompt: 自定义系统提示词,用于覆盖默认的协调系统提示词。 """ super().__init__(custom_prompt=custom_prompt) self.leader_model = leader_model self.leader_tools = leader_tools or [] async def generate_plan( self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs ) -> AsyncGenerator[TeamAction, Any]: delegation_tools = [] for m in team.members: persona = getattr(m, "persona", None) desc = getattr(m, "description", "") or "处理节点" if persona and not isinstance(persona, dict): desc = f"角色:{persona.role},目标:{persona.goal}" delegation_tools.append( DelegateTool( runnable=m, name=f"delegate_to_{m.name}", description=f"将子任务委派给专员 [{m.name}] 处理。专长:{desc}", ) ) leader_tools = self.leader_tools.copy() leader_tools.extend(delegation_tools) leader_agent = self._build_leader_agent( team=team, name=f"{team.name}_Leader", instruction=self.get_prompt(), tools=leader_tools, ) logger.debug(f"✨ **团队 [{team.name}] Leader** 正在汇总各方报告...") if context.run.event_bus: await context.run.event_bus.emit( ToolStreamChunkEvent( tool_name="Team Leader", content="✨ 团队 Leader 正在汇总各方报告...", ) ) logger.debug(f"👨💼 [CoordinateStrategy] '{team.name}' 正在启动协调推理循环...") leader_res = yield CallAction(agent=leader_agent, task=prompt) yield FinishAction(result=leader_res.output) class BroadcastStrategy(BaseTeamStrategy): """广播策略:并发让所有成员处理同一个任务,最后由 Leader 总结""" default_system_prompt = """## 角色与目标 你是一个多智能体团队的总结者(Leader)。 以下是各位专家的独立处理结果,请融合各方观点,取长补短,给出一份最终的总结报告。""" def __init__( self, leader_model: str | None = None, leader_tools: list[ToolSource] | None = None, custom_prompt: str | None = None, ): """ 广播策略初始化,并发让所有成员处理同一个任务,最后由 Leader 总结。 参数: leader_model: 总结节点 (Leader) 使用的大模型名称,若为空则默认继承全局。 leader_tools: 挂载给总结节点 (Leader) 的专属工具列表。 custom_prompt: 自定义系统提示词,用于覆盖默认的广播总结系统提示词。 """ super().__init__(custom_prompt=custom_prompt) self.leader_model = leader_model self.leader_tools = leader_tools or [] async def generate_plan( self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs ) -> AsyncGenerator[TeamAction, Any]: task_desc_str = ( prompt.description if isinstance(prompt, Task) else (prompt or "") ) if context.run.event_bus: await context.run.event_bus.emit( ToolStreamChunkEvent( tool_name="Team Broadcaster", content=f"🚀 正在并发广播任务给 {len(team.members)} 位专家...", ) ) actions = [CallAction(agent=m.name, task=task_desc_str) for m in team.members] results = yield ConcurrentCallAction(actions=actions) if context.run.event_bus: await context.run.event_bus.emit( ToolStreamChunkEvent( tool_name="Team Leader", content="✨ 所有专家汇报完毕,Leader 正在融合各方观点...", ) ) logger.debug(f"✨ **团队 [{team.name}] Leader** 正在汇总各方报告...") summary_text = "\n\n".join( [f"### 【{name} 的意见】:\n{res.output}" for name, res in results] ) synthesize_prompt = ( f"**用户原始任务**: {task_desc_str}\n\n" f"以下是各位专家的独立处理结果,请融合各方观点,给出一份最终的总结报告:\n\n" f"{summary_text}" ) leader_agent = self._build_leader_agent( team=team, name=f"{team.name}_Leader", instruction=self.get_prompt(), tools=self.leader_tools, ) leader_res = yield CallAction(agent=leader_agent, task=synthesize_prompt) yield FinishAction(result=leader_res.output) class TaskStrategy(BaseTeamStrategy): """任务规划策略:Leader 利用工具箱在黑板上拆解任务、管理依赖并驱动 Member 执行""" default_system_prompt = """ 你是一个多智能体团队的项目经理(Planner)。 请仔细阅读用户的请求,将其拆解为一个个具体的子任务, 并利用 `create_task` 建立所有任务和依赖关系(注意 `assignee` 必须严格从下方的团队成员中选择)。 【⚠️核心执行流规范】 1. 分配完毕后,**必须立刻停止调用任何工具,并直接输出纯文本回复** (如:'任务已分配,等待执行'),从而结束你的当前回合。 2. 当底层自动执行完毕后,系统会再次唤醒你并提供最新的看板结果。 请根据结果决定是下发新任务、要求重做,还是调用 `mark_all_complete` 汇报总结。 """ # noqa: E501 def __init__( self, leader_model: str | None = None, leader_tools: list[ToolSource] | None = None, max_iterations: int = 15, blackboard_schema: type[BaseModel] | None = None, initial_blackboard_state: BaseModel | None = None, custom_prompt: str | None = None, ): """ 任务规划策略初始化,Leader 利用工具箱在黑板上拆解任务、管理依赖并 驱动 Member 执行。 参数: leader_model: 规划节点 (Leader) 使用的大模型名称,若为空则默认继承全局。 leader_tools: 挂载给规划节点 (Leader) 的专属附加工具列表。 max_iterations: 引擎驱动的状态机最大迭代/循环次数,防止死循环。 blackboard_schema: 团队共享黑板的数据结构类型 (Pydantic Model 类)。 initial_blackboard_state: 共享黑板的初始数据状态实例。 custom_prompt: 自定义系统提示词,用于覆盖默认的规划系统提示词。 """ super().__init__(custom_prompt=custom_prompt) self.leader_model = leader_model self.leader_tools = leader_tools or [] self.max_iterations = max_iterations self.blackboard = None self.bb_toolkit = None if blackboard_schema is not None: from zhenxun.services.ai.run.blackboard import BlackboardManager from zhenxun.services.ai.tools.providers.builtin.blackboard import ( BlackboardToolkit, ) self.blackboard = BlackboardManager( schema=blackboard_schema, initial_state=initial_blackboard_state ) self.bb_toolkit = BlackboardToolkit(self.blackboard) self.leader_tools.append(self.bb_toolkit) async def generate_plan( self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs ) -> AsyncGenerator[TeamAction, Any]: from zhenxun.services.ai.flow.team.models import TaskBoardState, TaskNodeStatus from zhenxun.services.ai.flow.team.task_tools import TaskPlanningToolkit if self.blackboard is not None: context.session.blackboard = self.blackboard if self.bb_toolkit: for m in team.members: if not hasattr(m, "tool_definitions"): setattr(m, "tool_definitions", []) m_tools = getattr(m, "tool_definitions") if self.bb_toolkit not in m_tools: m_tools.append(self.bb_toolkit) member_infos = [] for m in team.members: desc = getattr(m, "description", "") or "处理节点" persona = getattr(m, "persona", None) if persona and not isinstance(persona, dict): desc = f"角色:{persona.role},目标:{persona.goal}" member_infos.append( f'\n' f" Description: {desc}\n" f"" ) members_xml = "\n" + "\n".join(member_infos) + "\n" final_instruction = self.get_prompt() + "\n\n" + members_xml task_toolkit = TaskPlanningToolkit(members=team.members) leader_tools = self.leader_tools.copy() leader_tools.append(task_toolkit) leader_agent = self._build_leader_agent( team=team, name=f"{team.name}_Planner", instruction=final_instruction, tools=leader_tools, ) logger.debug( f"📋 [TaskStrategy] '{team.name}' 正在启动 Engine-Driven 状态机循环..." ) if "__task_board__" not in context.session.shared_state: context.session.shared_state["__task_board__"] = ( self.blackboard._state if self.blackboard else TaskBoardState() ) board = cast(TaskBoardState, context.session.shared_state["__task_board__"]) max_iterations = self.max_iterations planner_prompt = prompt for iteration in range(max_iterations): if board.is_goal_complete: yield FinishAction(result=board.final_summary or "目标已标记完成。") return available_tasks = board.get_available_tasks() if not available_tasks: if iteration > 0: board_str = board.render_board_to_string() goal_str = getattr(prompt, "description", None) or ( str(prompt) if prompt else "" ) planner_prompt = f"""### 🎯 用户的终极目标 (Original Goal) {goal_str} ### 📋 当前看板最新状态 {board_str} **系统指令**:底层执行引擎的回合已结束。当前没有可立即执行的 pending 任务。 请检查是否有 failed 的任务需要修复重新指派?或者如果所有任务均已 completed, 请立刻调用 `mark_all_complete` 汇报总结。""" logger.info(f"🧠 [TaskStrategy] 唤醒 Planner (Iter: {iteration})") leader_res = yield CallAction(agent=leader_agent, task=planner_prompt) if board.is_goal_complete: yield FinishAction(result=board.final_summary or leader_res.output) return continue logger.info( f"🚀 [TaskStrategy] 引擎接管:并发执行 {len(available_tasks)} 个任务..." ) actions = [] valid_tasks = [] for task in available_tasks: member_agent = next( (m for m in team.members if m.name == task.assignee), None ) if not member_agent: board.update_task_status( task.id, TaskNodeStatus.failed, f"执行异常: 找不到名为 '{task.assignee}' 的专家。", ) continue board.update_task_status(task.id, TaskNodeStatus.in_progress) logger.debug(f" 🔄 [任务状态变更] `{task.title}` -> in_progress") task_prompt = f"### 🎯 你被指派的任务目标:\n{task.description}" if task.result: task_prompt += ( f"\n\n### 💡 项目经理的补充建议/历史反馈:\n{task.result}" ) if task.dependencies: dep_results = [] for dep_id in task.dependencies: dep_task = board.get_task(dep_id) if dep_task and dep_task.result: dep_results.append( f"【前置任务 [{dep_task.title}] 的产出】:\n" f"{dep_task.result}" ) if dep_results: task_prompt += ( "\n\n### 📦 你的任务依赖以下前置结果,请基于此进行处理:\n" + "\n\n".join(dep_results) ) if task.metadata: import json meta_str = json.dumps(task.metadata, ensure_ascii=False) task_prompt += f"\n\n### ⚙️ 附加系统元数据约束:\n{meta_str}" actions.append(CallAction(agent=member_agent.name, task=task_prompt)) valid_tasks.append(task) if not actions: continue results = yield ConcurrentCallAction(actions=actions) for task, (agent_name, agent_res) in zip(valid_tasks, results): if isinstance(agent_res, BaseException): output_str = f"❌ 专家框架级崩溃: {agent_res}" board.update_task_status(task.id, TaskNodeStatus.failed, output_str) final_status = "failed" else: output_str = str(agent_res.output) if output_str.startswith("Error:") or output_str.startswith("❌"): board.update_task_status( task.id, TaskNodeStatus.failed, output_str ) final_status = "failed" else: board.update_task_status( task.id, TaskNodeStatus.completed, output_str ) final_status = "completed" logger.debug(f" 🔄 [任务状态变更] `{task.title}` -> {final_status}") yield FinishAction( result=f"达到最大迭代次数 ({max_iterations}),任务未能在限定步数内完成。" )