Agent 工具调用基础:让 Agent 真正动手做事 · 第 3 / 6 节

第 3 课:五类常用工具:读、写、执行、搜索、调用

学习目标:

  • 按后果大小把常见工具分成五类,并说出每类的典型签名
  • 解释为什么执行命令类工具的风险和其他四类不在同一量级
  • 说明搜索类工具为什么返回命中片段而不是整个文件

前置要求:完成第 2 课,理解一次工具调用的往返结构 | 上一课 第 2 课 << | 下一课 第 4 课 >>

先看一张表

工具典型入参返回什么出错时最糟能糟到哪
读文件path文件内容(字符串)读到不该读的文件,信息泄露
写文件pathcontent成功/失败状态覆盖别人还没保存的工作
执行命令commandstdout/stderr/退出码删库、发请求、装恶意包,后果不可逆
搜索querypath命中位置列表 + 片段返回太多把上下文挤爆,或者漏掉关键结果
调用外部 API结构化参数(因服务而异)JSON/错误对象花了别人的钱、发错了消息、拿到过期数据

这张表按什么排序?不是字母顺序,是「一次调用能造成多大范围的伤害」,也就是这次调用的爆炸半径(blast radius):只读工具的爆炸半径几乎为零——读错文件顶多是这一轮对话跑偏;写文件能覆盖已有内容;执行命令能对整个系统做任意事情。往下逐类展开,你会发现每一类除了「能干什么」之外,还各自带着一个只有这一类才会踩的坑。

读:最安全,但不是零风险

读文件工具的签名通常长这样:

返回值就是文件内容本身,通常还带上行号方便模型后续引用:

1  export function add(a, b) {2    return a + b;3  }

读文件不改变任何状态,模型读错了、读多了,最坏结果就是这一轮对话里多了些不相关的内容,模型自己会发现读错然后重新读。这是它被称为「最安全」的原因——不是说它没有风险,而是说风险出不了这次对话的范围。

真正的风险在于读到了不该读的文件。如果 Agent 有权限读取 ~/.ssh/id_rsa 或者项目里的 .env,一次看似无害的「帮我看看这个目录下有什么」就可能把密钥内容原样搬进对话上下文——之后只要这段上下文被模型输出、被记录日志,或者被后续某个「调用外部 API」的工具带出去,泄露就已经发生了。这也是为什么读文件工具几乎总要配合路径白名单或沙箱使用,而不是「反正是只读,随便给权限」。第 5 课会具体讲这类边界怎么设。

写:后果不对称的起点

写文件工具比读文件多一个参数、少一份安全感:

返回值通常很简单,一个状态而已:

问题不在返回值,在这次调用本身。读文件出错了,你重新读一次就好,状态没变。写文件出错了——比如模型把 path 填错,或者 content 里少复制了一半——原来那个文件的内容已经被覆盖,撤销不了,除非有版本控制或者备份。这就是「只读工具与写工具后果不对称」:两类工具的调用形状几乎一样(都是一个 path 加几个参数),但一类可以随便重试,另一类每次调用都在赌一把。

所以负责任的写工具设计会加一层保护,比如要求「编辑前必须先读过这个文件」(防止模型凭记忆瞎改),或者返回旧内容和新内容的 diff 而不只是「成功」两个字,让调用方(也就是宿主应用)有机会在真正落盘前展示改动。这些不是本课的重点,第 4 课讲接口设计时会展开。

执行:五类里独一档的风险

执行命令类工具的签名看起来最朴素:

一个字符串进去,stdout、stderr、退出码出来:

问题是这个 command 字段本质上是一个开放式入口——它不是「删除某个文件」或「读取某一行」这种被 schema 约束住的具体操作,而是任意一段 shell 脚本。rm -rfcurl 把数据发到外部服务器、npm install 装一个被投毒的包,全都能装进这一个字符串里。其余四类工具(读、写、搜索、调用 API)无论签名怎么设计,能做的事都被参数结构限定死了;执行命令工具的能力边界等于整个操作系统的能力边界。这就是它在五类里独一档的原因:不是「风险更高一点」,是风险的量级完全不同。

正因为如此,官方文档才会专门为这一类工具设计操作系统级的隔离:文件系统访问和网络访问是两个独立的沙箱层,即使模型被提示注入操纵、执意要跑一条危险命令,操作系统边界也照样拦住,不依赖模型「愿不愿意」配合1。这种设计动机说得很直接——目标就是保证即便提示注入成功了,破坏也出不了沙箱2。第 5 课会具体讲这层隔离怎么配置,这里先记住一点:凡是「执行命令」这个签名出现的地方,默认把它当成五类里最需要额外约束的一类去对待。

搜索:返回位置,不返回整个世界

搜索工具(比如按关键词或正则在代码库里找)的签名往往带着「限制返回量」的参数:

返回值不是文件本身,是「在哪找到的、上下文长什么样」:

如果这个工具直接把命中文件的全部内容塞回来,会出两个问题。第一个是省 token 的问题:一次搜索命中 50 个文件,每个文件几百行,全部塞进上下文,这一轮对话的输入就被这一个工具调用吃光了,后面模型还要不要继续干活3?写工具描述、控制输入输出的边界,本身就是让工具好用的基本要求之一4。第二个问题更关键:搜索的本意不是「把所有可能相关的内容都读一遍」,而是「帮模型定位它接下来该去哪继续看」。返回命中位置加一小段上下文片段,模型看完这些片段,自己判断「这几个结果里,第 2 条像是我要找的,我再单独读一下那个文件的完整内容」——这就是搜索工具和读文件工具配合工作的方式:搜索负责缩小范围,读文件负责拿到细节。返回「命中位置」而不是「整个文件」,给的正是这个可以继续跟进的线索,而不是一次性倾倒所有可能有用的内容。

调用外部 API:失败是常态,不是例外

前四类工具基本都在本地系统里打转,调用外部 API 这一类不一样——它要跨网络,跨一个你不掌控的服务:

正常返回是这样:

但外部服务会限流、会超时、会因为权限不够拒绝请求、会在你调用的间隙修改了自己的接口。这些不是「意外情况」,是这一类工具运行时的常态。真正决定这个工具好不好用的,不是「正常情况下返回什么」,是「失败的时候返回什么」:

这条错误信息不是给你看的,是给模型看的——模型下一步该重试还是该换个策略,取决于它能不能读懂这个 error 字段。MCP 的官方规范专门把这点写进了协议:客户端应当把工具执行过程中产生的错误反馈给模型,让模型有机会自我纠正、重新尝试5。也就是说,一个把 429 状态码直接吞掉、只返回一句「调用失败」的工具,是在剥夺模型自我修正的机会;一个把 retry_after 这种具体信息带回来的工具,才是把「失败」当成正常工作流程的一部分在设计。

调用外部 API 这一类还牵涉到另一层风险:如果这个 Agent 同时能读到私有数据、又暴露在不可信内容(比如用户粘贴的一段网页文字)、又有对外发消息或发请求的能力,这三者凑在一起,就是安全研究里常说的「致命三要素」(lethal trifecta)——攻击者不需要攻破你的系统,只需要在你会读到的内容里藏一句指令,让 Agent 自己把私有数据带出去6。这个话题第 5 课会专门展开,这里先知道:调用外部 API 是这条链路上最后也是最关键的一环,因为它是数据真正离开你系统的出口。

小结

  • 五类工具的风险不是均匀分布的:读文件后果最轻,写文件开始不可逆,执行命令的爆炸半径等于整个操作系统,搜索和调用外部 API 各自有独立的坑
  • 只读工具和写工具的核心区别在于能不能安全重试——读错了重读就好,写错了原内容可能已经回不来
  • 执行命令类工具之所以需要操作系统级沙箱兜底,是因为它的 command 参数是开放字符串,不像其他四类那样被 schema 结构限定住能做的事12
  • 搜索工具返回命中位置加片段而不是整个文件,一是省 token,二是把「定位」和「读取细节」这两步分开,给模型一条可以继续跟进的线索3
  • 调用外部 API 的工具要把失败信息(错误类型、能不能重试)原样带回给模型,而不是吞掉——失败在这一类工具里是常态,不是例外5

下一课,我们把这五类工具的「签名」拆开来看:一个好的工具名字、description、参数 schema、返回值该怎么写,才能让模型第一次就调对。

>> 第 4 课:工具接口设计:名字、描述、参数、返回值

Footnotes

  1. Configure the sandboxed Bash tool - Claude Code Docs — https://code.claude.com/docs/en/sandboxing 2

  2. Making Claude Code more secure and autonomous with sandboxing - Anthropic Engineering — https://www.anthropic.com/engineering/claude-code-sandboxing 2

  3. Introducing advanced tool use on the Claude Developer Platform | Anthropic Engineering — https://www.anthropic.com/engineering/advanced-tool-use 2

  4. Writing effective tools for AI agents—using AI agents | Anthropic Engineering — https://www.anthropic.com/engineering/writing-tools-for-agents

  5. Tools - Model Context Protocol — https://modelcontextprotocol.io/docs/concepts/tools 2

  6. The lethal trifecta for AI agents - Simon Willison's Weblog — https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/

练习

01

下面是三个工具的调用结果草稿,每个都有问题。指出问题出在哪一类工具身上(读/写/执行/搜索/调用 API),说明为什么这样设计不合适,并给出你会怎么改。

Level 1:给工具挑返回值
  1. search_code 返回:{ "content": "<50 个文件的完整源码拼在一起,共 8000 行>" }
  2. write_file 返回:{ "success": true }(不带任何 diff 或旧内容信息)
  3. send_email 失败时返回:{ "error": "failed" }
完成标准 · 本地勾选
02

你要给 Agent 接一个工具:定期检查一个第三方物流 API 的运单状态,如果状态变成「异常」,就把运单号和异常原因写进本地一个 alerts.log 文件。

Level 2:给一个新场景选工具类型并设计签名

这个任务里其实包含了不止一类工具。写出:

  1. 需要用到本课五类里的哪几类?各自负责什么?
  2. 给「调用外部 API 查询运单状态」这个工具写一个 JSON 格式的 input_schema,至少包含运单号参数(接口设计的系统讲法在下一课,这里照着本课出现过的签名格式模仿即可)
  3. 这个工具查询失败时(比如运单号不存在、接口超时),返回值该长什么样?
完成标准 · 本地勾选

我的笔记

记下想法、痛点、没懂的地方。只写进这门课的附录,正课文件不动。