【学习笔记】工具设计的艺术 —— 你改了一个参数名,Agent 性能掉了 30%-05/15
上一篇聊完上下文管理,Agent 能「想得更久」了。但光能想还不够——它还得能「做」。
工具,就是 Agent 伸出去的那双手。
我第一次认真思考工具设计,是因为一件让我很困惑的事。
我当时在调试一个 Claude Code 任务,发现 Agent 在读文件的时候,明明有更高效的工具可用,但它偏偏绕一大圈用 Bash 的 cat 命令。我以为是指令写得不够清楚,反复改系统提示,效果有限。
后来和 Anthropic 工程团队的一篇分享对上了:模型在训练过程中对特定工具名称和描述形成了一种「肌肉记忆」。cat 这个命令,模型见过几十亿次训练数据,用起来极其流畅。你自定义的 read_file_efficiently 工具,对模型来说是陌生的,调用决策会多一层摩擦。
这是工具设计里最反直觉的地方,也是最容易踩的坑。今天就从这里开始。
一、工具是 Agent 唯一的手
这句话听起来像废话,但真正想清楚它的含义,会改变你设计工具的方式。
Agent没有直接感知世界的能力。它不能「看」文件,它只能调用读文件的工具;它不能「跑」代码,它只能调用执行代码的工具;它不能「上网」,它只能调用发 HTTP 请求的工具。
1.1 Agent 能做什么,完全取决于你给它什么工具。
工具设计是 Harness 里最直接决定 Agent 能力边界的环节。工具设计得好,它干起活来利落;工具设计得差,它要么绕路、要么乱来、要么卡死在那儿什么也做不了。
二、「肌肉记忆」现象
先把这个反直觉的东西讲清楚。
2026 年初,OpenAI 发布了 Codex 的 Harness 工程详解。里面提到一个令人印象深刻的细节:Codex-5.3 对 apply_patch 这个工具格式形成了深度依赖。
apply_patch 是 OpenAI 为 Codex 设计的代码修改工具,格式有点像 unified diff,但有自己的特殊语法。模型在大量训练数据中反复见到这个格式,逐渐「学会」了用这种方式表达代码修改意图。
然后有一次 OpenAI 工程师做了一个小小的格式调整——改了 apply_patch 的某个语法细节。
结果:Agent 在代码修改任务上的表现明显下滑,工程师折腾了好几天才找到根因。
不是bug,不是prompt 问题,是模型对工具接口形成了「肌肉记忆」,接口变了,肌肉记忆和现实对不上,性能就掉。
这个现象的工程含义很深:
2.1 工具名字很重要。 用模型见过的惯用名称,read_file、write_file、run_command,比自创的 execute_filesystem_operation 更容易被模型正确调用。
2.2 工具接口一旦稳定,不要轻易改。 就算你觉得新的参数名更清晰、更合理,改了可能带来意想不到的性能退化。要改,就得同步更新训练或 fine-tuning。
2.3 描述要对准模型的已有认知。 工具 description 里的措辞,尽量和模型训练集里的语言习惯对齐。「Reads the content of a file at the given path」比「Retrieves textual data from a filesystem node」效果好,不是因为更准确,是因为更眼熟。
这不是让你放弃给工具起好名字,而是要在「人类可读性」和「模型熟悉度」之间找平衡。特别是在生产环境,稳定性比优雅性更重要。
三、原子工具 vs 通用工具
工具设计里另一个核心决策:什么时候用原子工具,什么时候用通用工具(Bash)?
原子工具:单一职责,做一件事,返回结构化结果。
-
read_file(path)→ 返回文件内容 -
write_file(path, content)→ 写入文件 -
search_code(pattern, directory)→ 返回匹配列表
通用工具:Bash/Shell,什么都能做,返回原始命令输出。
表面上看,Bash 更强大——会的命令无限,灵活性极高。但实际工程里,原子工具有几个 Bash 无法替代的优势:
可预测性。 原子工具的输入输出是结构化的,错误处理确定性强。Bash 命令的输出是字符串,模型需要解析,解析可能出错。
可审计性。 看日志时,read_file("/etc/passwd") 比 cat /etc/passwd 更容易追踪。特别是在权限审计场景里,原子工具的调用记录更干净。
可沙箱化。 原子工具可以在设计层面限制操作范围(比如 read_file 只读工作目录内的文件),Bash 是万能的,限制起来更复杂。
但Bash 也有它不可替代的位置:作为后备工具,处理原子工具没覆盖到的场景。
Anthropic 的实践经验是:原子工具为主,Bash 为后备。
大概率的情况是:80% 的操作用原子工具,确定性好、性能稳;剩下 20% 的奇怪需求让 Agent 自己想办法用 Bash 解决,别把所有边界情况都变成原子工具——那会让工具列表膨胀到无法管理。
我自己踩过一个坑:早期为了「精确控制」,把所有操作都做成原子工具,包括「查某个文件的行数」「获取目录树」「检查某个端口是否开放」。工具列表长到一百多个,模型选工具的时候经常选错,因为太多了,分不清楚。
后来砍回到 15 个核心原子工具 + Bash 后备,选工具的准确率明显提升。

四、工具描述是 Harness 里的「软规则」
前一篇聊 Fowler 分类法,Guide × Inferential 象限放的是「软规则」——CLAUDE.md 里写的那些供模型自己理解、自己权衡的规则。
工具描述(description)就是 Guide × Inferential 的另一个战场。
模型不是靠工具名称决定调不调用,它靠的是描述。描述写得好,模型准确判断使用时机;描述写得差,模型要么该用不用,要么乱用。
几个具体原则:
4.1 描述要说「什么时候用」,不只是「是什么」。
差的描述:
read_file: Reads a file from the filesystem.
好的描述:
read_file: Read the complete content of a file. Use when you need
to inspect file content before editing, or verify changes after
writing. For large files (>1000 lines), prefer reading specific
line ranges to avoid context overflow.
前者告诉模型工具是什么,后者告诉模型什么时候用、怎么用、有什么限制。
4.2 描述要包含「不该用」的场景。
bash: Execute shell commands. Use for operations not covered by
dedicated tools. DO NOT use for reading/writing files (use
read_file/write_file instead) or searching code (use
search_code). Reserve for: package management, process control,
network operations, system info.
明确告诉模型 Bash 的禁用场景,减少它偷懒走捷径。
4.3 参数描述要精确,带例子。
path (string): Absolute or relative path to the file.
Examples: "/workspace/src/main.py", "src/utils/helper.ts"
DO NOT use ~ or environment variables in the path.
模型会按照 few-shot 的逻辑参考例子,加例子比加解释更有效。
五、沙箱设计
这是工具设计里最容易被忽视、但出问题最严重的部分。
Agent有了 Bash 工具,理论上能做系统上几乎任何事。这听起来很强大,实际上是个定时炸弹。
2025年7月,Replit出现了一起「Rogue Agent」事件:Agent 在执行某个清理任务时,误判了生产数据库的清理范围,执行了 DROP TABLE,然后为了掩盖错误,继续伪造了部分记录。等工程师发现时,损失已经造成了。
沙箱设计就是要在 Agent 和危险操作之间建一道确定性的防线。不是靠 prompt 里写「不要删数据库」,而是靠 Harness 机制确保它物理上做不到。
几个核心实践:
命令白名单,不是黑名单。
黑名单思路:列出禁止的命令(rm -rf、DROP TABLE……),不在名单里的都允许。
白名单思路:列出允许的命令,不在名单里的全部拒绝。
生产环境必须用白名单。黑名单永远列不完,攻击者(或者犯错的 Agent)总能绕过去。
允许清单示例:
ALLOWED_COMMANDS = [
"ls", "find", "grep", "cat", "head", "tail",
"git status", "git diff", "git log",
"npm test", "npm run build",
"python -m pytest"
]
网络隔离。
默认情况下,Agent 不应该有网络访问权限。需要联网的操作通过专门的原子工具(fetch_url、search_web)实现,这些工具有自己的权限控制和日志。
Bash 里能跑 curl,但 curl 不在白名单里——这就是分层控制的意义。
「Elevated」执行模式。
某些操作不可避免地需要更高权限(写生产配置、触发部署流程)。对这类操作,Harness 不是禁止,而是要求人工审批后才能执行。
具体实现:工具被调用时,Harness 检测到「危险操作」标记,暂停执行,推送审批请求给工程师。工程师确认后,操作才真正执行。
这就是上篇提到的 Fowler「In the Loop」模式——不是让人直接改产物,而是让人做关键决策的守门人。
资源限制。
CPU 时间上限、内存上限、文件大小上限、单次执行超时。
不是为了省钱,是为了防止 Agent 陷入死循环(比如递归搜索整个文件系统)时把整个系统拖垮。

六、文件系统是最重要的原语
说完了工具本身的设计,有一件更底层的事值得单说:文件系统是 Agent Harness 最基础的原语。
不是数据库,不是消息队列,不是 API。就是最朴素的文件系统。
原因不复杂:
6.1 持久性。 上下文窗口是临时的,数据库需要连接,但文件系统是 Agent 停掉再起来也能读到的稳定状态。进度文件、任务列表、中间结果——全存文件。
6.2 可协作性。 工程师能直接读文件、改文件、审查 Agent 的工作。这种透明度在其他存储方式里很难实现。
6.3 天然版本控制。 代码文件在 Git 里,Agent 的每次提交都是一个审计日志。出问题时 git diff 一眼看清楚它改了什么。回滚的时候 git reset 一行命令。
6.4 与工具系统的深度集成。read_file、write_file、search_code 这些最核心的工具,全部围绕文件系统展开。Agent 的「感知」和「行动」都通过文件系统完成。
Anthropic 的工程实践里,文件系统被定义为 Agent 的「工作区」(workspace)——一个有边界的、可读写的、有版本历史的空间。Harness 负责管理这个工作区的边界,确保 Agent 只在授权范围内操作。
Claude Code 就是这么设计的。它以当前目录为工作区,默认只操作这个目录内的文件,需要更大范围时需要明确授权。工作区边界既是能力边界,也是安全边界。
七、把三条线拉通
工具设计有三条线要同时考虑:
能力线:Agent 能做什么?
│ 原子工具覆盖 80% 常用操作
│ Bash 后备处理长尾
└──────────────────────────────────────────→
质量线:Agent 做得对不对?
│ 工具描述写清楚使用时机
│ 参数带例子,带禁用场景
└──────────────────────────────────────────→
安全线:Agent 不会做坏事?
│ 命令白名单
│ 网络隔离
│ Elevated 审批
└──────────────────────────────────────────→
三条线缺任何一条,要么 Agent 能力不够用,要么干活乱来,要么哪天给你来个 rm -rf /。
设计工具集的时候,我自己的流程是:先拉能力线(哪些操作必须有原子工具),再考虑质量线(每个工具的 description 要怎么写),最后过一遍安全线(哪些操作需要沙箱,哪些需要 Elevated)。
顺序不能反。能力没设计好就去想安全,会把自己绕进去。
下一篇聊长程自主执行。工具有了,上下文也管好了,但 Agent 跑着跑着还是会「走神」——不是能力不够,是缺少让它保持专注的机制。Ralph Loop 和 Feature List 就是干这个的。
参考文献:
更多推荐



所有评论(0)