工具注册表与 schema 校验


文档摘要

工具注册表与 schema 校验 本节摘要:Agent 无法校验的工具就是 Agent 无法调用的工具。本节先建注册表与 schema 检查器,再建工具。一个 2026 编码 Agent 注册的工具数比模型单上下文窗口能装的还多——非平凡外壳会注册两百个工具、每轮只浮现十到四十个。注册表是「有哪些工具、参数什么形状、调哪个 handler」的唯一真相源。本节实现一个覆盖九成实际调用的 JSON Schema 2020-12 子集、返回 json-pointer 形状的精确错误路径、拒绝静默重注册,并把校验器保持为纯函数(无 I/O、无时间、无全局),以便在重放日志上重跑。 对应原课程:Phase 19 · Lesson 21 · (原英文 )。

工具注册表与 schema 校验

本节摘要:Agent 无法校验的工具就是 Agent 无法调用的工具。本节先建注册表与 schema 检查器,再建工具。一个 2026 编码 Agent 注册的工具数比模型单上下文窗口能装的还多——非平凡外壳会注册两百个工具、每轮只浮现十到四十个。注册表是「有哪些工具、参数什么形状、调哪个 handler」的唯一真相源。本节实现一个覆盖九成实际调用的 JSON Schema 2020-12 子集、返回 json-pointer 形状的精确错误路径、拒绝静默重注册,并把校验器保持为纯函数(无 I/O、无时间、无全局),以便在重放日志上重跑。

对应原课程:Phase 19 · Lesson 21 · tool-registry-schema-validation(原英文 phases/19-capstone-projects/21-tool-registry-schema-validation/docs/en.md)。本节属「Agent Harness 深度构建赛道」第二节。

学习目标

阅读完本节,你应当能够:

  1. 持有一个类型化注册表(工具名 → schema → handler),分发器问一次后即可信任。
  2. 实现覆盖九成实际调用的 JSON Schema 2020-12 子集(八个关键字)。
  3. 返回精确的、json-pointer 形状的错误路径,让模型一轮往返内自我修正。
  4. 拒绝重注册除非显式 override——静默覆盖是生产工具目录漂移的根源。
  5. 保持校验器纯(无 I/O、无时间、无全局),以便在重放日志上重跑。

一、问题与直觉

一个 2026 编码 Agent 注册的工具数比模型单上下文窗口能装的还多。非平凡外壳会注册两百个工具,每轮只浮现十到四十个。注册表是「有哪些工具、参数什么形状、调哪个 handler」的唯一真相源。一旦这三个答案钉死,外壳其余部分就不用再猜。

我们要避免的错误是:发 handler 不发 schema,或发 schema 不做校验。两者都常见,两者都把下一层(第 23 节的分发器)变成猜谜游戏,唯一的失败模式是 handler 抛栈跟踪。

二、从零实现

工具记录(ToolRecord)的形状:name(唯一,小写字母数字与下划线段,点分隔,如 snake_case.segment.case)、description(一行,给模型看)、schema(JSON Schema 2020-12 子集)、handler(同步或异步,返回 Any)、idempotent(分发器据此决定重试)、timeout_ms(覆盖分发器默认)。

schema 是校验器唯一碰的字段;handler 对它不透明。我们故意分开——schema 是数据,handler 是代码。混在一起会诱使你把校验逻辑塞进 handler,这正是我们要止的 bug。

JSON Schema 2020-12 子集(八个关键字):

type string/number/integer/boolean/object/array/null properties 属性名 -> schema 的映射 required 属性名列表 enum 允许的原始值列表 minLength 整数,适用于字符串 maxLength 整数,适用于字符串 pattern ECMA-262 兼容正则,适用于字符串 items 适用于每个数组元素的 schema

这够覆盖工具 API 真实所需。我们不加的关键字(oneOf/anyOf/allOf/$ref/条件)在生产 schema 里合法,但会把校验器变成带环的树遍历器。我们在建注册表,不是 JSON Schema 引擎。

错误路径用 json-pointer(模型读路径比读句子好):

{"a": {"b": [1, 2, "x"]}} ^ /a/b/2

若 schema 要 args.user.email 而模型传了整数,错误应是 /user/emailexpected_type: string。模型下轮无需自然语言往返即可修。

三、注册、override 与边界

register(name, schema, handler, **opts) 默认拒绝重注册,调用方必须传 override=True 才替换。这是运维卫生——代码库两处静默注册同名工具,是那种生产里要花一周才找到的 bug。注册表暴露三个读方法:get(name) 返回记录或抛异常;validate(name, args) 返回 Ok 或错误列表;names() 按注册顺序返回工具名。

校验器是对 schema 树的单遍递归,纯函数。它不调 handler、不强转类型(字符串 "42" 不通过 number schema)、不静默截断。它不是安全边界——恶意 handler 在校验通过后仍可作恶;第 23 节的分发器会加超时与沙箱层。注册表加的是形状

四、可复用产物

code/main.py 定义 ToolRegistryToolRecordValidationError 与八个校验函数。校验器按 schema["type"] 分派(或有 enum 时当无类型枚举检查)。每个类型校验器返回空列表或 ValidationError 列表。顶层遍历器拼接错误并在下降时前缀路径段。code/tests/test_registry.py 覆盖注册、override、校验成功、带路径的校验失败、子集里每个关键字。

五、框架对比

JSON Schema 2020-12 全规范是一篇论文,我们要的八个关键字覆盖九成真实调用。MCP 的工具 schema、OpenAI 的 function calling、Anthropic 的 tool_use 都收敛到了类似的子集。本节的差异化在「错误路径精确」——/user/emailexpected_type 让模型自我修正,这比自然语言错误描述省一轮往返。另一个差异化是「拒绝静默重注册」,把工具目录漂移挡在注册时。

六、练习

  1. $ref:对本地 definitions 块实现 $ref 解析,测一个引用复合类型的工具。
  2. additionalProperties: false:实现严格形状,拒绝未声明属性,测一个带额外字段的调用。
  3. 错误路径嵌套:构造一个三层嵌套对象,故意在深层放错类型,确认指针路径 /a/b/2 精确。
  4. override 卫生:写一个测试,确认不传 override=True 的重注册被拒,且错误信息含原注册的来源。
  5. 纯函数验证:在重放日志上重跑校验器,确认无 I/O/时间依赖导致结果漂移。

本节要点回顾

  1. 注册表先于工具:它是「有哪些工具/什么形状/调哪个 handler」的唯一真相源。
  2. 八关键字子集:type/properties/required/enum/minLength/maxLength/pattern/items 覆盖九成。
  3. json-pointer 错误路径:模型读 /user/email 比读句子好,一轮往返内自我修正。
  4. 拒绝静默重注册:override=True 才替换,挡工具目录漂移。
  5. 校验器纯函数:无 I/O/时间/全局,可在重放日志上重跑。
  6. 加的是形状不是安全:恶意 handler 仍可作恶,沙箱在第 23 节分发器。

下一节,我们给注册表配上「JSON-RPC stdio 传输」,把它浮现给模型客户端。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U