mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-09 22:00:01 +08:00
* ✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 - 【重构】将原 services/llm 重构并迁移至全新的 services/ai 架构,提供向下兼容垫片 - 【新增】引入 Agent、Team、Workflow 三大智能体与工作流编排范式 - 【新增】引入基于 RAG 的长期向量记忆与中期槽位记忆系统 - 【新增】引入基于 Docker 的安全代码执行沙箱环境 - 【新增】支持 MCP 协议,允许动态管理和调用 MCP 服务 - 【新增】引入输入输出安全合规护栏与自愈反思机制 - 【优化】重构并优化多厂商 API 适配器 (Gemini, OpenAI, DeepSeek, GLM 等) - 【优化】优化日志脱敏与 Token 预估机制 - 【移除】移除旧版 llm default 和 llm reset-key 命令,新增 llm mcp 管理命令 * 🔧 chore(deps): 更新项目依赖与配置 - 添加 mcp、jieba 和 aiodocker 依赖到配置文件及 requirements.txt - 在 pyright 配置中设置 reportMissingImports 为 none - 调整 .gitignore 中 resources 目录的忽略规则 * ♻️ refactor(tools): 重构工具终止机制并清理知识库日志输出 - 统一使用 `context.state["__end_run__"]` 替代 `EndRunResult` 控制任务结束 - 移除文件系统和向量知识库检索工具中 `ToolResult` 的 `.with_log` 调用 - 调整指令处理器(Directive)的返回值为 `tool_res.output` - 修复部分类型检查警告并优化联合类型判断语法 * ♻️ refactor(tools): 重构工具副作用指令与控制流熔断机制 - 引入 `DirectivePayload` 及 `ToolResult` 的子类以结构化表达工具副作用 - 移除通过 `context.state` 传递魔术变量的隐式控制流设计 - 重构 `DirectiveManager` 处理器接口,直接在处理器中修改 `AgentState` 并构建 `AgentRunResult` - 在 `StandardAgentExecutor` 中统一通过 `directive_manager` 调度工具返回的副作用指令 - 补全 `MessageBuilder` 中部分核心方法的文档注释 * 🐛 fix(sandbox): 修复 Docker 沙箱容器状态检测与会话清理逻辑 -【修复】修正 `is_alive` 中直接读取私有属性的问题,改用 `show()` 返回值 -【修复】解决 `execute_code` 中缓存的执行器与当前会话不一致的问题 -【优化】在清理工作区前增加容器存活检测,避免向已死容器发送请求 -【优化】创建容器时增加运行状态校验,若已停止则自动从缓存中移除并重建 -【优化】优化容器销毁和清理逻辑,静默处理容器不存在 (404) 的异常 * 📝 docs(core): 补充核心模块初始化方法的文档注释 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
642 lines
26 KiB
Python
642 lines
26 KiB
Python
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 = """<how_to_respond>
|
||
你是一个多智能体团队的项目经理(Planner)。
|
||
请仔细阅读用户的请求,将其拆解为一个个具体的子任务,
|
||
并利用 `create_task` 建立所有任务和依赖关系(注意 `assignee` 必须严格从下方的团队成员中选择)。
|
||
【⚠️核心执行流规范】
|
||
1. 分配完毕后,**必须立刻停止调用任何工具,并直接输出纯文本回复**
|
||
(如:'任务已分配,等待执行'),从而结束你的当前回合。
|
||
2. 当底层自动执行完毕后,系统会再次唤醒你并提供最新的看板结果。
|
||
请根据结果决定是下发新任务、要求重做,还是调用 `mark_all_complete` 汇报总结。
|
||
</how_to_respond>""" # 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'<member id="{m.name}" name="{m.name}">\n'
|
||
f" Description: {desc}\n"
|
||
f"</member>"
|
||
)
|
||
|
||
members_xml = "<team_members>\n" + "\n".join(member_infos) + "\n</team_members>"
|
||
|
||
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}),任务未能在限定步数内完成。"
|
||
)
|