♻️ 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
+3 -3
View File
@@ -1,7 +1,7 @@
import nonebot
from .bridges.matcher_bridge import MatcherTool, bind_matcher
from .core.decorators import Rules, tool
from .bridges.matcher_bridge import bind_matcher
from .core.decorators import Rules, tool, toolkit
from .core.toolkit import BaseToolkit
from .engine.registry import tool_provider_manager
from .models import (
@@ -22,11 +22,11 @@ async def _shutdown_mcp_provider():
__all__ = [
"BaseToolkit",
"MatcherTool",
"Native",
"Rules",
"ToolOptions",
"ToolResult",
"bind_matcher",
"tool",
"toolkit",
]
@@ -41,6 +41,14 @@ class DelegateTool(BaseTool):
name: str | None = None,
description: str | None = None,
):
"""
初始化委派工具,将可运行实体包装为子例程工具。
参数:
runnable: 被包装的可运行实体,可以是 Agent、Team 或 Workflow 等。
name: 自定义工具名称,若为 None 则默认推导为 runnable 实体名。
description: 工具描述信息,用于指导大模型何时代用此工具。
"""
resolved_name = name or getattr(runnable, "name", "SubRunnable")
resolved_desc = description or getattr(
runnable, "description", f"将子任务委派给 {resolved_name} 执行"
+9 -1
View File
@@ -2,7 +2,7 @@ from typing import Any
from pydantic import BaseModel, Field, create_model
from zhenxun.services.ai.run import RunContext
from zhenxun.services.ai.run.context import RunContext
from zhenxun.services.ai.tools.core.tool import BaseTool
from zhenxun.services.ai.tools.models import HandoffResult, ToolResult
@@ -19,6 +19,14 @@ class HandoffTool(BaseTool):
target_description: str,
input_schema: type[BaseModel] | Any | None = None,
):
"""
初始化移交工具,为模型赋予转移对话控制权到指定实体的能力。
参数:
target_name: 被转移的目标接收者(Agent 或负责人)的唯一标识名称。
target_description: 目标接收者的职责或专长说明,供模型决策是否移交。
input_schema: 自定义移交数据结构,指定转移时所需携带的结构化参数。
"""
super().__init__(
name=f"transfer_to_{target_name}",
description=(
@@ -20,7 +20,7 @@ from pydantic import BaseModel
from zhenxun.services.ai.core.exceptions import ToolRetryError
from zhenxun.services.ai.core.models import ToolDefinition
from zhenxun.services.ai.run import RunContext
from zhenxun.services.ai.run.context import RunContext
from zhenxun.services.ai.tools.core.schema import build_schema_hint
from zhenxun.services.ai.tools.core.tool import BaseTool
from zhenxun.services.ai.tools.models import EndRunResult, ToolOptions, ToolResult
@@ -37,6 +37,14 @@ class MatcherAdapter(ABC):
args_schema: type[BaseModel] | None,
command_formatter: Callable[[Any], list[Any] | str] | None = None,
):
"""
初始化 Matcher 桥接适配器。
参数:
matcher: 目标 Matcher 类,负责实际接收 Fake State 并执行命令。
args_schema: 描述 LLM 需要填写的参数 Pydantic Schema,可以为 None。
command_formatter: 可选的自定义命令行格式化器,将参数序列化为命令行参数。
"""
self.matcher = matcher
self.args_schema = args_schema
self.command_formatter = command_formatter
@@ -314,6 +322,17 @@ class MatcherTool(BaseTool):
args_schema: type[BaseModel] | None,
terminal: bool = True,
):
"""
初始化通用状态机穿透工具。
参数:
matcher: 目标 Matcher 类,由 nonebot 装饰器创建的指令匹配器。
adapter: 对应的 MatcherAdapter 桥接适配器,用于构建 Fake State。
name: 工具的唯一标识名称(仅限英文字母、数字及下划线)。
description: 工具的职责说明,指导大模型选用此工具。
args_schema: 描述 LLM 需要填写的参数 Pydantic Schema,可以为 None。
terminal: 是否为终端工具,若为 True 则执行完毕后立即中断大模型循环,默认 True。
""" # noqa: E501
self.matcher = matcher
self.adapter = adapter
self.terminal = terminal
+2 -1
View File
@@ -1,4 +1,4 @@
from .decorators import Rules, tool
from .decorators import Rules, tool, toolkit
from .schema import (
FieldPermission,
RequireAdminLevel,
@@ -20,4 +20,5 @@ __all__ = [
"RequireSuperUser",
"Rules",
"tool",
"toolkit",
]
+9 -15
View File
@@ -1,16 +1,24 @@
import asyncio
from collections.abc import Callable
import json
from typing import Any
from aiocache import SimpleMemoryCache
from zhenxun.configs.config import Config
from zhenxun.services.ai.capabilities import (
AbstractCapability,
WrapToolExecuteHandler,
WrapToolValidateHandler,
)
from zhenxun.services.ai.run import RunContext
from zhenxun.services.ai.core.exceptions import (
NeedsInputException,
ToolFatalError,
ToolRetryError,
)
from zhenxun.services.ai.run.context import RunContext
from zhenxun.services.ai.run.di import DependencyInjector
from zhenxun.services.ai.run.hitl import HITLController
from zhenxun.services.ai.tools.models import ToolResult
from zhenxun.services.ai.utils import PermissionUtils
from zhenxun.services.log import logger
@@ -99,8 +107,6 @@ class ApprovalCapability(AbstractCapability):
if tool is None:
return await handler(arguments)
import json
args_str = json.dumps(arguments, ensure_ascii=False, indent=2)
confirm_msg = f"即将在本地执行高危工具 [{tool_name}]\n参数:\n{args_str}"
@@ -110,8 +116,6 @@ class ApprovalCapability(AbstractCapability):
await hitl_lock.acquire()
try:
from zhenxun.services.ai.run.hitl import HITLController
hitl = HITLController(context)
await hitl.ask_confirm(f"⚠️ **安全交互审批**\n\n{confirm_msg}", timeout=60.0)
logger.info(f"🛡️ [HITL] 工具 {tool_name} 审批通过。")
@@ -275,8 +279,6 @@ class ConfigDependencyCapability(AbstractCapability):
async def prepare_tools(
self, context: RunContext, tool_defs: list[Any]
) -> list[Any]:
from zhenxun.configs.config import Config
if Config.get_config(self.module, self.key) == self.expected_value:
return tool_defs
return []
@@ -292,12 +294,6 @@ class InteractiveCapability(AbstractCapability):
args: str | dict[str, Any],
handler: WrapToolValidateHandler,
) -> dict[str, Any]:
from zhenxun.services.ai.core.exceptions import (
NeedsInputException,
ToolFatalError,
ToolRetryError,
)
tool = context.call.current_tool
current_kwargs = dict(args) if isinstance(args, dict) else args
@@ -322,8 +318,6 @@ class InteractiveCapability(AbstractCapability):
"请发送文本补充,或回复“取消”中止。"
)
try:
from zhenxun.services.ai.run.hitl import HITLController
hitl = HITLController(context)
user_input = await hitl.ask_text(prompt_msg, timeout=60.0)
except ToolFatalError:
+140 -162
View File
@@ -4,53 +4,99 @@ from collections.abc import Callable
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.core.capabilities import (
AdminLevelCapability,
ApprovalCapability,
CacheCapability,
FallbackCapability,
GroupOnlyCapability,
InteractiveCapability,
LifecycleCapability,
SuperuserCapability,
)
from zhenxun.services.ai.tools.models import ToolOptions
from zhenxun.utils.pydantic_compat import model_copy
def _update_settings(func: Callable, **kwargs) -> Callable:
"""叠加协议底层:创建或更新函数的 __tool_settings__"""
if not hasattr(func, "__tool_settings__"):
setattr(func, "__tool_settings__", ToolOptions())
settings: ToolOptions = getattr(func, "__tool_settings__")
for k, v in kwargs.items():
if k == "capabilities":
settings.capabilities.extend(v)
elif k == "metadata":
settings.metadata.update(v)
def toolkit(
rules: list[ToolOptions] | ToolOptions | None = None,
prefix: str = "",
instructions: str | None = None,
):
"""
类级别的工具箱装饰器,用于向内部所有 @tool 统一下发配置规则、名称前缀以及工具箱级系统提示词说明。
参数:
rules: 声明式规则集合或单个规则,应用于工具箱内所有工具。
prefix: 工具箱内所有工具的名称前缀,通常以下划线结尾。
instructions: 工具箱级别的系统提示词补充说明,大模型可见。
返回:
Callable: 装饰器函数,接收一个类并返回该类。
""" # noqa: E501
def decorator(cls):
merged_options = ToolOptions()
if rules:
rule_list = rules if isinstance(rules, list) else [rules]
for r in rule_list:
if isinstance(r, ToolOptions):
merged_options = merged_options.merge(r)
from zhenxun.services.ai.tools.models import ToolkitConfig
base_config = getattr(cls, "_default_config", None) or ToolkitConfig()
new_config = model_copy(base_config, deep=True)
if prefix:
new_config.prefix = prefix
if new_config.shared_options:
new_config.shared_options = new_config.shared_options.merge(merged_options)
else:
setattr(settings, k, v)
return func
new_config.shared_options = merged_options
cls._default_config = new_config
if instructions is not None:
cls.default_instructions = instructions
return cls
return decorator
def require_sandbox(
python_packages: list[str] | None = None,
node_packages: list[str] | None = None,
system_packages: list[str] | None = None,
):
) -> ToolOptions:
"""显式声明该工具所需安装的沙箱环境依赖"""
def decorator(func):
setattr(
func,
"__sandbox_requirements__",
{
"python": python_packages or [],
"node": node_packages or [],
"system": system_packages or [],
},
)
return func
return decorator
return ToolOptions(
sandbox_requirements={
"python": python_packages or [],
"node": node_packages or [],
"system": system_packages or [],
}
)
class ToolkitMethodDescriptor:
"""用于 Toolkit 类方法的描述符,确保在实例化时绑定正确的 self 并生成 FunctionTool"""
def __init__(
self, func: Callable, name: str, description: str, settings: ToolOptions
self, func: Callable, name: str, description: str | None, settings: ToolOptions
):
"""
初始化 Toolkit 类方法描述符。
参数:
func: 底层的 Python 可执行函数对象。
name: 大模型识别与调用的工具名。
description: 大模型阅读的工具功能描述说明。
settings: 工具的声明式高阶配置项 (ToolOptions)。
"""
self.func = func
self.name = name
self.description = description
@@ -64,7 +110,6 @@ class ToolkitMethodDescriptor:
import types
from zhenxun.services.ai.tools.core.tool import FunctionTool
from zhenxun.utils.pydantic_compat import model_copy
bound_func = types.MethodType(self.func, instance)
return FunctionTool(
@@ -82,6 +127,7 @@ class ToolkitMethodDescriptor:
def tool(
name: str | None = None,
description: str | None = None,
rules: list[ToolOptions] | ToolOptions | None = None,
settings: ToolOptions | None = None,
tags: list[str] | None = None,
auto_register: bool = False,
@@ -91,23 +137,26 @@ def tool(
将普通函数或类方法注册为 LLM 工具的统一装饰器大一统。
参数:
name: 工具的名称(英文字母及下划线)。
大模型将看到此名称。如果为空则默认使用函数名。
description: 工具描述。
大模型将基于此决定何时、如何使用该工具。如果为空,将读取函数 docstring。
settings: 工具的高阶配置对象 (ToolOptions)。
用于控制极速缓存、人工审批拦截、静默执行等扩展能力。
tags: 工具的标签列表。
用于被 Agent 的智能字符串路由识别并进行能力注入。
auto_register: 是否自动注册到当前命名空间的工具注册表。
默认为 False,开发者需要显式传递实例。若为 True 方可通过字符串调用。
require_prefix: 是否自动添加插件命名空间前缀(仅对游离函数生效)。默认为 False。
name: 工具的名称(英文字母及下划线),大模型将看到此名称,如果为空则默认使用函数名。
description: 工具描述,大模型将基于此决定何时、如何使用该工具,如果为空,将读取函数 docstring。
rules: 声明式规则集合预设列表,用于接收通过 Rules.xxx() 生成的策略载荷并组合。
settings: 工具的高阶配置对象 (ToolOptions),用于控制极速缓存、人工审批拦截、静默执行等扩展能力。
tags: 工具的标签列表,用于被 Agent 的智能字符串路由识别并进行能力注入。
auto_register: 是否自动注册到当前命名空间的工具注册表,默认为 False,若为 True 方可通过字符串调用。
require_prefix: 是否自动添加插件命名空间前缀(仅对游离函数生效),默认为 False。
返回:
Callable | FunctionTool: 包装后的函数或方法。
"""
""" # noqa: E501
def decorator(func: Callable):
base_settings = settings or ToolOptions()
if rules:
rule_list = rules if isinstance(rules, list) else [rules]
for r in rule_list:
if isinstance(r, ToolOptions):
base_settings = base_settings.merge(r)
if tags:
base_settings.tags = list(set(base_settings.tags + tags))
existing_settings = getattr(func, "__tool_settings__", None)
@@ -116,10 +165,13 @@ def tool(
reqs = getattr(func, "__sandbox_requirements__", None)
if reqs:
base_settings.sandbox_requirements = reqs
if not base_settings.sandbox_requirements:
base_settings.sandbox_requirements = reqs
else:
base_settings.sandbox_requirements.update(reqs)
tool_name = name or func.__name__
tool_desc = description or func.__doc__ or "未提供描述"
tool_desc = description
import inspect
@@ -172,30 +224,19 @@ def tool(
return decorator
def with_cache(ttl: int = 3600, cache_function: Callable | None = None):
def with_cache(ttl: int = 3600, cache_function: Callable | None = None) -> ToolOptions:
"""开启极速缓存,阻止参数相同的重复请求发往底层。"""
from zhenxun.services.ai.tools.core.capabilities import CacheCapability
def decorator(func: Callable):
return _update_settings(
func,
capabilities=[CacheCapability(ttl=ttl, cache_function=cache_function)],
)
return decorator
return ToolOptions(
capabilities=[CacheCapability(ttl=ttl, cache_function=cache_function)]
)
def silent():
def silent() -> ToolOptions:
"""静默执行。工具执行过程与结果不会作为界面流渲染给用户,仅作大模型内部参考。"""
def decorator(func: Callable):
return _update_settings(func, silent=True)
return decorator
return ToolOptions(silent=True)
def direct_reply():
def direct_reply() -> ToolOptions:
"""直出模式。工具执行完毕后强制中断大模型的思考循环,将工具输出作为最终回答返回。"""
class DirectReplyCapability(AbstractCapability):
@@ -209,139 +250,70 @@ def direct_reply():
return EndRunResult(output=result.output)
return EndRunResult(output=result)
def decorator(func: Callable):
return _update_settings(func, capabilities=[DirectReplyCapability()])
return decorator
return ToolOptions(capabilities=[DirectReplyCapability()])
def interactive():
def interactive() -> ToolOptions:
"""开启交互式参数补全。如果参数缺失或校验失败,
会主动通过 Bot 向用户提问要求补全。"""
from zhenxun.services.ai.tools.core.capabilities import InteractiveCapability
def decorator(func: Callable):
return _update_settings(func, capabilities=[InteractiveCapability()])
return decorator
return ToolOptions(capabilities=[InteractiveCapability()])
def require_approval():
def require_approval() -> ToolOptions:
"""高危操作标记。调用前将拦截并发送至群组要求超管人工审核。"""
from zhenxun.services.ai.tools.core.capabilities import ApprovalCapability
def decorator(func: Callable):
return _update_settings(func, capabilities=[ApprovalCapability()])
return decorator
return ToolOptions(capabilities=[ApprovalCapability()])
def fallback(tool_name: str):
def fallback(tool_name: str) -> ToolOptions:
"""降级备用工具。主工具执行失败时自动路由至备用工具。"""
from zhenxun.services.ai.tools.core.capabilities import FallbackCapability
def decorator(func: Callable):
return _update_settings(
func,
capabilities=[FallbackCapability(fallback_tool_name=tool_name)],
)
return decorator
return ToolOptions(capabilities=[FallbackCapability(fallback_tool_name=tool_name)])
def before_execute(hook_func: Callable):
def before_execute(hook_func: Callable) -> ToolOptions:
"""生命周期拦截:在工具即将执行前触发,可在此篡改全局状态或通过依赖注入访问当前参数。"""
from zhenxun.services.ai.tools.core.capabilities import LifecycleCapability
def decorator(func: Callable):
return _update_settings(
func, capabilities=[LifecycleCapability(before_execute=hook_func)]
)
return decorator
return ToolOptions(capabilities=[LifecycleCapability(before_execute=hook_func)])
def after_execute(hook_func: Callable):
def after_execute(hook_func: Callable) -> ToolOptions:
"""生命周期拦截:在工具执行完毕后触发,可在此修改将发往大模型的返回结果。"""
from zhenxun.services.ai.tools.core.capabilities import LifecycleCapability
def decorator(func: Callable):
return _update_settings(
func, capabilities=[LifecycleCapability(after_execute=hook_func)]
)
return decorator
return ToolOptions(capabilities=[LifecycleCapability(after_execute=hook_func)])
def validate_args(hook_func: Callable):
def validate_args(hook_func: Callable) -> ToolOptions:
"""生命周期拦截:在工具反序列化校验前触发。如果校验失败直接抛出异常,将触发大模型重试机制。"""
from zhenxun.services.ai.tools.core.capabilities import LifecycleCapability
def decorator(func: Callable):
return _update_settings(
func, capabilities=[LifecycleCapability(validate_args=hook_func)]
)
return decorator
return ToolOptions(capabilities=[LifecycleCapability(validate_args=hook_func)])
def prepare_tool(hook_func: Callable):
def prepare_tool(hook_func: Callable) -> ToolOptions:
"""生命周期拦截:在向大模型渲染 JSON Schema 前触发,
可在此动态修改该工具的定义或返回 None 对大模型隐藏。"""
from zhenxun.services.ai.tools.core.capabilities import LifecycleCapability
def decorator(func: Callable):
return _update_settings(
func, capabilities=[LifecycleCapability(prepare_tool=hook_func)]
)
return decorator
return ToolOptions(capabilities=[LifecycleCapability(prepare_tool=hook_func)])
def require_superuser():
def require_superuser() -> ToolOptions:
"""仅限超级管理员使用。若调用者无权限,该工具将直接对大模型隐藏。"""
from zhenxun.services.ai.tools.core.capabilities import SuperuserCapability
def decorator(func: Callable):
return _update_settings(func, capabilities=[SuperuserCapability()])
return decorator
return ToolOptions(capabilities=[SuperuserCapability()])
def require_admin_level(min_level: int = 1):
def require_admin_level(min_level: int = 1) -> ToolOptions:
"""仅限满足指定群聊权限等级的用户使用。"""
from zhenxun.services.ai.tools.core.capabilities import AdminLevelCapability
def decorator(func: Callable):
return _update_settings(
func,
capabilities=[AdminLevelCapability(min_level)],
metadata={"admin_level": min_level},
)
return decorator
return ToolOptions(
capabilities=[AdminLevelCapability(min_level)],
metadata={"admin_level": min_level},
)
def require_group():
def require_group() -> ToolOptions:
"""限制该工具仅能在群聊环境中被大模型调用。"""
from zhenxun.services.ai.tools.core.capabilities import GroupOnlyCapability
def decorator(func: Callable):
return _update_settings(func, capabilities=[GroupOnlyCapability()])
return decorator
return ToolOptions(capabilities=[GroupOnlyCapability()])
def require_minimum_gold(amount: int):
def require_minimum_gold(amount: int) -> ToolOptions:
"""需满足一定的金币余额才可调用,同时执行后会自动扣除该金币。"""
def decorator(func: Callable):
return _update_settings(func, metadata={"cost_gold": amount})
return decorator
return ToolOptions(metadata={"cost_gold": amount})
def require_session_state(key: str, expected_value: Any = None):
def require_session_state(key: str, expected_value: Any = None) -> ToolOptions:
"""需要当前执行上下文中包含指定状态变量,否则隐藏工具"""
class StateDependencyCapability(AbstractCapability):
@@ -356,10 +328,7 @@ def require_session_state(key: str, expected_value: Any = None):
return tool_defs
return []
def decorator(func: Callable):
return _update_settings(func, capabilities=[StateDependencyCapability()])
return decorator
return ToolOptions(capabilities=[StateDependencyCapability()])
class Rules:
@@ -368,6 +337,15 @@ class Rules:
用于控制工具的极速缓存、沙箱要求、前端静默以及人机交互审批等。
"""
@staticmethod
def combine(*rules: ToolOptions) -> ToolOptions:
"""打包组合多个规则预设载荷"""
base = ToolOptions()
for r in rules:
if isinstance(r, ToolOptions):
base = base.merge(r)
return base
sandbox = staticmethod(require_sandbox)
"""环境:声明执行该工具所需的物理沙箱及 pip/npm 包依赖"""
+6 -15
View File
@@ -1,4 +1,5 @@
from collections.abc import Callable
import copy
import hashlib
import inspect
import json
@@ -6,18 +7,22 @@ from typing import Any, Literal
from pydantic import BaseModel, ValidationError
from zhenxun.services.ai.capabilities import CombinedCapability
from zhenxun.services.ai.core.exceptions import (
ControlFlowExit,
NeedsInputException,
ToolFatalError,
ToolRetryError,
)
from zhenxun.services.ai.core.models import ToolDefinition
from zhenxun.services.ai.run import RunContext
from zhenxun.services.ai.run.context import RunContext
from zhenxun.services.ai.tools.core.capabilities import InteractiveCapability
from zhenxun.services.ai.tools.models import (
ResolvedToolPayload,
ToolOptions,
ToolResult,
)
from zhenxun.services.ai.utils.utils import wrap_to_async
from zhenxun.services.log import logger
from zhenxun.utils.pydantic_compat import model_dump, model_json_schema, model_validate
@@ -112,8 +117,6 @@ class BaseTool:
def clone_with_options(self, override: Any) -> "BaseTool":
"""创建当前工具的浅拷贝,并覆盖 ToolOptions 和基本属性"""
import copy
new_tool = copy.copy(self)
if hasattr(override, "to_tool_options"):
@@ -154,8 +157,6 @@ class BaseTool:
)
schema_hint = build_schema_hint(validation_model)
from zhenxun.services.ai.tools.core.capabilities import InteractiveCapability
if any(
isinstance(c, InteractiveCapability) for c in self.settings.capabilities
):
@@ -225,10 +226,6 @@ class BaseTool:
)
if context and self.settings.capabilities:
from zhenxun.services.ai.capabilities import (
CombinedCapability,
)
combined_cap = CombinedCapability(self.settings.capabilities)
defs = await combined_cap.prepare_tools(context, [tool_def])
if not defs:
@@ -239,8 +236,6 @@ class BaseTool:
async def resolve(self, context: RunContext | None = None) -> ResolvedToolPayload:
"""实现 ToolResolvable 协议,将自身解析为标准 Payload"""
import copy
definition = await self.get_definition(context)
if definition is None:
return ResolvedToolPayload()
@@ -288,8 +283,6 @@ class BaseTool:
try:
return await self._core_execution(context_to_pass, **kwargs)
except Exception as e:
from zhenxun.services.ai.core.exceptions import ControlFlowExit
if not isinstance(e, ControlFlowExit):
logger.error(f"工具 {self.name} 执行抛出异常,将交由底层引擎处理: {e}")
raise
@@ -381,8 +374,6 @@ class FunctionTool(BaseTool):
super().__init__(name=name, description=description, settings=settings)
self._original_func = func
from zhenxun.services.ai.utils.utils import wrap_to_async
self.__name__ = self.name
self._func = wrap_to_async(func)
self._schema_built = False
+24 -10
View File
@@ -1,9 +1,10 @@
from collections.abc import Callable
import copy
import inspect
from typing import Any, ClassVar
from zhenxun.services.ai.core.protocols.tool import ToolExecutable
from zhenxun.services.ai.run import RunContext
from zhenxun.services.ai.run.context import RunContext
from zhenxun.services.ai.run.di import DependencyInjector
from zhenxun.services.ai.tools.models import (
ResolvedToolPayload,
@@ -51,19 +52,27 @@ class BaseToolkit:
tools: 除了带有 @tool 装饰器的方法外,要额外动态注入的独立工具列表。
instructions: 当前工具箱实例的系统提示词补充说明。
"""
base_config = getattr(self.__class__, "_default_config", ToolkitConfig())
if config is not None:
merged_dict = {
**model_dump(self._default_config, exclude_unset=True),
**model_dump(base_config, exclude_unset=True),
**model_dump(config, exclude_unset=True),
}
self.config = ToolkitConfig(**merged_dict)
else:
self.config = ToolkitConfig(
prefix=prefix,
include=include,
exclude=exclude,
shared_options=shared_options,
)
overrides = {}
if prefix is not None:
overrides["prefix"] = prefix
if include is not None:
overrides["include"] = include
if exclude is not None:
overrides["exclude"] = exclude
if shared_options is not None:
overrides["shared_options"] = shared_options
merged_dict = {**model_dump(base_config, exclude_unset=True), **overrides}
self.config = ToolkitConfig(**merged_dict)
if self.config.prefix is None:
self.config.prefix = ""
@@ -101,6 +110,7 @@ class BaseToolkit:
def _apply_global_settings(
self, original_name: str, settings: ToolOptions
) -> ToolOptions:
"""合并工具箱全局配置到单个工具的配置项中"""
if self.config.shared_options:
settings = self.config.shared_options.merge(settings)
@@ -143,16 +153,18 @@ class BaseToolkit:
)
async def get_tools(self, context: RunContext | None = None) -> dict[str, BaseTool]:
"""获取该工具箱中所有合法注册的工具实例映射表"""
if self._cached_tools is not None:
return self._cached_tools
tools_dict: dict[str, BaseTool] = {}
for t in self._injected_tools:
t.parent_toolkit = self
if self.config.prefix and not t.name.startswith(self.config.prefix):
t.name = f"{self.config.prefix}{t.name}"
t.settings = self._apply_global_settings(t.name, t.settings)
tools_dict[t.name] = t
import inspect
for name, member in inspect.getmembers(self.__class__):
if hasattr(member, "__toolkit_tool__"):
bound_tool = getattr(self, name)
@@ -201,6 +213,7 @@ class BaseToolkit:
return f"<{tag_name}>\n{text}\n</{tag_name}>"
def prefixed(self, prefix: str) -> "BaseToolkit":
"""克隆工具箱并为其中所有工具追加统一的前缀"""
new_tk = copy.copy(self)
new_tk.config = model_copy(self.config, deep=True)
current_prefix = new_tk.config.prefix or ""
@@ -209,6 +222,7 @@ class BaseToolkit:
return new_tk
def filtered(self, filter_func: Callable[[BaseTool], bool]) -> "BaseToolkit":
"""克隆工具箱并通过自定义过滤器筛选其中的工具"""
new_tk = copy.copy(self)
new_tk.config = model_copy(self.config, deep=True)
old_filter = self._instance_filter
+4 -16
View File
@@ -13,6 +13,7 @@ from nonebot.adapters import Message as PlatformMessage
from zhenxun.services.ai.capabilities import CombinedCapability
from zhenxun.services.ai.core.exceptions import (
ControlFlowExit,
ToolFatalError,
ToolRetryError,
)
@@ -24,11 +25,14 @@ from zhenxun.services.ai.core.stream_events import (
ToolStreamChunkEvent,
UserCustomEvent,
)
from zhenxun.services.ai.message_builder import MessageBuilder
from zhenxun.services.ai.run.context import RunContext
from zhenxun.services.ai.run.di import DependencyInjector
from zhenxun.services.ai.tools.models import (
StateSyncResult,
ToolOptions,
ToolResult,
ToolResultChunk,
ValidatedToolCall,
)
from zhenxun.services.log import logger
@@ -144,8 +148,6 @@ class ToolExecutor:
available_tools: "ToolCollection | dict[str, Any] | None" = None,
) -> RunContext:
"""准备/克隆工具调用所使用的隔离 RunContext"""
from zhenxun.services.ai.run import RunContext
safe_context = (
context.clone_for_tool_call(tool_call_id, tool_name)
if context
@@ -256,8 +258,6 @@ class ToolExecutor:
available_tools = {}
tool_name = validated.call.tool_name
if not validated.args_valid or validated.tool is None:
from zhenxun.services.ai.core.exceptions import ControlFlowExit
if isinstance(validated.validation_error, ControlFlowExit):
raise validated.validation_error
@@ -303,16 +303,12 @@ class ToolExecutor:
if not isinstance(result, ToolResult):
result = ToolResult(output=result)
from zhenxun.services.ai.tools.models import StateSyncResult
if isinstance(result, StateSyncResult) and result.state_notice:
if safe_context:
safe_context.run.add_system_prompt(
f"[系统通知(状态同步)]:{result.state_notice}"
)
except BaseException as e:
from zhenxun.services.ai.core.exceptions import ControlFlowExit
if isinstance(e, ControlFlowExit):
raise e
if isinstance(e, asyncio.CancelledError):
@@ -394,8 +390,6 @@ class ToolExecutor:
func_name = original_call.tool_name
if isinstance(result_pair, BaseException):
from zhenxun.services.ai.core.exceptions import ControlFlowExit
if isinstance(result_pair, ControlFlowExit):
raise result_pair
if isinstance(result_pair, asyncio.CancelledError):
@@ -438,8 +432,6 @@ class ToolExecutionPolicy:
"""
计算当前工具的绝对最大重试次数。
优先使用工具级配置 (ToolOptions.max_retries),如果未设置,则使用全局配置。
由于重试机制是保证 Agent 稳定性的防线,
即使全局为 0,底层默认也会给予至少 1 次的机会。
"""
tool_retries = getattr(self.settings, "max_retries", None)
if tool_retries is not None:
@@ -496,8 +488,6 @@ class NativeToolRunner(ToolRunner):
if isinstance(chunk, ToolResult):
res = chunk
else:
from zhenxun.services.ai.tools.models import ToolResultChunk
chunk_obj = (
chunk
if isinstance(chunk, ToolResultChunk)
@@ -525,8 +515,6 @@ class NativeToolRunner(ToolRunner):
final_result = res
else:
if str(type(res)).find("Message") != -1:
from zhenxun.services.ai.message_builder import MessageBuilder
uni_msg = (
MessageBuilder.message_to_unimessage(res)
if isinstance(res, PlatformMessage)
@@ -26,6 +26,7 @@ class ToolCollection(list[T], Generic[T]):
"""支持按索引和按名称获取的工具集合 (List + Dict)"""
def __init__(self, iterable: Iterable[T] | None = None):
"""初始化工具集合。"""
super().__init__(iterable or [])
self._name_cache: dict[str, T] = {}
self._build_name_cache()
@@ -79,9 +80,11 @@ class ToolCollection(list[T], Generic[T]):
self._name_cache[value.name.lower()] = value
def get(self, key: str, default: Any = None) -> T | Any:
"""通过名称获取工具,若不存在则返回默认值。"""
return self._name_cache.get(key.lower(), default)
def append(self, object: T) -> None:
"""向集合中添加工具,并更新名称缓存。"""
name = object.name
if name.lower() in self._name_cache:
old_tool = self._name_cache[name.lower()]
@@ -95,16 +98,19 @@ class ToolCollection(list[T], Generic[T]):
self._name_cache[name.lower()] = object
def extend(self, iterable: Iterable[T]) -> None:
"""批量添加工具到集合中。"""
for t in iterable:
self.append(t)
def remove(self, value: T) -> None:
"""从集合中移除指定工具,并同步更新缓存。"""
super().remove(value)
name = getattr(value, "name", None)
if name and name.lower() in self._name_cache:
del self._name_cache[name.lower()]
def pop(self, index: SupportsIndex = -1) -> T:
"""弹出指定位置的工具,并从缓存中移除。"""
tool = super().pop(index)
name = getattr(tool, "name", None)
if name and name.lower() in self._name_cache:
@@ -112,6 +118,7 @@ class ToolCollection(list[T], Generic[T]):
return tool
def filter_by_names(self, names: list[str] | None = None) -> "ToolCollection[T]":
"""根据名称列表筛选并返回新的工具子集合。"""
if names is None:
return self
return ToolCollection(
@@ -123,16 +130,20 @@ class ToolCollection(list[T], Generic[T]):
)
def clear(self) -> None:
"""清空集合及所有名称缓存。"""
super().clear()
self._name_cache.clear()
def keys(self):
"""获取所有工具名称缓存的键。"""
return self._name_cache.keys()
def values(self):
"""获取所有已缓存的工具实例。"""
return self._name_cache.values()
def items(self):
"""获取所有工具名称与实例的键值对。"""
return self._name_cache.items()
@@ -145,11 +156,13 @@ class _StringResolver:
def __init__(
self, name: str, manager: "ToolProviderManager", default_namespace: str
):
"""初始化字符串路由解析器。"""
self.name = name
self.manager = manager
self.default_namespace = default_namespace
async def resolve(self, context: RunContext | None = None) -> ResolvedToolPayload:
"""解析字符串路由并返回匹配的工具载荷。"""
if self.name in self.manager._macro_resolvers:
resolver = self.manager._macro_resolvers[self.name]
resolved = (
@@ -192,11 +205,13 @@ class _QueryResolver:
def __init__(
self, query: Query, manager: "ToolProviderManager", default_namespace: str
):
"""初始化查询对象路由解析器。"""
self.query = query
self.manager = manager
self.default_namespace = default_namespace
async def resolve(self, context: RunContext | None = None) -> ResolvedToolPayload:
"""执行查询以解析并返回匹配的工具载荷。"""
payload = ResolvedToolPayload()
namespaces_to_search = []
@@ -235,10 +250,12 @@ class _CallableResolver:
"""
def __init__(self, func: Callable, manager: "ToolProviderManager"):
"""初始化可调用对象路由解析器。"""
self.func = func
self.manager = manager
async def resolve(self, context: RunContext | None = None) -> ResolvedToolPayload:
"""将可调用对象转换为函数工具并返回其解析载荷。"""
for candidate in (
getattr(self.func, "__tool_name__", None),
getattr(self.func, "__name__", None),
@@ -263,12 +280,14 @@ class _TypeAdapterResolver:
manager: "ToolProviderManager",
default_namespace: str,
):
"""初始化类型适配器解析器。"""
self.item = item
self.resolver_func = resolver_func
self.manager = manager
self.default_namespace = default_namespace
async def resolve(self, context: RunContext | None = None) -> ResolvedToolPayload:
"""执行类型适配器函数并解析返回对应的工具载荷。"""
resolved = (
await self.resolver_func(self.item)
if is_coroutine_callable(self.resolver_func)
@@ -285,11 +304,13 @@ class ToolProviderManager:
_instance: "ToolProviderManager | None" = None
def __new__(cls) -> Self:
"""单例模式的实例创建方法。"""
if cls._instance is None:
cls._instance = super().__new__(cls)
return cast(Self, cls._instance)
def __init__(self):
"""初始化工具提供者管理器。"""
if hasattr(self, "_initialized") and self._initialized:
return
@@ -304,11 +325,13 @@ class ToolProviderManager:
self._type_resolvers: dict[type, Callable] = {}
def register_macro_resolver(self, macro_str: str, resolver_func: Callable) -> None:
"""注册宏解析器函数。"""
self._macro_resolvers[macro_str] = resolver_func
def register_type_resolver(
self, target_type: type, resolver_func: Callable
) -> None:
"""注册特定类型的工具解析函数。"""
self._type_resolvers[target_type] = resolver_func
def register(self, provider: ToolProvider):
@@ -445,6 +468,7 @@ class ToolProviderManager:
excluded_servers: list[str] | None = None,
namespaces: list[str] | None = None,
) -> ToolCollection:
"""获取已解析完成的所有可用工具集合。"""
has_filters = (
allowed_servers is not None
or excluded_servers is not None
@@ -462,11 +486,13 @@ class ToolProviderManager:
return tools
async def resolve_specific_tools(self, tool_names: list[str]) -> ToolCollection:
"""根据名称列表检索并返回特定的工具集合。"""
return await self._query_engine(names=tool_names, include_providers=True)
async def get_function_tools(
self, names: list[str] | None = None
) -> ToolCollection:
"""获取本地注册的所有函数工具集合。"""
return await self._query_engine(names=names, include_providers=False)
def _normalize_to_resolver(self, item: Any, default_ns: str) -> Any:
@@ -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,