像 Agent 一样设计工具
flowchart LR
A["用户目标"] --> B["Agent 理解"]
B --> C["选择工具"]
C --> D["执行动作"]
D --> E["观察反馈"]
E --> B
C --> F["工具太少:做不了"]
C --> G["工具太多:选不动"]
Agent 产品最难的地方,不是给模型接一个工具,而是设计一组它真的会用的工具。工具太少,模型被困住;工具太多,模型开始犹豫、误用、绕路。你要做的不是堆能力,而是替模型设计一个合适的行动空间。
出处与延伸阅读:
- 原文:Seeing like an agent: how we design tools in Claude Code(Claude Blog,2026-04-10)
- 本文基于用户提供的本地译文《Claude Code 构建经验:像 Agent 一样思考》整理和改写。
- 相关主题:Prompt Caching 是 Agent 的地基
这篇文章会讲什么
构建 Agent harness 时,你会很快遇到一个问题:
我到底应该给模型什么工具?
一个万能 bash?一个 code execution?一堆针对具体场景的专用工具?搜索工具、提问工具、计划工具、任务工具、subagent,又该放在哪里?
这篇文章想讨论的是工具设计背后的判断:
- 为什么工具是 Agent 的行动空间
- 为什么结构化工具有时比输出格式指令更可靠
- 为什么工具会随着模型能力变化而过时
- 搜索工具为什么应该让模型主动构建上下文
- progressive disclosure 为什么能扩展能力,却不污染主上下文
先说结论
好的 Agent 工具设计,不是“把所有 API 都暴露给模型”。
它更像给一个聪明但不稳定的同事设计工作台:
- 常用动作要顺手
- 危险动作要有边界
- 不确定时要能提问
- 做复杂任务时要能分解
- 查资料时要能自己找,而不是一次性被喂一堆
- 工具本身要随着模型能力变化而更新
Claude Code 这篇经验最有价值的地方,是它没有把工具设计写成死规则。相反,它反复强调一件事:
观察模型真实输出,比相信自己的工具设计直觉更重要。
Agent 工具设计不是一次建模完成,而是长期调试模型行为。
工具不是功能清单,而是行动空间
我们习惯用人类视角看工具:这个按钮能做什么、这个 API 能返回什么、这个 CLI 有哪些参数。
但 Agent 不是这样感知工具的。
它看到的是一组名称、description、schema、调用结果,以及过往经验里“调用这个工具通常会发生什么”。工具定义会直接影响它如何理解任务、如何拆分步骤、什么时候停下来问人。
所以工具设计首先不是 API 设计,而是行为设计。
一个工具至少要回答四个问题:
| 问题 | 设计含义 |
|---|---|
| 模型什么时候应该用它? | description 要写触发条件,而不只是功能摘要 |
| 用它之前需要知道什么? | schema 要逼近必要上下文 |
| 调用后模型会看到什么? | 结果要可解释、可继续推理 |
| 调错了会怎样? | 错误信息要帮助恢复,而不是只报失败 |
如果这些问题没想清楚,工具越多越危险。模型不是“多一个选择就多一份能力”,它也可能多一份误判。
AskUserQuestion:把提问做成工具
Agent 经常需要向用户补信息。
最简单的方式是让模型直接用自然语言问:“你希望我怎么做?”但这类问题常常有两个问题:
- 用户回答成本高
- 模型输出格式不稳定
Claude Code 团队一开始也尝试过几种方案,比如在退出计划时附带问题,或者要求模型用固定 Markdown 格式输出选项。问题是,这些办法都容易混在原任务里,模型也可能漏格式、加废话、问错粒度。
最后更稳定的做法,是把提问做成一个独立工具。
当模型调用 AskUserQuestion 时,界面弹出结构化问题,Agent 暂停循环,等用户回答后再继续。
这个设计很小,但它体现了 Agent 工具设计的一条重要原则:
如果某种交互对任务很关键,就不要只靠文本格式约定。
工具可以把一个模糊行为变成可控交互。
提问也是一种动作,不只是输出。
Todo 到 Task:工具会随着模型能力变化而过时
早期 Claude Code 需要 Todo 列表。
这是很自然的设计:让模型列任务、逐项完成、保持不跑偏。为了让模型更稳,还可以定期插入系统提醒,告诉它当前目标是什么。
但随着模型能力提升,这类工具可能开始反过来限制模型。
一个更强的模型不只是“更会完成 Todo”,它可能更会在执行中发现原计划不合理、拆出子任务、交给 subagent、调整依赖关系。此时固定 Todo 就变成了束缚。
于是 TodoWrite 被更接近协作单元的 Task Tool 替代。
这件事有一个很重要的提醒:
工具不是永恒基础设施,它是对当前模型能力的补丁。
模型弱的时候,工具要帮它保持轨道。
模型强了以后,工具要帮它协作和表达状态。
如果团队不重新评估工具,就会把过去为旧模型设计的拐杖,变成新模型的绊脚石。
搜索工具:让 Agent 自己构建上下文
Claude Code 早期也尝试过用 RAG 给模型喂上下文。
RAG 有优势:快、可索引、召回稳定。但它也有一个问题:上下文是外部系统替模型选好的。模型不一定知道为什么这些材料重要,也不一定能继续追问。
后来 Claude Code 更偏向给模型搜索工具,比如 grep,让它自己去找文件、自己读、自己组装上下文。
这条路线很有启发。
随着模型变强,它越来越像一个会使用资料室的人,而不是一个只接受资料包的人。你给它一个可以探索的环境,它反而能找到更精确的上下文。
当然,这不意味着 RAG 没用。更好的理解是:
| 方式 | 更适合 |
|---|---|
| 系统主动检索 | 高吞吐、标准问答、召回范围清楚 |
| Agent 主动搜索 | 复杂仓库、路径不清、需要逐步追踪线索 |
| 两者结合 | 先给候选,再让 Agent 自己确认和扩展 |
Coding Agent 的特殊之处在于,代码库是一个高度结构化、可搜索、可验证的世界。让 Agent 自己搜索,往往比一次性塞上下文更自然。
Progressive Disclosure:别把所有知识塞进主上下文
Agent 会遇到大量低频知识。
比如用户问:“Claude Code 的 slash command 怎么配?”或者“某个内部 API 应该怎么查?”这些信息确实有用,但如果全部塞进 system prompt,主任务会被污染。
Progressive disclosure 的思路是:只在需要时逐步披露。
一个典型做法是:
主 Agent 知道有某类资料存在
↓
需要时读取入口文档
↓
入口文档指向更细资料
↓
必要时调用专门 subagent
↓
主 Agent 只拿回最终答案
Claude Code Guide subagent 就是这个思路。用户问 Claude Code 自身用法时,主 Agent 不把所有文档读进来,而是把问题交给一个更懂文档搜索的子 agent。子 agent 在自己的上下文里搜索,再把答案交回主线。
这个设计解决的是上下文卫生问题。
主 Agent 不应该什么都知道。它应该知道什么时候找谁。
工具数量不是越少越好,也不是越多越好
很多团队会在两个极端之间摇摆:
- 极简派:只给 bash,让模型自己搞定一切
- 专用派:每个场景都做一个工具
这两种都有问题。
只给 bash,看起来灵活,但模型要自己记所有命令、参数、错误恢复方式。它很容易把轮次浪费在试错上。
工具太细,看起来可靠,但模型每次都要在一大堆工具里做选择。工具描述稍微重叠,就可能选错。
更实用的判断是看任务频率和错误成本:
| 场景 | 工具设计倾向 |
|---|---|
| 高频、标准、错误成本高 | 做专用结构化工具 |
| 低频、探索性、边界不清 | 给搜索或通用执行能力 |
| 需要人类确认 | 做显式提问或审批工具 |
| 涉及危险动作 | 工具层加权限和护栏 |
| 需要隔离上下文 | 用 subagent 或 skill |
工具设计的目标不是“工具列表漂亮”,而是让模型在关键时刻少猜。
读输出,比猜模型更重要
这篇经验里最朴素的一句话,反而最值得带走:多实验,认真读输出。
因为 Agent 工具设计很难纯靠推理想出来。
你可能觉得某个 schema 很清晰,模型却总是漏参数。
你可能觉得某个工具太专用,模型却特别喜欢用,而且效果很好。
你可能觉得某个提醒很必要,但新模型已经不需要它了,甚至被它束缚。
所以工具迭代应该有一个固定循环:
设计工具
↓
观察模型什么时候调用
↓
读调用前后的推理和结果
↓
记录误用与漏用
↓
修改 description / schema / 返回值 / 护栏
↓
再观察
不要只看成功案例。真正有价值的是失败样本:模型为什么没用这个工具?为什么用了另一个?为什么问了一个很差的问题?为什么把搜索结果读偏了?
这些失败会告诉你,模型眼里的工具世界和你设计文档里的工具世界,差在哪里。
小结
像 Agent 一样设计工具,本质上是暂时放下人类工程师的直觉。
你不是在给自己设计 CLI,也不是在给后端服务设计 API。你是在给一个会阅读、会推理、会犯错、会被上下文影响的模型设计行动空间。
好工具会让模型更果断、更可控、更容易恢复。
坏工具会让模型更困惑、更啰嗦、更会绕路。
所以 Agent 工具设计没有终局答案。它只能随着模型、任务和环境一起长。