Extensibility & Integrity

工具系统注册与 JSON 参数自动净化自愈

全面解读 tools/registry.py 装饰器、工具流转模型与参数解析的高容错健壮设计
工具执行与参数净化
📊 图 3-1:模型工具调用调度与损坏 JSON 参数自动修复流转图

🔌 1. 统一工具发现与注册机制

在 Hermes 中,给大模型添加一个自定义工具极其简单。系统在 tools/registry.py 中实现了一个全局注册管理器,通过装饰器反射收集参数:

💻 新手实战:如何编写一个新工具

第一步,在 tools/ 目录下新建一个 my_custom_tool.py 脚本,并按如下模板挂载装饰器:

# tools/my_custom_tool.py
from tools.registry import registry

@registry.register(
    name="query_stock_price",
    description="Query the real-time stock price for a given ticker symbol.",
    schema={
        "type": "object",
        "properties": {
            "ticker": {"type": "string", "description": "The stock symbol, e.g. AAPL"},
            "limit": {"type": "integer", "description": "Max rows to return", "default": 10}
        },
        "required": ["ticker"]
    }
)
def query_stock_price(ticker: str, limit: int = 10) -> str:
    # 真实的工具业务执行逻辑
    return f"Real-time data for {ticker}: $185.20 (limited to {limit} records)"

底层扫描机制:在启动时,model_tools.pydiscover_builtin_tools() 会利用 Python 的 pkgutil.iter_modules() 遍历 tools/ 文件夹下的全部 .py 文件并强制执行 importlib.import_module()。由于装饰器在模块导入时即触发,对应的 Schema 与函数引用便自动灌入全局 registry._tools 哈希表中,无需任何手工配置注册列表。

🛡️ 2. ToolExecutor 参数弱类型强转与 UML 拓扑

大模型常会输出类型不精准的参数(例如在 JSON 参数中把 100 生成为 "100" 字符串)。如果直接传入强类型的 Python 业务函数,就会抛出 TypeError 并中断执行。Hermes 在 agent/tool_executor.py 中内置了 ToolExecutor,利用 Python 的元编程反射机制对参数进行类型治理。其类结构设计与输入净化关系如下图所示:

参数强转与JSON自愈UML
📊 图 3-2:ToolExecutor 反射强转与 JSON 自愈 UML 静态结构图
# ToolExecutor 类型自愈逻辑
import inspect

def coerce_arguments(func, original_args: dict) -> dict:
    sig = inspect.signature(func)
    coerced = {}
    for param_name, param in sig.parameters.items():
        if param_name not in original_args:
            continue
        val = original_args[param_name]
        expected_type = param.annotation
        
        # 1. 布尔型强转
        if expected_type is bool and isinstance(val, str):
            coerced[param_name] = val.lower() in ("true", "1", "yes")
        # 2. 数值型强转
        elif expected_type in (int, float) and isinstance(val, (str, float, int)):
            coerced[param_name] = expected_type(val)
        else:
            coerced[param_name] = val
    return coerced

通过这套内省强转与 ToolParameterCoercer 映射算法,极大提升了工具执行的健壮性,使智能体免受模型参数扰乱之苦。

🩹 3. 损坏 JSON 的正则自愈与 Unicode 清洗

在超长上下文或高负载下,模型生成的 JSON arguments 经常损坏。Hermes 在 agent/message_sanitization.py 中集成了一套高性能自愈算法:

⚙️ JSON 自愈核心:_repair_tool_call_arguments

如果大模型生成的 JSON 字符串由于 Token 暴跌或网络中断导致不完整(如 {"path": "/src", "recursive": true 缺失结尾括号),普通的 json.loads() 会崩溃。_repair_tool_call_arguments() 使用启发式算法抢救:

  • 补全截断括号:通过栈分析检测未闭合的 {, [,在尾部强行追加相应的闭合符 }, ]
  • 剔除尾逗号:通过正则 r',(\s*[}\]])' 匹配并清除诸如 {"a": 1,} 中多余的逗号,使其满足严格的 JSON 规范。
  • 转义自愈:针对模型输出中包含未转义双引号的非法字符串(如 "content": "He said "Hello" to me"),自动扫描识别并转换为 \"Hello\"

🚫 损坏 Unicode 清洗:_sanitize_surrogates

在流式传输或大模型分词不规范时,可能会输出孤立的 UTF-16 Surrogate 字符(即 \ud800\udfff 之间的无效代理项)。在 Python 中对其编码会引发严重的 UnicodeEncodeError。该模块通过迭代清理机制过滤掉无效代理对,保障后续落库与日志输出的平滑性:

# 清理无效代理对字符
def _sanitize_surrogates(text: str) -> str:
    # 强制将孤立的 Surrogate 字符替换为空,防 Python json encode 崩溃
    return text.encode('utf-8', 'surrogateescape').decode('utf-8', 'ignore')

🚨 4. 工具安全栅栏(Guardrails)与超时边界

agent/tool_guardrails.py 中,Hermes 实施了严密的安全防线,保护宿主机不被模型误操作系统破坏:

  • 路径隔离防护:所有涉及文件修改的工具均拦截 .. 向上遍历。所有操作限制在当前的 WORKSPACE_DIR 根目录以内。
  • 超时硬中断:智能体调用 `terminal` 工具运行 Shell 脚本时,底层会强制设定一个 timeout 阀值。如果模型发起了如 ping google.com 这类不带限制次数的阻塞命令,执行器会在超时后抛出 TimeoutExpired 并强行 kill 掉子进程,释放系统资源。