工具系统注册与 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.py 的 discover_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 的元编程反射机制对参数进行类型治理。其类结构设计与输入净化关系如下图所示:
# 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 掉子进程,释放系统资源。
🔗 本章子任务深入剖析专栏
为深度掌握工具系统与净化机制,推荐阅读以下子技术专题: