像智能体一样思考:我们如何在 Claude Code 中设计工具
了解 Claude Code 团队如何从模型的角度出发,设计、测试并改进工具。
构建智能体框架最难的部分之一是构造它的工具。
Claude 完全通过 工具调用 来运作,但在 Claude API 中,有多种方式可以利用 bash、skills 和 代码执行 等原语来构造工具。(你可以在 @RLanceMartin 的新文章中阅读更多关于 Claude API 上程序化工具调用的内容)。
那么你该如何设计智能体的工具呢?你会给它一个像 bash 或代码执行这样的通用工具吗?还是五十个专门的工具,每个用例一个?
为了代入模型的思维,想象一下你被给了一道很难的数学题。你会想要什么工具来解决它?这取决于你自己的技能水平!
纸是最低限度的,但你会受限于手动计算。计算器会更好,但你需要知道如何操作更高级的选项。最快、最强大的选择是电脑,但你必须知道如何使用它来编写和执行代码。
这是一个设计智能体的有用框架。你希望给它提供与其自身能力相匹配的工具。但你怎么知道那些能力是什么?你需要观察、阅读它的输出、进行实验。你学会了像智能体一样去观察。
如果你正在构建一个智能体,你将面临我们遇到过的同样的问题:何时添加一个工具,何时移除一个工具,以及如何区分两者。以下是我们构建 Claude Code 时回答这些问题的方式,包括我们最初搞错的地方。
使用 ASKUSERQUESTION 工具改进引导提问
在构建 AskUserQuestion 工具时,我们的目标是提高 Claude 提问的能力(通常称为引导提问)。
虽然 Claude 可以直接用纯文本提问,但我们发现回答这些问题感觉花费了不必要的额外时间。我们如何降低这种摩擦,增加用户与 Claude 之间的通信带宽?
尝试 1:修改 ExitPlanTool
我们尝试的第一种方法是向 ExitPlanTool 添加一个参数,使其在计划之外还包含一个提问数组。这是最容易实现的修复方案,但它让 Claude 感到困惑,因为我们同时要求它提供一个计划和一组关于该计划的问题。如果用户的回答与计划内容冲突怎么办?Claude 需要调用两次 ExitPlanTool 吗?我们知道这个策略行不通,于是回到了起点。(你可以在我们关于提示缓存的文章中阅读更多关于我们为何创建 ExitPlanTool 的原因)
尝试 2:更改输出格式
接下来,我们尝试更新 Claude 的输出指令,让它使用一种稍微修改过的 markdown 格式来提问。例如,我们可以要求它输出一个带方括号备选项的项目符号问题列表。然后我们可以解析该问题并将其格式化为用户界面。
Claude 通常可以生成这种格式,但并不可靠。它会追加多余的句子、遗漏选项,或者完全放弃这种结构。于是我们尝试下一种方法。
尝试 3:AskUserQuestion 工具
最终,我们决定创建一个 Claude 可以随时调用的工具,但在计划模式下会特别提示它这样做。当该工具被触发时,我们会显示一个模态框来展示问题,并阻塞智能体的循环,直到用户回答为止。
这个工具让我们能够提示 Claude 输出结构化内容,帮助我们确保 Claude 为用户提供多个选项。它还为用户提供了组合此功能的方式,例如在 Agent SDK 中调用它,或在 skills 中引用它。
最重要的是,Claude 似乎很喜欢调用这个工具,我们发现它的输出效果很好。毕竟,即使设计得再好的工具,如果 Claude 不知道如何调用它,也无法发挥作用。
这是 Claude Code 中引导式交互的最终形态吗?我们对此表示怀疑。随着 Claude 能力的提升,为其服务的工具也必须随之演进。下一节将展示一个曾经有所帮助的工具开始成为阻碍的案例。
通过能力更新:任务与待办事项
我们最初推出 Claude Code 时,意识到模型需要一个 待办事项列表 来保持进度。待办事项可以在开始时写入,并随着模型完成工作而被勾选。为此,我们为 Claude 提供了 TodoWrite 工具,它可以写入或更新待办事项并展示给用户。
但即便如此,我们经常看到 Claude 忘记它需要做什么。为了适应这一点,我们每隔 5 轮插入一次系统提醒,提醒 Claude 它的目标。
随着模型的改进,它们发现待办事项列表存在局限性。被发送待办事项列表的提醒让 Claude 认为它必须坚持列表,而不是在意识到需要改变方向时修改它。我们还看到 Opus 4.5 在使用子代理方面也变得更好,但子代理如何在共享的待办事项列表上进行协调呢?
有鉴于此,我们用 Task 工具 取代了 TodoWrite 功能。待办事项侧重于让模型保持进度,而任务则帮助代理之间进行通信。任务可以包含依赖关系,在子代理之间共享更新,模型还可以修改和删除它们。
随着模型能力的提升,你的模型曾经需要的工具现在可能正在限制它们。重要的是要 不断重新审视之前的假设,思考需要哪些工具。这也是为什么坚持支持一小批能力特征相当相似的模型是有益的。
设计搜索接口
我们构建的最重要的工具,是那些让 Claude 自己寻找上下文的工具。
Claude Code 最初在内部发布时,我们使用了 RAG:一个向量数据库会预先索引代码库,而运行框架会在每次响应前检索相关代码片段并交给 Claude。虽然 RAG 强大且快速,但它需要索引和设置,并且在各种不同的环境中可能很脆弱。最重要的是,Claude 是被给予这个上下文,而不是自己去寻找上下文。
但如果 Claude 可以在网上搜索,为什么它不能也搜索你的代码库呢?通过给 Claude 一个 Grep 工具,我们可以让它搜索文件并自行构建上下文。
随着 Claude 变得更聪明,当获得正确的工具时,它在构建自己的上下文方面变得越来越好。
当我们推出 Agent Skills 时,我们将渐进式披露的概念形式化,这允许代理通过探索逐步发现相关上下文。
Claude 现在可以读取技能文件,而这些文件随后可以引用模型可以递归读取的其他文件。事实上,技能的一个常见用途是为 Claude 添加更多搜索能力,例如向它提供如何使用 API 或查询数据库的说明。
在一年的时间里,Claude 从基本无法构建自己的上下文,发展到能够跨多个文件层进行嵌套搜索,以找到它所需的确切上下文。
渐进式披露现在是我们用来在不添加工具的情况下添加新功能的常用技术。在下一节中,我们将解释原因。
渐进式披露:Claude Code 指南代理
Claude Code 目前有约 20 个工具,我们的团队经常重新审视是否需要所有这些工具才能让 Claude 发挥最大效能。添加新工具的门槛很高,因为这会给模型增加一个需要考虑的选项。
例如,我们注意到 Claude 对如何使用 Claude Code 了解不够。如果你问它如何添加 MCP 或斜杠命令的作用,它将无法回答。
我们本可以将所有这些信息放在系统提示中,但考虑到用户很少询问这些,这会增加上下文腐烂并干扰 Claude Code 的主要工作:编写代码。
相反,我们尝试了渐进式披露:我们给了 Claude 一个指向其文档的链接,它可以在需要时加载和搜索。这行得通,但 Claude 会将大段文档拉入上下文中,以找到用户一句话就能得到的答案。
因此我们构建了 Claude Code 指南——一个 Claude 在用户询问 Claude Code 本身时调用的子代理。子代理在其自己的上下文中进行文档搜索,遵循关于如何搜索和提取什么的详细指令,并只返回答案。主代理的上下文保持干净。
虽然这不是一个完美的解决方案(当你询问如何设置它自己时,Claude 仍可能会感到困惑),但我们能够在不添加新工具的情况下将内容添加到 Claude 的行动空间中。
像代理一样观察是一门艺术,而非科学
为你的模型设计工具既是一门艺术,也是一门科学。它在很大程度上取决于你使用的模型、代理的目标以及它所操作的环境。
我们最好的建议是什么?经常实验,阅读你的输出,尝试新事物。最重要的是,尝试像代理一样观察。
立即开始使用 Claude Code。
关于作者: Thariq Shihipar 是 Anthropic 的技术团队成员,从事 Claude Code 的工作。