这一节的核心思想就一句话:模型能看到什么工具,Harness 就应该从同一个来源里去执行什么工具。只要来源分裂,后面就很容易出现“模型看得到但系统调不到”,或者“系统能执行但模型根本不知道”的问题。
![图片[1]-小节四:把 Registry 接入 Agent Harness——让模型看到的 Tool 和 Harness 能执行的 Tool 来自同一个地方-大赛人网](https://www.dsrw.com/wp-content/uploads/2026/11/image-1470.png)
图里把这件事拆得很清楚:上面一支是给模型看的 Tool Schema,下面一支是给 Harness 用的 get(name)、权限校验和执行逻辑,但它们都从同一个 Registry 出来。这样,工具目录才真正成为整个 Harness 的单一事实来源。
到这里我们已经有:
ToolRegistry
Tool
@tool
现在要解决最后一个特别重要的问题,模型看到的 Tool 列表,和 Harness 实际能执行的 Tool,怎么保证一致?
很多 Agent Bug 都来自这两套列表不同步。
比如 Prompt 写:
你可以使用:
read_file
search_files
run_python
但 Registry 实际只有:
read_file
run_python
模型一调用:
search_files
就 UnknownTool。
最稳的思路是,模型可见工具列表直接从 Registry 生成。
也就是说:
Registry
├─ 给 Provider 生成 Tool Schemas
└─ 给 Executor 提供真实 Callable
一个来源,两种用途。
执行时也必须回 Registry
Main Loop:
response = provider.chat(
messages,
tools=schemas,
)
模型返回:
read_file
Executor 不自己写 if/elif。
而是:
registry.execute(
name,
arguments,
)
这样整个 Tool 生命周期是统一的。
一个完整 Harness Demo
下面我们把:
Registry
@tool
FakeProvider
Main Loop
全部接起来。
这段代码已经具备一个非常重要的结构:
工具定义
↓
@tool
↓
Registry
├─ list_tools
└─ execute
以后再加 Tool:
@tool()
def search_files(...):
...
Main Loop 不需要增加:
elif name == "search_files":
这就是抽象真正起作用的地方。
Registry 为什么还是安全控制的挂载点?
再往后走一步,Registry 就不只是“收工具”的地方了,它还是权限、暴露范围、可见性控制的天然挂载点。
![图片[2]-小节四:把 Registry 接入 Agent Harness——让模型看到的 Tool 和 Harness 能执行的 Tool 来自同一个地方-大赛人网](https://www.dsrw.com/wp-content/uploads/2026/11/image-1471.png)
这张图很好理解:同一个 Registry 里可以统一存放工具定义,但面向分析师、运营、管理员时,暴露出去的能力集合并不一样。也就是说,Registry 既承担了统一管理,也承担了能力裁剪的入口作用。
后面我们会加:
Tool.risk_level
Tool.requires_approval
Tool.timeout
于是执行前:
tool = registry.get(name)
middleware.check(tool, arguments)
所有 Tool 都经过同一入口。
这会比:
每个函数内部自己判断安全
稳定很多。
所以 Registry 最终会连接:
Tool Schema
Tool Executor
Middleware
Tracing
Permission
Human Approval
本节先把它做好,后面会越来越省力。
现在可以回到配套项目核对一次。项目中的方法名与前面教学版本略有不同:names() 返回工具名称,schemas() 返回给 Provider 使用的描述,execute(name, **kwargs) 负责真实调用。职责仍然对应 list_tools()、Schema 生成与统一执行,只是接口更贴近最终 Engine。
cd agent_harness_course_projects
python3 projects/03_tool_registry_schema/main.py
运行后先看到 add 的 Schema,再看到结果 5。可以再做一个很小的实验:重复注册同名 add,确认 Registry 明确报错;然后请求一个不存在的名称,观察 get() 返回的 KeyError。这两个失败都应该是可预测、可测试的,而不是静默覆盖或动态搜索其他函数。
还要留意,配套 ToolRegistry.execute() 当前只负责查找并调用,参数 Schema 校验和按用户授权尚未内置在 Registry 中。Registry 是这些能力的稳定挂载点,但“有挂载点”不等于相关控制已经全部生效;下一节会继续处理 Schema,第 24~26 节再补 Middleware、审批和 Sandbox。
工具注册之后,还要决定谁能看见和执行
假设我们同时接入两个文档服务,它们都提供 search。Registry 若只按裸名称存储,后注册的工具可能覆盖前一个。命名空间可以消除歧义,但暴露给模型的名称还要符合目标接口的命名限制。我们可以保留内部限定名,再由 Provider 映射为合法名称,回传时还原。
发现工具、注册工具、展示工具和执行授权是四个环节。一个工具已经注册,不代表所有用户都能看见;从可见列表隐藏它,也不能替代执行时鉴权。实际调用必须再次依据当前身份、资源归属和策略判断,因为用户权限可能在两次请求之间改变。
版本、风险与幂等字段也要找到消费位置:版本进入 Trace,风险供策略判断,幂等性影响重试。远程 MCP Server 的描述与 annotations 是外部声明,Host 不能直接把它们当作可信权限结论。配套适配器展示了允许列表的入口,完整授权和服务端验证仍需继续接入。
动手检查:同名与越权分别由谁处理
注册两个名为 search 的远程工具,再模拟一个用户只获得其中一个服务的权限。参考验收是:名称不会互相覆盖,模型仅看见允许的工具,直接构造另一个工具调用也会在执行层被拒绝。若只能通过“模型通常不会这样调用”来解释结果,授权链路还没有完成。
















请登录后查看评论内容