作者:Thariq (@trq212) — Claude Code @anthropic,前 YC W20,MIT 媒体实验室。Towards machines of loving grace.
原文:Lessons from Building Claude Code: Seeing like an Agent
构建智能体(Agent)系统时,最棘手的问题之一就是如何设计它的行动空间。
Claude 通过工具调用来完成各种操作。在 Claude API 中,工具的构建方式多种多样——可以基于 bash、技能(skills),以及最新支持的代码执行等基础能力。(关于如何在 Claude API 上实现程序化工具调用,推荐阅读 X 上 @RLanceMartin 的最新文章。)
有了这么多选择,你该如何为智能体设计工具集?只给一个工具——比如代码执行或 bash——够用吗?如果准备 50 个工具呢?每种场景都配一个专属工具?
我常常这样代入模型的视角去思考:假设你面前有一道很难的数学题,你希望手边有什么工具来解题?答案取决于你自身的能力。
纸笔是最基本的——但只能靠手算。计算器好一些,不过你得会操作才能处理复杂运算。最高效的当然是电脑,但前提是你得会写代码、会执行程序。
这个类比为智能体的设计提供了一个很好的思考框架:工具应当匹配使用者的能力。那么,怎样才能知道模型的能力边界在哪里?只有一个办法——仔细观察它的行为,反复阅读它的输出,不断实验迭代。换句话说,你需要学会像智能体一样看世界。
以下是我们在构建 Claude Code 的过程中,从 Claude 身上学到的几条重要经验。
优化信息引导:AskUserQuestion 工具的诞生
构建 AskUserQuestion 工具的初衷,是提升 Claude 主动向用户提问的能力——这在业界通常称为“引导式交互”(elicitation)。
Claude 当然可以直接用纯文本向用户提问,但我们发现用户回答这类问题时总觉得费事、效率不高。如何才能降低沟通摩擦、提高双向交互的带宽?
第一次尝试:给 ExitPlanTool 加参数
我们最初的方案很直接——给 ExitPlanTool 新增一个参数,让它在输出计划的同时附带一组澄清性问题。这在实现上最简单,但效果却很糟糕:同时要求 Claude 给出“计划”和“关于计划的问题”让它非常困惑。如果用户的回答与计划内容矛盾了怎么办?Claude 需要再调用一次 ExitPlanTool 吗?显然,我们需要换一种思路。
(关于为什么会有 ExitPlanTool 这个工具,可以参阅我们关于提示缓存 prompt caching 的文章。)
第二次尝试:约定输出格式
接着,我们尝试通过修改输出指令,让 Claude 以一种特定的 Markdown 格式来提问——比如用项目符号列出问题,用方括号标注备选答案,然后我们在前端解析并渲染成交互式 UI。
虽然这是侵入性最小的改动,Claude 表面上也能配合,但输出质量很不稳定。它时不时会多写几句废话、漏掉选项,甚至完全换一种格式。靠约定输出格式来实现结构化交互,终究不够可靠。
第三次尝试:AskUserQuestion 工具
最终方案是创建一个独立的工具,Claude 可以在任何时候调用它——我们尤其会在计划模式下引导它使用。当工具被触发时,前端弹出一个模态框展示问题,同时暂停智能体的执行循环,直到用户作出回答。
这个工具带来了几个好处:我们可以约束 Claude 输出结构化内容;可以确保它为用户提供多个选项;用户还能灵活地复用这一能力——比如在 Agent SDK 中调用,或在自定义技能中引用。
最关键的一点是:Claude 很自然地就学会了使用这个工具,输出效果也很好。再精巧的工具设计,如果模型不知道何时、如何调用它,那也是白搭。
这是引导式交互的最终形态吗?我们也说不准。正如下一节将展示的——对某个模型有效的方案,换一个模型可能就失灵了。
工具要跟着模型能力迭代:从 Todo 到 Task 的演进
随着模型能力的持续提升,曾经不可或缺的工具反而可能成为束缚。定期重新审视对工具的既有假设非常重要。这也是我们倾向于只支持少数几个能力水平接近的模型的原因——便于统一调优。
Claude Code 刚上线时,我们发现模型需要一份待办清单(Todo List)来保持工作方向。它可以在任务开始时列出清单,完成一项勾掉一项。为此我们提供了 TodoWrite 工具,让 Claude 能编写和更新待办事项,并实时展示给用户。
然而 Claude 还是会经常“忘事”。为此我们每隔 5 轮对话就插入一条系统提醒,把当前目标再告诉它一遍。
但等到模型变得更强之后,情况反转了——它不仅不再需要被提醒,反而觉得这些提醒是种干扰。不断收到待办清单的提示让 Claude 以为自己必须严格按部就班,丧失了灵活调整计划的能力。与此同时,Opus 4.5 在调度子智能体(subagents)方面进步明显,但共享一份扁平的待办清单显然无法满足多智能体协作的需求。
于是我们用全新的 Task 工具替换了 TodoWrite。如果说 Todo 的定位是“帮模型记住该做什么”,那么 Task 的定位则是“帮多个智能体协同工作”。Task 支持依赖关系、跨子智能体的状态更新,模型还可以随时修改或删除任务。
搜索接口的设计演进
在所有工具中,有一类对 Claude 尤为关键——搜索工具,也就是那些帮助模型自主构建上下文的工具。
Claude Code 早期使用 RAG 向量数据库来为 Claude 检索相关上下文。RAG 虽然强大且响应迅速,但依赖索引构建和环境配置,跨环境部署时相当脆弱。更本质的问题在于:上下文是被喂给 Claude 的,而不是它自己找到的。
转念一想——Claude 能在互联网上搜索信息,为什么不能直接搜索代码库呢?于是我们给了它一个 Grep 工具,让它自己检索文件、自主构建上下文。
这背后体现了一个规律:模型越聪明,只要给它趁手的工具,它自主构建上下文的能力就越强。
后来我们引入了智能体技能(Agent Skills),正式提出了“渐进式披露”(Progressive Disclosure)的理念——让智能体通过主动探索,逐层发现所需的上下文信息。
具体来说,Claude 可以读取技能文件,而技能文件中又可以引用其他文件,模型可以递归地一层层读下去。事实上,技能的一个常见用法就是为 Claude 扩展搜索能力——比如告诉它如何调用某个 API 或查询某个数据库。
短短一年间,Claude 从几乎无法自主获取上下文,进化到了能够跨多层文件进行嵌套搜索、精准定位所需信息的水平。
如今,渐进式披露已经成为我们扩展功能的常用手段——无需新增工具,就能赋予模型新的能力。
渐进式披露实战:Claude Code Guide 子智能体
Claude Code 目前拥有约 20 个工具。我们始终在反思:是否真的每一个都不可或缺?新增工具的门槛很高,因为每多一个工具,就多给模型增加了一个需要权衡的选项。
举个例子:我们发现 Claude 对“如何使用 Claude Code 本身”知之甚少。用户问它怎么添加 MCP 服务器、某个斜杠命令怎么用,它往往答不上来。
最直接的做法是把所有使用文档塞进系统提示词。但用户实际很少问这类问题,额外的提示内容会造成上下文膨胀,还会干扰 Claude Code 的核心任务——写代码。
于是我们尝试用渐进式披露来解决:给 Claude 一个指向自身文档的链接,让它按需检索。这种方案能用,但有个问题——Claude 倾向于把大量搜索结果一股脑塞进上下文,而用户其实只需要一个简洁的答案。
最终方案是构建了一个专门的 Claude Code Guide 子智能体。当用户提出关于 Claude Code 自身使用方法的问题时,主智能体会将请求转交给这个子智能体处理。子智能体内置了详尽的文档检索指令,知道如何高效搜索、精炼返回结果。
这个方案还称不上完美——用户问到某些配置问题时 Claude 仍可能犯糊涂——但已经比从前好了很多。关键在于,我们在不增加任何新工具的前提下,成功拓展了 Claude 的能力边界。
这是一门艺术,不是一套公式
如果你期望从这篇文章中找到一套构建智能体工具的“万能法则”,恐怕要让你失望了。为模型设计工具,与其说是工程,不如说是手艺——它在很大程度上取决于你所用的模型、智能体要完成的目标,以及它所处的运行环境。
多做实验,仔细阅读输出,大胆尝试新方向。
学会像智能体一样看世界。
FIN