在构建一个智能体框架(agent harness)时,最困难的部分之一,就是设计它的行动空间(action space)。
Claude 通过“工具调用(Tool Calling)”来行动,但在 Claude API 里,工具可以用很多不同的原语来构建,比如 bash、skills,以及最近加入的代码执行(想了解 Claude API 上“程序化工具调用”的更多内容,可以阅读 @RLanceMartin 的新文章)。
面对这么多选择,你要如何设计智能体的工具?你只需要一个工具,比如代码执行或 bash 就够了吗?还是你会准备 50 个工具,每个对应智能体可能遇到的一种用例?
为了让自己更贴近模型的思维方式,我喜欢想象:你被给了一道很难的数学题。你会希望有什么工具来帮助你解题?答案取决于你自己的能力!
纸和笔是最低配置,但你会受限于手算速度和准确度。计算器更好,但你必须知道如何使用它更高级的功能。最快、最强的选择是一台电脑,但你也必须知道如何编写并执行代码来使用它。
这个框架对于设计智能体非常有用。你想给智能体提供与它自身能力相匹配、形状合适的工具。但你怎么知道它的能力到底是什么?你需要去观察、阅读它的输出、不断实验。你需要学会像智能体一样去“看”。
下面是我们在构建 Claude Code 的过程中,通过认真观察 Claude 得到的一些经验教训。
改进追问能力与 AskUserQuestion 工具
在构建 AskUserQuestion 工具时,我们的目标是提升 Claude 提问的能力(通常被称为 elicitation,即“引出信息/追问”)。
当然,Claude 也可以直接用纯文本提问,但我们发现,用户回答这些问题常常会觉得花了不必要的时间。我们能不能降低这种摩擦,提高用户与 Claude 之间的沟通带宽?
尝试 #1——修改 ExitPlanTool
我们最先尝试的是:给 ExitPlanTool 加一个参数,让它在输出计划的同时,附带一个问题数组。这是最容易实现的方法,但它让 Claude 感到困惑,因为我们同时在要求它给出计划,又要求它提出关于计划的一组问题。如果用户的回答与计划内容冲突怎么办?Claude 需要调用 ExitPlanTool 两次吗?我们需要换一种思路。
(你可以在我们关于提示缓存(prompt caching)的文章里读到更多我们为什么要做 ExitPlanTool 的原因。)
尝试 #2——改变输出格式
接着我们尝试修改 Claude 的输出指令,让它用一种稍作调整的 markdown 格式来提问。比如要求它输出一个项目符号问题列表,并在方括号里给出可选项,然后我们就可以解析这种格式,把问题渲染成用户界面。
这种方法是我们能做的最通用改动,而且 Claude 似乎也能按这种格式输出,但它并不可靠。Claude 有时会多加几句话、有时会漏掉选项、有时会完全换一种格式。
尝试 #3——AskUserQuestion 工具
最后,我们决定创建一个工具,让 Claude 可以在任何时候调用它,但我们尤其会在“计划模式(plan mode)”时提示它去用。当工具触发时,我们会弹出一个模态框来展示问题,并阻止智能体循环继续运行,直到用户作答。
这个工具让我们能够要求 Claude 输出结构化内容,也帮助我们确保 Claude 会给用户提供多个选项。它还为用户提供了组合使用这种功能的方式,例如在 Agent SDK 中调用它,或在 skills 里引用它。
最重要的是,Claude 似乎很喜欢调用这个工具,而且我们发现它的输出效果确实很好。因为即便一个工具设计得再好,如果 Claude 不懂得如何调用它,也不会起作用。
这就是 Claude Code 里追问能力(elicitation)的最终形态吗?我们也不确定。正如你会在下一个例子里看到的:对一个模型有效的方法,对另一个模型未必就是最优。
随着能力变化而更新:Tasks 与 Todos
在我们刚发布 Claude Code 时,我们意识到模型需要一个待办清单(Todo list)来保持思路不跑偏。待办事项可以在开始时写下,并在模型执行工作时逐条勾选。为此我们给 Claude 提供了 TodoWrite 工具,用于写入或更新 Todos,并把它展示给用户。
但即便如此,我们仍经常看到 Claude 忘记自己要做什么。为了适应这种情况,我们在系统层面每 5 轮插入一次提醒,提示 Claude 它的目标是什么。
然而,随着模型能力提升,它们不仅不再需要被提醒 Todo 列表,反而可能觉得 Todo 列表在限制它们。持续收到 Todo 列表提醒,会让 Claude 以为自己必须严格遵循这个列表,而不是可以去修改它。我们还看到 Opus 4.5 在使用子智能体(subagents)方面也变得更强,但子智能体要如何在共享的 Todo 列表上进行协作呢?
基于这些观察,我们用 Task 工具替换了 TodoWrite(关于 Tasks 的更多内容可以阅读这里)。相比于 Todos 主要用于让模型保持在正轨上,Tasks 更侧重于帮助多个智能体彼此沟通。Tasks 可以包含依赖关系,能在子智能体之间共享更新,而且模型可以修改或删除任务。
当模型能力增强时,你过去以为模型“需要”的工具,可能反而会束缚它。因此,持续回头审视你对“需要哪些工具”的旧假设非常重要。这也解释了为什么坚持支持一小组能力轮廓相近的模型会更有用。
设计搜索界面
对 Claude 来说,一组特别关键的工具,是让它能够构建自身上下文(context)的搜索工具。
Claude Code 刚推出时,我们使用的是 RAG 向量数据库来为 Claude 找到相关上下文。RAG 强大且快速,但它需要索引与前期配置,而且在各种环境下可能会比较脆弱。更重要的是:这些上下文是“我们喂给 Claude 的”,而不是“Claude 自己找到的”。
但如果 Claude 都能上网搜索了,为什么不能搜索你的代码库?如果我们给 Claude 一个 Grep 工具,就可以让它自己搜索文件并构建上下文。
随着 Claude 变得更聪明,我们看到一种明显的模式:只要给它合适的工具,它就越来越擅长自己构建上下文。
当我们引入 Agent Skills 时,我们把“渐进式披露(progressive disclosure)”的想法正式化了:让智能体通过探索来逐步发现相关信息。
Claude 可以读取 skill 文件,而这些文件又可以引用其他文件,模型能够递归地继续读取。事实上,skills 的一个常见用途,就是为 Claude 增加更多搜索能力,比如在 skill 里加入如何使用某个 API 或查询数据库的说明。
在一年时间里,Claude 从几乎无法自己构建上下文,进步到能够进行多层嵌套的文件搜索,跨越好几层结构,最后精准找到它需要的上下文。
如今,渐进式披露已经成为我们在不新增工具的前提下,为系统加入新功能的一种常用技术。
渐进式披露——Claude Code 指南智能体
Claude Code 目前大约有 20 个工具,而我们一直在问自己:我们真的需要全部这些工具吗?新增工具的门槛很高,因为这会让模型多一个需要思考的选项。
例如,我们发现 Claude 对如何使用 Claude Code 本身了解不够。如果你问它如何添加 MCP,或者某个斜杠命令(slash command)是做什么的,它可能无法回答。
我们当然可以把所有这些信息塞进系统提示词里,但考虑到用户很少问这些问题,这会导致上下文腐烂(context rot),并干扰 Claude Code 的主要任务:写代码。
所以我们尝试了一种渐进式披露:我们给 Claude 一个指向文档的链接,让它在需要时加载并搜索更多信息。这个方法有效,但我们发现 Claude 往往会为了找到正确答案,把大量搜索结果加载进上下文,而实际上用户需要的只是“答案本身”。
于是我们构建了 Claude Code Guide 子智能体。当你询问 Claude Code 自身相关的问题时,我们会提示 Claude 去调用这个子智能体。这个子智能体拥有大量关于如何高效搜索文档、以及应该返回什么内容的指令。
这并不完美——当你问它如何设置它自己时,Claude 仍然可能困惑——但它已经比过去好很多了!我们在不新增工具的情况下,为 Claude 的行动空间增加了能力。
这是一门艺术,而不是科学
如果你期待一套关于如何构建工具的刚性规则,那么很遗憾,这篇文章并不会提供那样的答案。为模型设计工具,与其说是科学,不如说同样是一门艺术。它高度依赖于你使用的模型、智能体的目标,以及它运行的环境。
要经常实验,仔细阅读输出,勇于尝试新方法。学会像智能体一样去看。