Agent 工具系统:如何给 Agent 装上双手

引言

上一篇我们讲了 Agent Loop 和 Harness 工程------Agent 有了一个"身体框架",可以持续运转。但如果你仔细想想,一个只能"说话"的 Agent 其实什么都做不了。它能告诉你"你应该把第 42 行的 bug 修掉",但它自己改不了那一行代码。

这就像一个被绑住双手的专家:脑子清楚得很,但你让他修电脑,他只能嘴上指挥。

工具系统就是给 Agent 松绑的那把钥匙。

工具调用的本质:翻译官模式

很多人一听"AI 调用工具",脑子里浮现的是某种复杂的插件系统。其实本质简单得令人发指:

  1. 模型在回复中说:"我要用 read_file 工具,参数是 path: main.py"
  2. 你的代码(Harness)看到这句话,去真的读了 main.py 的内容
  3. 把文件内容作为"工具结果"喂回给模型
  4. 模型拿到内容,继续思考下一步

就这么简单。模型本身并不能真的读文件------它只是"表达了读文件的意图",是你写的代码帮它执行了这个意图。

类比一下:模型是一个只会说英语的老板,工具系统是翻译官。老板说"Read that document",翻译官跑去把文件拿来、翻译好、递回去。老板从头到尾没碰过那份文件。

flowchart LR A["模型发出 tool_use 请求"] --> B["Harness 执行工具"] B --> C["返回 tool_result"] C --> D["模型继续推理"] style A fill:#fff7e6,stroke:#fa8c16 style B fill:#e8f4fd,stroke:#1890ff style C fill:#f6ffed,stroke:#52c41a style D fill:#fff7e6,stroke:#fa8c16

Claude Code 的核心工具箱

Claude Code 给模型配备了一套精心设计的工具,每一个都对应开发者日常的一个基本操作:

工具 做什么 类比
bash 执行 shell 命令 开发者的终端
read_file 读取文件内容 打开文件看一眼
write_file 写入文件 新建或覆盖一个文件
edit_file 精确替换文件中的文本 用编辑器改几行代码
glob 按模式搜索文件名 find . -name "*.py"
grep 搜索文件内容 在代码里搜关键字

注意这些工具的特点:每一个都很小、很具体、只做一件事。没有一个"万能工具"可以同时读写文件还能跑命令。这是有意为之的,后面会讲为什么。

调度表模式:优雅的路由机制

工具定义好了,Agent Loop 收到模型的工具调用请求后,怎么知道该执行哪个函数?

最朴素的做法是 if-else:

python 复制代码
if tool_name == "bash":
    result = run_bash(args)
elif tool_name == "read_file":
    result = run_read(args)
elif tool_name == "write_file":
    result = run_write(args)
# ... 继续堆叠

能跑,但丑。每加一个工具就要多写一个分支,代码越来越长,改起来还容易漏。

项目中用的是调度表模式(dispatch map)------一个字典,把工具名映射到处理函数:

python 复制代码
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

调用的时候一行搞定:

python 复制代码
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"

新增工具?往字典里加一行。删工具?删一行。工具的定义和调度完全解耦。

这个模式在 Web 框架的路由表、命令行工具的子命令分发、甚至游戏引擎的事件系统里都能看到。本质上就是用数据结构代替控制结构------用一张表代替一堆 if-else。

打开项目中的 s02_tool_use.py,你会看到 Agent Loop 的代码和 s01 几乎一模一样------唯一的变化就是工具数组从一个变成了四个,加了一张调度表。循环没变,只是手变多了。 这就是好架构的力量:扩展能力不需要修改核心逻辑。

安全边界:有手,不能乱摸

给 Agent 工具就像给实习生权限------你希望他能干活,但不希望他把生产数据库删了。

项目中实现了两层安全防护:

第一层:危险命令黑名单

python 复制代码
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
    return "Error: Dangerous command blocked"

这是最粗暴但最有效的防线。模型如果尝试执行 rm -rf /,直接拦截,返回错误。

第二层:路径逃逸检查

python 复制代码
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

这一层更精细。模型传入的文件路径,先解析成绝对路径(处理掉 ../ 之类的相对路径),然后检查它是否还在工作目录内。如果模型试图读 /etc/passwd 或者用 ../../ 跳出项目目录,直接报错。

类比一下:第一层是门口保安,看到可疑人物直接拦;第二层是门禁系统,只允许刷卡进入自己有权限的楼层。

实际的 Claude Code 产品里,安全机制远比这复杂------有权限分级、用户确认弹窗、沙箱执行等。但核心思路是一样的:默认不信任模型的操作意图,每一步都校验

工具设计的三个原则

回头看这套工具体系,有三个设计原则值得记住:

原则一:原子化------一个工具做一件事

read_file 只读文件,write_file 只写文件。不会有一个 file_manager 工具同时承担读写删改搜索五种功能。

为什么?因为模型在选择工具时依赖的是工具描述。一个功能明确的工具,模型很容易理解什么时候该用它。一个瑞士军刀式的工具,模型反而容易选错参数、用错模式。

原则二:可组合------工具之间可以串联

模型可以先用 glob 找到所有 .py 文件,再用 read_file 逐个读取,再用 edit_file 修改其中需要改的。这种组合不是你提前编排好的------是模型自己决定的调用顺序。

原子化的工具天然支持组合。就像 Unix 哲学里的管道:ls | grep | sort,每个命令只做一件事,但组合起来威力巨大。

原则三:描述清晰------模型靠描述理解工具

模型不会看你的 Python 代码来理解工具怎么用。它看的是你在工具定义里写的 description 和参数的 input_schema

python 复制代码
{"name": "edit_file",
 "description": "Replace exact text in file.",
 "input_schema": {
     "type": "object",
     "properties": {
         "path": {"type": "string"},
         "old_text": {"type": "string"},
         "new_text": {"type": "string"}
     },
     "required": ["path", "old_text", "new_text"]
 }}

这段描述就是工具的"使用说明书"。写得好,模型用得准;写得含糊,模型会误用。在实际项目中,工具描述的打磨往往比工具实现本身花的时间还多。

对应代码:s01 与 s02

在项目的 agents/ 目录下:

  • s01_agent_loop.py:只有一个 bash 工具的最简 Agent。展示了核心循环。
  • s02_tool_use.py:扩展到四个工具(bash、read_file、write_file、edit_file),引入了调度表模式。

对比这两个文件,你会发现核心的 agent_loop 函数几乎没变------变化全在工具定义和调度表上。这不是巧合,这是架构设计的目标:开闭原则的活教材。对扩展开放(加工具),对修改关闭(不动循环)。

小结

工具系统把 Agent 从"只能说"变成了"能做事"。调度表模式让工具扩展变得轻而易举,安全边界确保 Agent 不会越权操作,而原子化 + 可组合 + 描述清晰的设计原则保证了工具体系的可维护性和可扩展性。

但有了手还不够。你给一个人一堆工具,他如果没有计划、没有条理,干着干着就会乱套------读了一堆文件但忘了为什么要读,改了一处代码但忘了还有三处要改。

下一篇,我们聊聊怎么给 Agent 装一个"大脑"------规划能力和子代理机制。

相关推荐
ly甲烷3 小时前
智能体Skills详细介绍与上手指南
ai·agent·skills
有趣的老凌3 小时前
一篇文章带你了解 Agent Skills —— 告别AI“失控”
前端·agent·claude
handsomestWei3 小时前
【开源】从设计文档到可交付技术交底书:专利.Skill
开源·大模型·agent·skill·clawhub·skillhub
刀法如飞14 小时前
Agentic Workflow 设计与实战指南
架构·agent·ai编程
tkevinjd14 小时前
基于LangChain的简易智能旅游助手Agent
langchain·agent
码云之上17 小时前
上下文工程实战:解决多轮对话中的"上下文腐烂"问题
前端·node.js·agent
攀登的牵牛花17 小时前
我把 Gemma4:26b 装进 M1 Pro 后,才看清 AI 编程最贵的不是模型费,而是工作流
前端·agent
LucaJu18 小时前
Agent Skill 踩坑记录 | SpringBoot 打包后 Skill 加载失败问题排查与解决
agent·skill·spring ai·spring ai alibaba
DigitalOcean19 小时前
拒绝 GPU 集群资源浪费:教你打造自动化降本的 AI 运维 Agent
agent·自动化运维