原文链接:https://x.com/trq212/status/2027463795355095314
原文标题:Lessons from Building Claude Code: Seeing like an Agent
作者:Thariq(@trq212),Claude Code(@anthropicai)团队成员,YC W20 校友,MIT 媒体实验室前成员。
构建智能体系统最困难的部分之一是构建它的动作空间(action space)。
Claude 通过工具调用(Tool Calling)来执行操作,但在 Claude API 中可以通过多种方式构建工具,例如使用 bash、技能(skills)以及最近新增的代码执行功能(了解更多关于 Claude API 中程序化工具调用的信息@RLanceMartin的新文章)。
面对这么多选项,你如何设计智能体的工具?你只需要一个工具(如代码执行或 bash)吗?还是应该有 50 个工具,每个对应智能体可能遇到的一个用例?
站在模型的角度思考
为了让自己站在模型的角度思考,我喜欢想象自己被给了一道很难的数学题。你会需要什么工具来解决它?这取决于你自己的技能!
- 纸笔是最低要求,但你会受限于手动计算。
- 计算器会更好,但你需要知道如何操作高级功能。
- 最快、最强大的选项是计算机,但你必须知道如何用它来编写和执行代码。
这是设计智能体的一个有用框架。你想给它与其自身能力相匹配的工具。但你怎么知道这些能力是什么呢?你需要关注、阅读它的输出、进行实验。你要学会像智能体一样思考。
以下是我们在构建 Claude Code 时通过关注 Claude 学到的一些经验。
经验一:改进引导(Elicitation)与 AskUserQuestion 工具

在构建 AskUserQuestion 工具时,我们的目标是提高 Claude 提问的能力(通常称为引导)。
虽然 Claude 可以用纯文本提问,但我们发现回答这些问题感觉花费了不必要的时间。如何降低这种摩擦,增加用户与 Claude 之间的沟通带宽呢?
尝试 #1 - 编辑 ExitPlanTool
我们首先尝试的是在 ExitPlanTool 中添加一个参数,让问题数组与计划一起出现。这是最容易实现的方案,但它让 Claude 感到困惑,因为我们同时要求提供计划和对计划的一组问题。如果用户的回答与计划内容冲突怎么办?Claude 需要调用两次 ExitPlanTool 吗?我们需要另一种方法。
(你可以在我们的提示缓存相关文章中阅读更多关于为什么我们要创建 ExitPlanTool 的内容)
尝试 #2 - 更改输出格式
接下来,我们尝试修改 Claude 的输出指令,让它使用一种稍微修改过的 Markdown 格式来提问。例如,我们可以要求它输出一个带括号选项的项目符号问题列表。然后我们可以解析这些问题并将其格式化为用户界面。
虽然这是最通用的改变,Claude 似乎也擅长输出这种格式,但它无法保证。Claude 会添加额外的句子、省略选项,或者使用完全不同的格式。
尝试 #3 - AskUserQuestion 工具

最终,我们决定创建一个 Claude 可以随时调用的工具,但我们特别提示它在计划模式(plan mode)期间使用。当工具触发时,我们会显示一个弹窗来展示问题,并阻塞智能体的循环直到用户回答。
这个工具让我们能够提示 Claude 输出结构化内容,并帮助我们确保 Claude 给用户提供多个选项。它还让用户能够以多种方式组合这个功能,例如在 Agent SDK 中调用它或在技能中引用它。
最重要的是,Claude 似乎喜欢调用这个工具,我们发现它的输出效果很好。即使是设计得最好的工具,如果 Claude 不理解如何调用它,也无法发挥作用。
这是否是 Claude Code 中引导功能的最终形态?我们不确定。正如你在下一个例子中看到的,对一个模型有效的方法对另一个模型可能不是最好的。
经验二:随着能力更新——任务与待办事项

当我们首次推出 Claude Code 时,我们意识到模型需要一个待办事项列表(Todo list)来保持正轨。待办事项可以在开始时写好,然后随着模型的工作逐一勾选。为此,我们给了 Claude TodoWrite 工具,它会写入或更新待办事项并显示给用户。
但即便如此,我们经常看到 Claude 忘记它需要做什么。为了适应这种情况,我们每 5 轮插入一次系统提醒,提醒 Claude 它的目标。
但随着模型的改进,它们不仅不需要被提醒待办事项列表,反而可能觉得它限制了它们。收到待办事项列表的提醒会让 Claude 认为它必须严格遵守列表,而不是修改它。我们还看到 Opus 4.5 在使用子智能体方面变得更好,但子智能体如何协调共享的待办事项列表呢?
看到这一点,我们用任务工具(Task Tool)取代了 TodoWrite。待办事项是为了让模型保持正轨,而任务更多是关于帮助智能体相互通信。任务可以包含依赖关系、在子智能体之间共享更新,模型可以修改和删除它们。
随着模型能力的提升,你的模型曾经需要的工具现在可能会限制它们。重要的是要不断重新审视关于需要什么工具的先前假设。这也是为什么支持一小部分能力特征相似的模型是有用的。
经验三:设计搜索界面
对 Claude 来说,特别重要的一组工具是可以用来构建自身上下文的搜索工具。
当 Claude Code 首次推出时,我们使用 RAG 向量数据库来为 Claude 查找上下文。虽然 RAG 强大且快速,但它需要索引和设置,并且在不同环境中可能很脆弱。更重要的是,Claude 是被给予这个上下文,而不是自己找到上下文。
但如果 Claude 可以在网上搜索,为什么不能在代码库中搜索呢?通过给 Claude 一个 Grep 工具,我们可以让它自己搜索文件并构建上下文。
这是我们随着 Claude 变得更聪明而看到的一种模式——如果给它正确的工具,它会越来越擅长构建自己的上下文。
当我们引入智能体技能(Agent Skills)时,我们正式提出了渐进式披露(progressive disclosure)的概念,它允许智能体通过探索逐步发现相关上下文。
Claude 可以阅读技能文件,这些文件又可以引用其他文件,模型可以递归地阅读这些文件。事实上,技能的常见用途是为 Claude 添加更多搜索能力,比如给它关于如何使用 API 或查询数据库的指令。
在一年的时间里,Claude 从几乎无法构建自己的上下文,发展到能够跨多个文件层进行嵌套搜索,找到它需要的精确上下文。
渐进式披露现在是我们添加新功能而不添加工具的常用技术。
渐进式披露——Claude Code 指南智能体
Claude Code 目前大约有 20 个工具,我们不断问自己是否需要所有这些工具。添加新工具的门槛很高,因为这会给模型多一个需要思考的选项。
例如,我们注意到 Claude 对如何使用 Claude Code 了解不够。如果你问它如何添加 MCP 或斜杠命令的作用,它无法回答。
我们可以把所有这些信息放在系统提示中,但考虑到用户很少问这些问题,这会增加上下文污染(context rot),并干扰 Claude Code 的主要工作:编写代码。
相反,我们尝试了一种渐进式披露的形式。我们给了 Claude 一个指向其文档的链接,它可以加载这些文档来搜索更多信息。这有效,但我们发现 Claude 会将大量结果加载到上下文中才能找到正确答案,而实际上你只需要答案。
所以我们构建了 Claude Code 指南子智能体,当用户询问关于 Claude Code 本身的问题时,Claude 会被提示调用它。这个子智能体有关于如何良好搜索文档以及返回什么的详细指令。
虽然这并不完美——当你问它如何设置自己时,Claude 仍然会感到困惑——但比以前好多了!我们能够在不添加工具的情况下扩展 Claude 的动作空间。
结语:艺术,而非科学
如果你希望获得一套关于如何构建工具的严格规则,很遗憾,这不是本指南的内容。为模型设计工具既是科学,也是艺术。它严重依赖于你使用的模型、智能体的目标以及它运行的环境。
经常实验,阅读你的输出,尝试新事物。像智能体一样思考。