♻️ refactor(tools): 重构工具装饰器系统并优化沙箱与文档注释 (#2147)

* ♻️ refactor(ai): 重构 AI 服务模块并完善文档注释

- 【重构】统一清理并优化所有 AI 服务模块文件的导入语句,将其移至文件顶部
- 【重构】重构 `hooks.py` 中的 `Hooks` 派发逻辑,使用通用管道函数消除重复代码,并引入 `HookPoint` 描述符
- 【重构】重构工具装饰器实现,新增 `toolkit` 类装饰器,优化 `BaseToolkit` 配置合并与前缀处理
- 【功能】Docker 沙箱容器创建时支持自动注入系统代理环境变量并配置 `ExtraHosts`
- 【功能】Jupyter 服务启动前自动清理旧进程并初始化临时目录权限
- 【修复】优化 Pydantic 结构化输出校验失败时的错误信息提取,提供更详细的字段级错误反馈
- 【修复】在 `api.py` 中避免将 `ModelRetry` 和 `ControlFlowExit` 异常错误地包装为 `LLMException`
- 【文档】为 AI 服务、沙箱、工具链、工作流等核心模块补充完整的 Docstring 和类型注释

* 📝 docs(ai): 补全核心模块文档注释并清理冗余代码

- 补全 `run/context`、`run/hooks` 和 `tools/engine/registry` 中类与方法的中文文档注释
- 清理 `tools/providers/builtin/sandbox` 中未使用的 `PythonPluginProtocol` 协议及相关导入
- 规范化部分代码的格式与尾随逗号

* ♻️ refactor!(flow): 重构 Task 为 AgentTask 并优化工作流元数据定义

- 【Breaking Change】将 `Task` 重命名为 `AgentTask` 以避免命名冲突
- 更新 Agent、Team、Workflow 等模块中的类型声明与相关逻辑
- 引入 `AutoNodeMeta` 强类型元数据,替换工作流装饰器中的裸字典定义
- 将 `StepMeta`、`ConditionMeta` 和 `RouterMeta` 统一移动至 `types.py`
- 优化 `RunnableNode` 对上游 `AgentTask` 的处理与拼接逻辑
- 调整团队协作策略中 `FinishAction` 的返回值为完整结果对象

* ♻️ refactor(workflow): 移除人工确认机制并重构错误策略

- 移除工作流节点的人工确认(HITL)与挂起继续机制
- 删除 `auto` 自动化工作流及相关装饰器文件
- 将错误处理策略类从 `types.py` 拆分并移动到新文件 `policies.py`
- 优化节点执行失败时的异常信息格式化输出
- 移除 `WorkflowRunResult` 和 `StepOutput` 中与挂起相关的状态字段

* 🚨 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>
This commit is contained in:
Rumio
2026-07-05 11:47:21 +08:00
committed by GitHub
co-authored by webjoin111 pre-commit-ci[bot]
parent 80fc5b86a7
commit 0b32d69c9c
61 changed files with 1659 additions and 1511 deletions
+15 -1
View File
@@ -78,6 +78,7 @@ class ConcurrentCallAction(TeamAction):
"""
actions: list[CallAction]
"""并发执行的呼叫动作列表"""
class FinishAction(TeamAction):
@@ -108,13 +109,21 @@ class SubTaskRecord(BaseModel):
"""单条子任务(工单)数据契约"""
id: str = Field(default_factory=lambda: str(uuid.uuid4())[:8])
"""子任务的唯一标识 ID"""
title: str = ""
"""子任务标题"""
description: str = ""
"""子任务的具体描述"""
assignee: str | None = None
"""执行该任务的指派成员 Agent 名称"""
dependencies: list[str] = Field(default_factory=list)
"""该任务依赖的前置任务 ID 列表"""
status: TaskNodeStatus = TaskNodeStatus.pending
"""任务的当前执行状态"""
result: str | None = None
"""任务执行结果或输出信息"""
notes: list[str] = Field(default_factory=list)
"""执行过程中的追加备注或错误记录"""
metadata: dict[str, Any] = Field(default_factory=dict)
"""附加元数据,供系统底层或第三方插件挂载隐式上下文,对大模型不可见"""
@@ -126,8 +135,11 @@ class TaskBoardState(BaseModel):
"""
tasks: list[SubTaskRecord] = Field(default_factory=list)
"""看板中存储的所有子任务记录列表"""
is_goal_complete: bool = False
"""团队的终极目标是否已宣告完成"""
final_summary: str | None = None
"""目标完成后的终结总结报告"""
def create_task(
self,
@@ -151,6 +163,7 @@ class TaskBoardState(BaseModel):
return task
def get_task(self, task_id: str) -> SubTaskRecord | None:
"""根据 ID 或标题获取对应的子任务记录"""
return next(
(t for t in self.tasks if t.id == task_id or t.title == task_id), None
)
@@ -158,6 +171,7 @@ class TaskBoardState(BaseModel):
def update_task_status(
self, task_id: str, status: TaskNodeStatus, result: str | None = None
) -> SubTaskRecord | None:
"""更新指定任务的状态以及执行结果"""
task = self.get_task(task_id)
if not task:
return None
@@ -221,7 +235,7 @@ class TaskBoardState(BaseModel):
return available
def all_terminal(self) -> bool:
"""判断是否所有的任务都已经进入了终结状态(完成或失败)"""
"""检查是否所有的任务均已进入终结状态(已完成或已失败)"""
if not self.tasks:
return False
return all(
+9 -8
View File
@@ -9,7 +9,7 @@ from nonebot.utils import is_coroutine_callable
from zhenxun.services.ai.core.messages import AgentMessage
from zhenxun.services.ai.core.templates import PromptTemplate
from zhenxun.services.ai.flow.team.models import RouteDecision, Transition
from zhenxun.services.ai.run import RunContext, Task
from zhenxun.services.ai.run import AgentTask, RunContext
from zhenxun.services.ai.run.di import DependencyInjector
from zhenxun.services.log import logger
@@ -22,7 +22,7 @@ class BaseRouter(ABC):
self,
context: RunContext,
history: Sequence[AgentMessage],
prompt: str | Task | None = None,
prompt: str | AgentTask | None = None,
) -> RouteDecision | None:
"""核心路由方法"""
pass
@@ -46,11 +46,12 @@ class FunctionRouter(BaseRouter):
self,
context: RunContext,
history: Sequence[AgentMessage],
prompt: str | Task | None = None,
prompt: str | AgentTask | None = None,
) -> RouteDecision | None:
sig = inspect.signature(self.selector_func)
call_kwargs = {"prompt": prompt, "context": context, "history": history}
if isinstance(prompt, Task):
if isinstance(prompt, AgentTask):
call_kwargs["agent_task"] = prompt
call_kwargs["task"] = prompt
kwargs_resolved = await DependencyInjector.resolve_all(
@@ -95,11 +96,11 @@ class RegexRouter(BaseRouter):
self,
context: RunContext,
history: Sequence[AgentMessage],
prompt: str | Task | None = None,
prompt: str | AgentTask | None = None,
) -> RouteDecision | None:
text_to_match = (
prompt.description
if isinstance(prompt, Task)
if isinstance(prompt, AgentTask)
else (prompt or context.run.user_input or "")
)
@@ -125,7 +126,7 @@ class ChainRouter(BaseRouter):
self,
context: RunContext,
history: Sequence[AgentMessage],
prompt: str | Task | None = None,
prompt: str | AgentTask | None = None,
) -> RouteDecision | None:
for router in self.routers:
decision = await router.route(context, history, prompt)
@@ -174,7 +175,7 @@ class LLMRouter(BaseRouter):
self,
context: RunContext,
history: Sequence[AgentMessage],
prompt: str | Task | None = None,
prompt: str | AgentTask | None = None,
) -> RouteDecision | None:
from zhenxun.services.ai.flow.agent.agent import Agent
from zhenxun.services.ai.flow.agent.models import AgentConfig
+4 -1
View File
@@ -18,6 +18,7 @@ from zhenxun.services.ai.flow.team.strategy import BaseTeamStrategy
from zhenxun.services.ai.run import AgentRunResult, RunContext
from zhenxun.services.ai.run.models import AgentRunEnd
from zhenxun.services.log import logger
from zhenxun.utils.pydantic_compat import model_construct
class TeamRunner:
@@ -228,7 +229,9 @@ class TeamRunner:
logger.info(f"🏁 **团队 [{self.team.name}]** 协作圆满结束!")
if not isinstance(final_result, AgentRunResult):
final_result = AgentRunResult(output=final_result, usage=cumulative_usage)
final_result = model_construct(
AgentRunResult, output=final_result, usage=cumulative_usage
)
else:
final_result.usage += cumulative_usage
+34 -10
View File
@@ -17,7 +17,7 @@ from zhenxun.services.ai.flow.team.models import (
TeamAction,
)
from zhenxun.services.ai.flow.team.router import BaseRouter
from zhenxun.services.ai.run import RunContext, Task
from zhenxun.services.ai.run import AgentTask, RunContext
from zhenxun.services.ai.tools.bridges.delegate import DelegateTool
from zhenxun.services.log import logger
@@ -44,7 +44,11 @@ class BaseTeamStrategy(ABC):
return PromptTemplate(template).render(**kwargs)
async def generate_plan(
self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs
self,
team: "Team",
prompt: str | AgentTask | None,
context: RunContext,
**kwargs,
) -> AsyncGenerator[TeamAction, Any]:
"""
核心决策生成器 (Action Yielding Pattern)。
@@ -137,7 +141,11 @@ class RouteStrategy(BaseTeamStrategy):
self.state_flow = state_flow
async def generate_plan(
self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs
self,
team: "Team",
prompt: str | AgentTask | None,
context: RunContext,
**kwargs,
) -> AsyncGenerator[TeamAction, Any]:
router = self.router
if not router:
@@ -266,7 +274,7 @@ class RouteStrategy(BaseTeamStrategy):
)
continue
yield FinishAction(result=run_result.output)
yield FinishAction(result=run_result)
break
@@ -297,7 +305,11 @@ class CoordinateStrategy(BaseTeamStrategy):
self.leader_tools = leader_tools or []
async def generate_plan(
self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs
self,
team: "Team",
prompt: str | AgentTask | None,
context: RunContext,
**kwargs,
) -> AsyncGenerator[TeamAction, Any]:
delegation_tools = []
for m in team.members:
@@ -338,7 +350,7 @@ class CoordinateStrategy(BaseTeamStrategy):
leader_res = yield CallAction(agent=leader_agent, task=prompt)
yield FinishAction(result=leader_res.output)
yield FinishAction(result=leader_res)
class BroadcastStrategy(BaseTeamStrategy):
@@ -367,10 +379,14 @@ class BroadcastStrategy(BaseTeamStrategy):
self.leader_tools = leader_tools or []
async def generate_plan(
self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs
self,
team: "Team",
prompt: str | AgentTask | None,
context: RunContext,
**kwargs,
) -> AsyncGenerator[TeamAction, Any]:
task_desc_str = (
prompt.description if isinstance(prompt, Task) else (prompt or "")
prompt.description if isinstance(prompt, AgentTask) else (prompt or "")
)
if context.run.event_bus:
@@ -413,7 +429,7 @@ class BroadcastStrategy(BaseTeamStrategy):
leader_res = yield CallAction(agent=leader_agent, task=synthesize_prompt)
yield FinishAction(result=leader_res.output)
yield FinishAction(result=leader_res)
class TaskStrategy(BaseTeamStrategy):
@@ -471,7 +487,11 @@ class TaskStrategy(BaseTeamStrategy):
self.leader_tools.append(self.bb_toolkit)
async def generate_plan(
self, team: "Team", prompt: str | Task | None, context: RunContext, **kwargs
self,
team: "Team",
prompt: str | AgentTask | 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
@@ -542,6 +562,10 @@ class TaskStrategy(BaseTeamStrategy):
goal_str = getattr(prompt, "description", None) or (
str(prompt) if prompt else ""
)
expected_out = getattr(prompt, "expected_output", None)
if expected_out:
goal_str += f"\n\n### 🎯 [预期产出要求]\n{expected_out}"
planner_prompt = f"""### 🎯 用户的终极目标 (Original Goal)
{goal_str}
+4 -4
View File
@@ -19,7 +19,7 @@ from zhenxun.services.ai.flow.team.router import BaseRouter
from zhenxun.services.ai.flow.team.strategy import (
BaseTeamStrategy,
)
from zhenxun.services.ai.run import AgentRunResult, RunContext, Task
from zhenxun.services.ai.run import AgentRunResult, AgentTask, RunContext
from zhenxun.services.ai.tools.providers.skills.models import Skill, SkillSource
from zhenxun.utils.utils import infer_plugin_namespace
@@ -239,7 +239,7 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
async def run(
self,
prompt: PromptInput | Task | None = None,
prompt: PromptInput | AgentTask | None = None,
*,
context: "RunContext | None" = None,
capabilities: list[CapabilitySource] | None = None,
@@ -250,7 +250,7 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
团队级运行阻塞核心入口,内部静默分配任务给成员直至汇总结束。
参数:
prompt: 派发给多智能体团队的任务描述 or 契约对象 (Task)。
prompt: 派发给多智能体团队的任务描述 or 契约对象 (AgentTask)。
context: 显式传入的会话与运行上下文。
capabilities: 仅针对本次团队执行动态注入的临时拦截器列表。
kwargs: 透传的其他附加参数。
@@ -278,7 +278,7 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
@contextlib.asynccontextmanager
async def run_stream(
self,
prompt: PromptInput | Task | None = None,
prompt: PromptInput | AgentTask | None = None,
*,
context: "RunContext | None" = None,
capabilities: list[CapabilitySource] | None = None,