mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-21 20:20:05 +08:00
♻️ 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:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
80fc5b86a7
commit
0b32d69c9c
@@ -1,6 +1,7 @@
|
||||
from zhenxun.services.ai.core.exceptions import AbortException, ToolFatalError
|
||||
from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent
|
||||
from zhenxun.services.ai.run import Inject, RunContext
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.run.di import Inject
|
||||
from zhenxun.services.ai.tools.core.decorators import tool
|
||||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any, Literal
|
||||
import httpx
|
||||
|
||||
from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.tools.core.decorators import tool
|
||||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
import asyncio
|
||||
from typing import Any, Protocol
|
||||
from typing import Any
|
||||
|
||||
from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent, UserCustomEvent
|
||||
from zhenxun.services.ai.run import Inject, RunContext
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.run.di import Inject
|
||||
from zhenxun.services.ai.sandbox.models import (
|
||||
SandboxBlueprint,
|
||||
SandboxExecutionResult,
|
||||
)
|
||||
from zhenxun.services.ai.tools.core.decorators import silent, tool
|
||||
from zhenxun.services.ai.tools.core.decorators import Rules, tool
|
||||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.pydantic_compat import model_copy
|
||||
|
||||
|
||||
class PythonPluginProtocol(Protocol):
|
||||
@property
|
||||
def supports_state(self) -> bool: ...
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
code: str,
|
||||
timeout: int = 30,
|
||||
injected_code: str | None = None,
|
||||
) -> SandboxExecutionResult: ...
|
||||
|
||||
|
||||
class SandboxToolkit(BaseToolkit):
|
||||
default_prefix = ""
|
||||
|
||||
@@ -383,8 +371,8 @@ class SandboxToolkit(BaseToolkit):
|
||||
@tool(
|
||||
name="write_sandbox_file",
|
||||
description="将文本内容写入沙箱文件系统中,支持保存大块数据或配置,避免超过对话上下文。",
|
||||
rules=[Rules.silent()],
|
||||
)
|
||||
@silent()
|
||||
async def write_sandbox_file(
|
||||
self, path: str, content: str, context: RunContext, sandbox: Inject.Sandbox
|
||||
) -> ToolResult:
|
||||
@@ -405,8 +393,8 @@ class SandboxToolkit(BaseToolkit):
|
||||
@tool(
|
||||
name="read_sandbox_file",
|
||||
description="从沙箱文件系统中读取指定文件的文本内容。",
|
||||
rules=[Rules.silent()],
|
||||
)
|
||||
@silent()
|
||||
async def read_sandbox_file(
|
||||
self, path: str, context: RunContext, sandbox: Inject.Sandbox
|
||||
) -> ToolResult:
|
||||
|
||||
@@ -17,7 +17,7 @@ from pydantic import ValidationError
|
||||
|
||||
from zhenxun.services.ai.core.models import ToolDefinition
|
||||
from zhenxun.services.ai.core.stream_events import UserCustomEvent
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.sandbox.addons.base import BaseMcpProxyExtension
|
||||
from zhenxun.services.ai.sandbox.models import SandboxBlueprint
|
||||
from zhenxun.services.ai.tools.core.tool import BaseTool
|
||||
@@ -120,6 +120,16 @@ class MCPRemoteTool(BaseTool):
|
||||
parameters: dict,
|
||||
toolkit: "MCPToolkit",
|
||||
):
|
||||
"""
|
||||
初始化远端 MCP 工具在本地的代理对象。
|
||||
|
||||
参数:
|
||||
name: 工具在本地注册的唯一标识名称。
|
||||
original_tool_name: 该工具在远端 MCP 服务器上的原始名称。
|
||||
description: 工具的描述信息,用于指导大模型选用此工具。
|
||||
parameters: 工具参数的 JSON 模式描述。
|
||||
toolkit: 所属的 MCPToolkit 工具箱实例。
|
||||
"""
|
||||
super().__init__(name=name, description=description)
|
||||
self.original_tool_name = original_tool_name
|
||||
self.parameters = parameters
|
||||
@@ -132,7 +142,7 @@ class MCPRemoteTool(BaseTool):
|
||||
|
||||
@property
|
||||
def effective_ttl(self) -> float:
|
||||
"""动态获取当前最新的 TTL 配置,支持无缝热重载"""
|
||||
"""获取当前工具关联的有效生存期 TTL"""
|
||||
try:
|
||||
from zhenxun.services.ai.config import get_llm_config
|
||||
|
||||
@@ -272,6 +282,28 @@ class MCPToolkit(BaseToolkit):
|
||||
sandbox_session_id: str | None = None,
|
||||
sandbox_blueprint: SandboxBlueprint | None = None,
|
||||
):
|
||||
"""
|
||||
初始化模型上下文协议 (MCP) 的工具箱封装。
|
||||
|
||||
参数:
|
||||
server_name: MCP 服务器的唯一标识名称。
|
||||
prefix: 工具名称的前缀,防止工具命名冲突。
|
||||
transport: 连接 MCP 服务器的通信传输协议,可选 "stdio", "sse", "streamable-http", "sandbox_proxy"。
|
||||
command: 用于 stdio 或 sandbox_proxy 模式启动服务器的可执行命令。
|
||||
args: 启动服务器时附加的命令行参数。
|
||||
url: 用于 sse 或 streamable-http 模式的服务器连接 URL。
|
||||
env: 启动服务器时的进程环境变量。
|
||||
cwd: 启动服务器时的进程工作目录。
|
||||
install_command: 首次启动前执行的环境热装配/依赖安装命令。
|
||||
isolation: 环境隔离模式,可选 "shared" (共享模式) 或 "per_session" (每个会话独立)。
|
||||
timeout: 初始化和请求网络接口时的超时秒数,默认 30。
|
||||
admin_level: 调用工具所需的管理权限等级,默认 0 (无限制)。
|
||||
header_provider: 动态生成 HTTP 头部信息的工厂函数。
|
||||
env_provider: 动态生成进程环境变量的工厂函数。
|
||||
ttl: 闲置清理的生存时间 (秒),默认 600。
|
||||
sandbox_session_id: 当 transport 为 sandbox_proxy 时指定的沙箱会话 ID。
|
||||
sandbox_blueprint: 用于沙箱自动装配的环境蓝图配置。
|
||||
""" # noqa: E501
|
||||
super().__init__(
|
||||
config=ToolkitConfig(prefix=prefix) if prefix is not None else None
|
||||
)
|
||||
@@ -304,7 +336,7 @@ class MCPToolkit(BaseToolkit):
|
||||
|
||||
@property
|
||||
def effective_ttl(self) -> float:
|
||||
"""动态获取当前最新的 TTL 配置,支持无缝热重载"""
|
||||
"""获取当前工具箱的有效闲置生存期 TTL"""
|
||||
try:
|
||||
from zhenxun.services.ai.config import get_llm_config
|
||||
|
||||
@@ -314,7 +346,7 @@ class MCPToolkit(BaseToolkit):
|
||||
return 31536000.0 if self.ttl <= 0 else float(self.ttl)
|
||||
|
||||
async def _run_install_command(self, force: bool = False):
|
||||
"""执行环境预安装(热加载依赖)"""
|
||||
"""在本地运行预热依赖安装命令以准备 MCP 服务器环境"""
|
||||
if not self.install_command or not self.cwd:
|
||||
return
|
||||
|
||||
@@ -366,6 +398,7 @@ class MCPToolkit(BaseToolkit):
|
||||
async def get_session(
|
||||
self, context: RunContext | None = None
|
||||
) -> ClientSession | None:
|
||||
"""获取或建立与 MCP 服务器的会话连接,并刷新其生存状态"""
|
||||
await self.lifespan_manager.register(
|
||||
self.server_name,
|
||||
ttl=self.effective_ttl,
|
||||
@@ -376,8 +409,7 @@ class MCPToolkit(BaseToolkit):
|
||||
return self._shared_session
|
||||
|
||||
async def _spawn_session_task(self, dynamic_headers: dict, dynamic_env: dict):
|
||||
"""生成后台连接任务"""
|
||||
|
||||
"""在后台异步拉起 MCP 传输协议通道并进行服务初始化"""
|
||||
max_attempts = 2 if (self.transport == "stdio" and self.install_command) else 1
|
||||
|
||||
try:
|
||||
@@ -546,6 +578,7 @@ class MCPToolkit(BaseToolkit):
|
||||
self._ready_event.set()
|
||||
|
||||
async def initialize(self, context: RunContext | None = None):
|
||||
"""清空当前工具集并异步执行 MCP 服务端的全套初始化流程"""
|
||||
if self._is_initialized:
|
||||
return
|
||||
|
||||
@@ -584,6 +617,7 @@ class MCPToolkit(BaseToolkit):
|
||||
await self.close()
|
||||
|
||||
async def close(self):
|
||||
"""彻底关闭 MCP 工具箱,注销生存期并释放底层 WebSocket/进程通道"""
|
||||
current_task = asyncio.current_task()
|
||||
|
||||
if (
|
||||
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from zhenxun.services.ai.capabilities import AbstractCapability
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.tools.providers.skills.manager import skill_manager
|
||||
from zhenxun.services.ai.tools.providers.skills.models import Skill, SkillSource
|
||||
from zhenxun.services.ai.tools.providers.skills.toolkit import SkillMetaToolkit
|
||||
@@ -18,6 +18,13 @@ class SkillCapability(AbstractCapability):
|
||||
skills: Sequence[str | Path | Skill | SkillSource] | None = None,
|
||||
namespace: str | None = None,
|
||||
):
|
||||
"""
|
||||
初始化技能库挂载能力组件。
|
||||
|
||||
参数:
|
||||
skills: 需要挂载到该环境下的技能列表,支持名称、路径、Skill 实例或 SkillSource。
|
||||
namespace: 该技能库所处的作用域命名空间,若不指定则自动推导。
|
||||
""" # noqa: E501
|
||||
self.skills = skills or []
|
||||
self.namespace = namespace or infer_plugin_namespace()
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from typing import Any, cast
|
||||
|
||||
from zhenxun.services.ai.run import Inject, RunContext
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.run.di import Inject
|
||||
from zhenxun.services.ai.sandbox.models import SandboxBlueprint
|
||||
from zhenxun.services.ai.sandbox.protocols import (
|
||||
SupportsCommandExecution,
|
||||
SupportsFileSystem,
|
||||
)
|
||||
from zhenxun.services.ai.tools.core.decorators import silent, tool
|
||||
from zhenxun.services.ai.tools.core.decorators import Rules, tool
|
||||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||||
from zhenxun.services.ai.tools.models import ResolvedToolPayload, ToolResult
|
||||
from zhenxun.services.ai.tools.providers.skills.manager import (
|
||||
@@ -211,8 +212,8 @@ class SkillMetaToolkit(BaseToolkit, SkillSandboxExecutionMixin):
|
||||
@tool(
|
||||
name="read_skill_instructions",
|
||||
description="加载指定技能的完整使用说明与可用资源清单。返回值为 XML 结构。",
|
||||
rules=[Rules.silent()],
|
||||
)
|
||||
@silent()
|
||||
async def read_skill_instructions(self, skill_name: str) -> ToolResult:
|
||||
skill = await self._get_skill(skill_name)
|
||||
if not skill:
|
||||
@@ -261,8 +262,8 @@ class SkillMetaToolkit(BaseToolkit, SkillSandboxExecutionMixin):
|
||||
"安全读取指定技能目录下的附加文件"
|
||||
"(如 references/ 里的参考文档或 scripts/ 里的代码)。"
|
||||
),
|
||||
rules=[Rules.silent()],
|
||||
)
|
||||
@silent()
|
||||
async def read_skill_file(
|
||||
self,
|
||||
skill_name: str,
|
||||
|
||||
Reference in New Issue
Block a user