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

张开发
2026/4/9 16:35:14 15 分钟阅读

分享文章

Agent 工具系统:如何给 Agent装上双手
引言上一篇我们讲了 Agent Loop 和 Harness 工程——Agent 有了一个身体框架可以持续运转。但如果你仔细想想一个只能说话的 Agent 其实什么都做不了。它能告诉你你应该把第 42 行的 bug 修掉但它自己改不了那一行代码。这就像一个被绑住双手的专家脑子清楚得很但你让他修电脑他只能嘴上指挥。工具系统就是给 Agent 松绑的那把钥匙。工具调用的本质翻译官模式很多人一听AI 调用工具脑子里浮现的是某种复杂的插件系统。其实本质简单得令人发指模型在回复中说我要用read_file工具参数是path: main.py你的代码Harness看到这句话去真的读了main.py的内容把文件内容作为工具结果喂回给模型模型拿到内容继续思考下一步就这么简单。模型本身并不能真的读文件——它只是表达了读文件的意图是你写的代码帮它执行了这个意图。类比一下模型是一个只会说英语的老板工具系统是翻译官。老板说Read that document翻译官跑去把文件拿来、翻译好、递回去。老板从头到尾没碰过那份文件。模型发出 tool_use 请求Harness 执行工具返回 tool_result模型继续推理Claude Code 的核心工具箱Claude Code 给模型配备了一套精心设计的工具每一个都对应开发者日常的一个基本操作工具做什么类比bash执行 shell 命令开发者的终端read_file读取文件内容打开文件看一眼write_file写入文件新建或覆盖一个文件edit_file精确替换文件中的文本用编辑器改几行代码glob按模式搜索文件名find . -name *.pygrep搜索文件内容在代码里搜关键字注意这些工具的特点每一个都很小、很具体、只做一件事。没有一个万能工具可以同时读写文件还能跑命令。这是有意为之的后面会讲为什么。调度表模式优雅的路由机制工具定义好了Agent Loop 收到模型的工具调用请求后怎么知道该执行哪个函数最朴素的做法是 if-elseiftool_namebash:resultrun_bash(args)eliftool_nameread_file:resultrun_read(args)eliftool_namewrite_file:resultrun_write(args)# ... 继续堆叠能跑但丑。每加一个工具就要多写一个分支代码越来越长改起来还容易漏。项目中用的是调度表模式dispatch map——一个字典把工具名映射到处理函数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]),}调用的时候一行搞定handlerTOOL_HANDLERS.get(block.name)outputhandler(**block.input)ifhandlerelsefUnknown tool:{block.name}新增工具往字典里加一行。删工具删一行。工具的定义和调度完全解耦。这个模式在 Web 框架的路由表、命令行工具的子命令分发、甚至游戏引擎的事件系统里都能看到。本质上就是用数据结构代替控制结构——用一张表代替一堆 if-else。打开项目中的s02_tool_use.py你会看到 Agent Loop 的代码和s01几乎一模一样——唯一的变化就是工具数组从一个变成了四个加了一张调度表。循环没变只是手变多了。这就是好架构的力量扩展能力不需要修改核心逻辑。安全边界有手不能乱摸给 Agent 工具就像给实习生权限——你希望他能干活但不希望他把生产数据库删了。项目中实现了两层安全防护第一层危险命令黑名单dangerous[rm -rf /,sudo,shutdown,reboot, /dev/]ifany(dincommandfordindangerous):returnError: Dangerous command blocked这是最粗暴但最有效的防线。模型如果尝试执行rm -rf /直接拦截返回错误。第二层路径逃逸检查defsafe_path(p:str)-Path:path(WORKDIR/p).resolve()ifnotpath.is_relative_to(WORKDIR):raiseValueError(fPath escapes workspace:{p})returnpath这一层更精细。模型传入的文件路径先解析成绝对路径处理掉../之类的相对路径然后检查它是否还在工作目录内。如果模型试图读/etc/passwd或者用../../跳出项目目录直接报错。类比一下第一层是门口保安看到可疑人物直接拦第二层是门禁系统只允许刷卡进入自己有权限的楼层。实际的 Claude Code 产品里安全机制远比这复杂——有权限分级、用户确认弹窗、沙箱执行等。但核心思路是一样的默认不信任模型的操作意图每一步都校验。工具设计的三个原则回头看这套工具体系有三个设计原则值得记住原则一原子化——一个工具做一件事read_file只读文件write_file只写文件。不会有一个file_manager工具同时承担读写删改搜索五种功能。为什么因为模型在选择工具时依赖的是工具描述。一个功能明确的工具模型很容易理解什么时候该用它。一个瑞士军刀式的工具模型反而容易选错参数、用错模式。原则二可组合——工具之间可以串联模型可以先用glob找到所有.py文件再用read_file逐个读取再用edit_file修改其中需要改的。这种组合不是你提前编排好的——是模型自己决定的调用顺序。原子化的工具天然支持组合。就像 Unix 哲学里的管道ls | grep | sort每个命令只做一件事但组合起来威力巨大。原则三描述清晰——模型靠描述理解工具模型不会看你的 Python 代码来理解工具怎么用。它看的是你在工具定义里写的description和参数的input_schema。{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 装一个大脑——规划能力和子代理机制。

更多文章