系列:AI Agent 工程实践
上一篇:第 23 篇《Provider 抽象层设计》
下一篇:第 25 篇《Memory Service》

一、开场:一次安全审计发现的"幽灵工具"

有个团队做安全审计,要列出"Agent 总共能调哪些工具、每个权限多大"。结果没人答得上来——工具是 30 多个散落各处的函数,有的在 utils/,有的在 services/,有的直接写在 agent 文件里。审计花了一周,还漏了一个能执行 shell 命令的内部工具,差点出事。

问题不是"工具多",是工具没有被统一管理。上篇(23)讲了模型怎么收口,这篇讲工具怎么收口——Tool Registry

二、问题背景:散落函数 vs 统一注册

散落版:

# tools/email.py
def send_email(to, body): ...
# tools/sql.py
def run_query(sql): ...   # 能删库,但没人列得清
# agent/customer_agent.py 里又直接写了个 get_weather()

注册版:

# 任何地方用工具,都从 Registry 取,且经过权限校验
from tools.registry import ToolRegistry
reg = ToolRegistry()
reg.call("send_email", to=to, body=body)   # 自动校验权限 + 记录审计

差别:send_email 调不调得成,由 Registry 决定,而不是由"谁 import 了它"决定。

三、错误尝试:三种工具管理翻车

错误 1:工具是裸函数,直接 import 调用

谁都能 from tools.sql import run_query 然后执行。没有统一入口,权限、日志、审计全靠各调用点自觉——自觉从来靠不住。

错误 2:权限写在调用处

if user.role == "admin":
    run_query(sql)   # 权限判断散落 20 个调用点

加一个"财务只能查不能改"的规则,要改 20 处。权限该随工具走,不该随调用走。

错误 3:schema 和函数分离

函数签名改了,但喂给 LLM 的 JSON schema 没同步,模型按旧 schema 传参,运行时类型错误。工具的"说明书"(schema)和"实现"(函数)必须同源。

四、关键观察:一个工具 = 5 件套

好的工具管理,每个工具是五件事的绑定:

  1. Tool:名字 + 描述(给 LLM 看的语义)。
  2. Schema:输入参数结构(给 LLM 和校验用)。
  3. Permission:谁能调、什么条件下能调。
  4. Executor:真正执行的函数。
  5. Registry:把上面四件注册到一起,统一提供 list / get / call

Registry 是入口,Permission 是闸门,Schema 是契约,Executor 是引擎,Tool 是它们的身份证。 五件不全,工具就不"受控"。

五、最终方案:Registry 长什么样

tools/
├── registry.py       # ToolRegistry:register / list / get / call(带权限+审计)
├── base.py           # Tool 定义(name/description/schema/permission/executor)
├── email.py          # 工具实现:register Tool(...)
├── sql.py            # 同上,permission 标注"只读"
└── schemas.py        # 参数校验(可选,复杂场景)

Tool 定义示例:

Tool(
    name="send_email",
    description="向指定地址发送邮件",
    schema={"to": "string", "body": "string"},
    permission=Permission(roles=["admin", "service"]),
    executor=send_email_impl,
)

Registry.call 做的事:

def call(self, name, **args):
    tool = self.get(name)
    if not tool.permission.allows(current_user):   # 闸门
        raise PermissionDenied(name)
    tool.schema.validate(args)                      # 契约校验
    audit.log("tool_call", name, current_user)      # 审计
    return tool.executor(**args)                    # 执行

调用流(Mermaid):

flowchart TD
    A[Agent 决定调工具] --> R[ToolRegistry.call name]
    R --> P{权限校验}
    P -->|拒绝| DENY[PermissionDenied]
    P -->|通过| V[Schema 校验]
    V --> AU[审计日志]
    AU --> E[Executor 执行]

六、设计权衡:什么时候用 Registry

场景 建议 理由
2–3 个内部工具、无权限要求 直接函数调用 Registry 是负债
多工具 / 有敏感操作 / 要审计 必须 Registry 权限+审计是刚需
工具要开放给外部 / 多 Agent 共享 Registry + 版本 统一治理边界

反过度工程:不要为了 2 个工具上一套带 UI 的注册中心。Registry 的核心是"统一入口 + 权限闸门 + schema 同源",不是某个炫酷框架。

七、总结

  • ✅ 散落函数 = 审计黑洞 + 权限失控;30 个工具没人列得清是真实风险。
  • ✅ 三种翻车:裸函数直调、权限写调用处、schema 与实现分离。
  • ✅ 一个工具 = 5 件套:Tool(身份证)+ Schema(契约)+ Permission(闸门)+ Executor(引擎)+ Registry(入口)。
  • Registry.call 统一做权限校验 + schema 校验 + 审计 + 执行,调用方只传名字和参数。
  • ✅ 反过度工程:Registry 要的是"统一入口+权限+同源",不是复杂框架。

下一篇,钻进 memory/ —— 为什么 Memory 不该属于 Agent,而要独立成服务。(25)


参考资料(带用途说明)

  • 本系列(23)Provider 抽象层设计:本文与(23)同构——(23)收口模型,(24)收口工具,都是"变化轴内聚"思想的体现。
  • 本系列(22)AI Agent 项目应该如何分层:本文是 tools/ 层的展开,讲清边界与接口。
  • 本系列(14)MCP——为什么它正在成为 Agent 的 USB 接口:MCP 本质是工具的"外部注册协议",与本文 Registry 理念相通。
  • 本系列(13)Tool Calling——Agent 为什么要学会用工具:本文是(13)"工具能力"的工程化落地(统一管理)。

本文是 AI Agent 工程实践系列的第 24 篇(第四阶段第四篇)。


系列导航

上一篇:第 23 篇《Provider 抽象层设计》
下一篇:第 25 篇《Memory Service》

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐