题目 1:为什么大量 if name == ... 会随着 Tool 增多越来越难维护?
假设一个 Coding Agent 从 2 个 Tool 扩展到 15 个 Tool,请从扩展性、重复修改、工具发现、权限、测试几个角度解释为什么需要 Registry,而不只是回答“代码太长”。
大量 if/elif 最大的问题不是视觉上变长,而是每新增 Tool 都必须修改中心分发函数,形成强耦合。与此同时,模型可见工具列表、真实执行能力、权限规则、Tracing 元数据可能分别维护,容易不同步。Registry 把 Tool Name、Callable 和后续 Metadata 集中管理,让新增 Tool 通过注册完成,Main Loop 不必修改;测试也可以直接验证 Registry 中有哪些 Tool。它因此成为扩展能力和保持一致性的基础。
题目 2:判断题——可以用 globals()[tool_name] 替代 Tool Registry,对吗?
请说明为什么这种方式在普通脚本里可能能跑,但在 Agent Harness 中存在明显风险。
不建议。
globals()的目标是访问当前模块全局命名空间,而不是表达 Agent 被授权使用的工具集合。里面可能存在内部函数、调试函数、敏感操作和其他 callable。如果模型能按名字动态访问 globals,就可能触达本不该暴露的能力。Tool Registry 则采用显式白名单:只有主动注册的函数才属于 Agent Tool,因此同时承担能力目录与安全边界的角色。
题目 3:代码阅读——为什么 Registry 返回 Tool 对象比只返回 Callable 更容易扩展?
假设后续要增加 description、risk_level、requires_approval、timeout,请说明如果 Registry 只保存函数会遇到什么问题。
如果 Registry 只保存 Callable,工具的描述、风险等级、审批要求等信息就需要散落在其他字典或配置中,容易和函数定义失去同步。返回 Tool 对象后,可以把
name、description、func以及后续 Schema、Risk、Timeout 等 Metadata 放在同一实体上。这样 Provider 生成 Tool Schema、Middleware 做安全判断、Tracing 记录工具信息时,都能从同一个 Tool 对象读取,职责更集中。
题目 4:编程实现——写一个支持 register、get、list_tools、execute 的 ToolRegistry
要求完整可运行,并至少注册 read_file、list_files 两个工具;未知工具必须抛出或返回明确错误。
#下面是一种完整实现:
from typing import Any, Callable
class ToolRegistry:
def __init__(self):
self._tools: dict[
str,
Callable[..., Any]
] = {}
def register(
self,
func: Callable[..., Any],
) -> None:
name = func.__name__
if name in self._tools:
raise ValueError(
f"工具已存在:{name}"
)
self._tools[name] = func
def get(
self,
name: str,
) -> Callable[..., Any]:
if name not in self._tools:
raise KeyError(
f"未知工具:{name}"
)
return self._tools[name]
def list_tools(self) -> list[str]:
return list(
self._tools.keys()
)
def execute(
self,
name: str,
arguments: dict[str, Any],
) -> Any:
func = self.get(name)
return func(
**arguments
)
def read_file(path: str) -> str:
return f"读取:{path}"
def list_files() -> list[str]:
return [
"README.md",
"app.py",
]
if __name__ == "__main__":
registry = ToolRegistry()
registry.register(read_file)
registry.register(list_files)
print(
registry.list_tools()
)
print(
registry.execute(
"read_file",
{
"path": "app.py"
},
)
)
#这个版本已经实现最核心 Registry 能力,后续再逐渐加入 Tool 对象、Schema、权限等 Metadata
题目 5:编程实现——实现一个 @tool 自动注册装饰器
要求函数定义以后自动进入全局 Registry,并且函数本身仍可以正常直接调用。请说明装饰器为什么需要 return func。
#可以这样实现:
from typing import Any, Callable
TOOLS: dict[
str,
Callable[..., Any]
] = {}
def tool(
func: Callable[..., Any],
):
if func.__name__ in TOOLS:
raise ValueError(
f"重复工具:{func.__name__}"
)
TOOLS[
func.__name__
] = func
return func
@tool
def read_file(path: str) -> str:
return f"读取:{path}"
if __name__ == "__main__":
print(
list(TOOLS.keys())
)
print(
read_file(
"README.md"
)
)
#return func 很重要,因为装饰器语法本质上会把原函数名重新绑定为装饰器返回值。如果不返回原函数,read_file 可能变成 None 或其他对象,失去直接调用能力。这里我们只想增加“注册”副作用,同时保留原函数。
题目 6:综合应用——为什么模型可见 Tool 列表应该从 Registry 生成?
请结合“模型能看到 search_files,但 Registry 没有它”和“Registry 有 run_test,但模型看不到它”两个反例解释。查看参考答案
如果模型可见列表与执行 Registry 分开维护,会出现双向不一致。模型看得到
search_files但 Registry 没有时,模型会正常产生 Tool Call,却在执行阶段 UnknownTool;Registry 有run_test但模型看不到时,系统明明拥有能力,模型却永远不会选择它。让 Tool Schema 从 Registry 生成,可以把“模型能看到什么”和“Harness 能执行什么”建立在同一个事实来源上,显著降低配置漂移问题。
题目 7:概念理解——Tool Registry、Tool Schema、Tool Executor 有什么区别?
请分别用一句话说明三者职责,再说明它们如何在一次 Tool Calling 中协作。
Tool Registry 负责管理“有哪些 Tool,以及真实 Callable 在哪里”;Tool Schema 负责把 Tool 的名称、描述和参数结构告诉模型;Tool Executor 负责拿到模型 Tool Call 后进行查找、校验和实际执行。一次调用中,Provider 先把 Registry 产生的 Schema 提供给模型,模型返回 Tool Call,Executor 再通过 Registry 找到真实 Tool 并执行。三者配合后,模型世界的结构化动作才能稳定映射到 Python 世界的真实函数。
进一步阅读
- Python Decorator 相关官方文档:理解
@tool语法背后的函数包装与名称绑定机制。 - Python
dataclasses文档:适合定义 Tool Metadata 对象。 - Registry Pattern 相关资料:理解注册中心如何统一管理可扩展组件。
- Command Pattern 相关资料:Tool Call 可以理解成命令,Registry 负责找到对应 Command Handler。
- Plugin Architecture 相关资料:后续 Skills、SubAgent、插件化能力与 Registry 思路高度相关。
- Capability-based Security 相关资料:理解“只注册被授权能力”为什么比动态反射所有函数更安全。
- Dependency Injection 相关资料:Registry 与依赖注入在“大量可替换实现”场景里有相似设计思想。
- Python
inspect模块文档:下一节 Tool Schema 会用它读取函数签名和类型信息。
术语表
- 工具注册中心(Tool Registry):Agent Harness 中统一管理可用 Tool 的组件,负责注册、查找、枚举和执行工具,也是后续 Schema、权限、Tracing 等能力的重要挂载点。
- 工具对象(Tool):对一个可调用能力的结构化封装,除了真实 Python Callable,还可以保存 name、description、schema、risk_level 等 Metadata。
- 注册(Register):把一个 Python 函数或 Tool 对象显式加入 Agent 可用能力集合的过程。未注册的函数默认不应该被 Agent 执行。
- 工具查找(Tool Lookup):根据模型返回的 Tool Name,在 Registry 中查找真实 Tool 的过程。查找失败应形成明确 UnknownTool 错误。
- 自动注册装饰器(@tool):通过 Python Decorator 在函数定义时完成 Tool 构建和 Registry 注册,让“声明这是 Agent Tool”和“真正注册”合并为一个清晰动作。
- 工具元数据(Tool Metadata):描述工具附加属性的数据,例如 description、category、risk_level、timeout、requires_approval。它们会被 Schema、安全和 Tracing 等模块使用。
- 能力白名单(Capability Allowlist):只允许 Agent 使用明确注册的能力,而不是让模型动态访问任意 Python 函数。Registry 天然可以承担这层边界。
- 单一事实来源(Single Source of Truth):某类核心配置或数据只由一个地方定义。Tool Registry 应尽量成为“模型可见工具”和“真实可执行工具”的统一来源,避免两套列表漂移。
本章总结
最后再回看一眼全章主线,会更容易把这些零散概念串起来。
![图片[1]-章末自测-大赛人网](https://www.dsrw.com/wp-content/uploads/2026/11/image-1474.png)
你会发现,无论是自动注册、Schema 生成、权限控制,还是角色裁剪,最后其实都在围绕 Registry 这个“统一工具目录”展开。把它设计好,后面的 Tool Calling、权限控制、可见性管理都会轻松很多。
这一节我们把上一节的 Tool Calling 真正推进到了“工具系统”。
上一节模型已经能够产生:
{
"name": "read_file",
"arguments": {
"path": "app.py"
}
}
但我们没有停在一个字典。
我们进一步设计:
ToolRegistry
├── register
├── get
├── list_tools
└── execute
并且把真实 Python Function 包装成:
Tool
├── name
├── description
└── func
这样一来,Registry 不只是函数映射,而开始成为 Agent 的“能力目录”。
最重要的一点是,只有注册到 Registry 的能力,才应该被 Agent 视为 Tool。
这意味着 Registry 同时承担了扩展性和安全边界。
我们还进一步实现:
@tool
def read_file(path: str):
...
让函数定义与注册自动绑定。
以后新增 Tool,不再需要修改 Main Loop,也不需要在 Executor 里继续加:
elif name == ...
而是只新增:
@tool()
def search_files(...):
...
这正是我们想要的扩展方式,新增能力时增加 Tool,不修改 Agent 核心控制流。
如果你想验证自己真正掌握了这一节,可以做三个实验。
第一,自己实现:
register
get
list_tools
execute
第二,写一个 @tool 装饰器,让函数定义后自动注册。
第三,故意让模型请求一个不存在的 Tool,验证系统能明确返回 UnknownTool,而不是直接崩溃。
当这三个都跑通以后,我们的 Agent 已经拥有一个非常稳定的工具入口。
下一节会继续解决 Registry 里的另一个问题,模型怎么知道 read_file 需要一个 path: str 参数?
我们不能只告诉模型:
你有一个 read_file 工具。
还必须告诉它:
工具是干什么的
参数叫什么
参数是什么类型
哪些参数必填
所以第 08 节,我们会进入,Tool Schema:让模型知道工具怎么用。
到那时,Registry 中的 Python Function 会第一次自动转换成模型能够理解的结构化工具协议。
















请登录后查看评论内容