像 Agent 一样看世界

Claude Code 核心开发者分享:构建 Claude Code 的经验教训。

像 Agent 一样看世界
AI编程/Vibe Coding 遇到问题需要帮助的,联系微信 ezpoda,免费咨询。

在构建 AskUserQuestion 工具时,我们的目标是提升 Claude 向用户提问的能力(这通常被称为 elicitation)。虽然 Claude 可以用纯文本提问,但我们发现回答这些问题总是让人觉得不必要地耗时。我们怎样才能降低这种摩擦,提高用户和 Claude 之间的沟通带宽?

尝试 #1 - 修改 ExitPlanTool

我们最先尝试的是在 ExitPlanTool 上加一个参数,让它在输出计划的同时附带一组问题。这是最容易实现的方案,但它让 Claude 很困惑,因为我们同时要求它给出一个计划和一组关于这个计划的问题。如果用户的回答和计划内容冲突了怎么办?Claude 是不是需要调用两次 ExitPlanTool?我们需要另一种方案。(关于我们为什么要做 ExitPlanTool,可以在 我们关于 prompt caching 的文章 中了解更多)

尝试 #2 - 修改输出格式

接着我们尝试修改 Claude 的输出指令,让它输出一种稍作改动的 markdown 格式来提问。比如,我们可以要求它输出一组带有方括号选项的问题列表。然后我们解析这种格式,把问题渲染成 UI 展示给用户。这是我们能做出的最通用的改动,Claude 似乎也还算能输出这种格式,但并不能保证一定对。Claude 会多加几句话,遗漏选项,或者干脆换一种完全不同的格式。

尝试 #3 - AskUserQuestion 工具

最终,我们做了一个 Claude 可以在任何时候调用的工具,但特别在 plan mode 中会被提示去使用。当工具被触发时,我们会显示一个弹窗来展示问题,并阻塞 agent 的循环直到用户回答完毕。

这个工具让我们可以引导 Claude 输出结构化内容,并且确保 Claude 给用户提供多个选项。它还给了用户组合使用的能力,比如在 Agent SDK 中调用它,或者在 skills 中引用它。

最重要的是,Claude 似乎很喜欢调用这个工具,而且我们发现它的输出效果很好。即使是设计得再好的工具,如果 Claude 不理解怎么调用它,那也没用。

这是 Claude Code 中 elicitation 的最终形态吗?我们也不确定。正如你在下一个例子中会看到的,对一个模型有效的方案,对另一个模型未必是最好的。

Claude Code 刚上线时,我们意识到模型需要一个待办事项列表来保持专注。可以在开始时写好待办事项,然后随着模型的工作逐一勾选完成。为此我们给 Claude 提供了 TodoWrite 工具,用来创建或更新待办事项并展示给用户。

但即便如此,我们经常看到 Claude 忘记自己要做什么。为了应对这个问题,我们每隔 5 轮就插入一条系统提醒,提醒 Claude 它的目标。

但随着模型能力的提升,它们不仅不再需要被提醒待办事项,反而觉得这种提醒是一种束缚。收到待办事项的提醒会让 Claude 觉得它必须严格按照列表执行,而不是去修改它。我们还观察到 Opus 4.5 在使用子 agent 方面有了很大进步,但子 agent 之间怎么协调共享一个待办事项列表呢?

看到这些变化,我们用 Task 工具替换了 TodoWrite(关于 Tasks 的更多信息)。Todos 的目的是让模型保持在正轨上,而 Tasks 更多的是帮助 agent 之间进行沟通。Tasks 可以包含依赖关系,在子 agent 之间共享更新,模型还可以修改和删除它们。

随着模型能力的提升,你的模型曾经需要的工具可能现在反而在限制它。持续回顾之前关于"需要什么工具"的假设非常重要。这也是为什么最好只支持少数几个能力特征比较相似的模型。

对 Claude 来说,一组特别重要的工具是搜索工具——它可以用这些工具来构建自己的上下文。

Claude Code 刚推出时,我们使用 RAG 向量数据库来为 Claude 查找上下文。虽然 RAG 很强大也很快,但它需要索引和设置,而且在不同环境中可能会很脆弱。更重要的是,这些上下文是直接给到 Claude 的,而不是它自己找到的。

但如果 Claude 能在网上搜索,为什么不能搜索你的代码库呢?通过给 Claude 一个 Grep 工具,我们可以让它自己搜索文件、构建上下文。

这是我们观察到的一个趋势:随着 Claude 变得更聪明,只要给它合适的工具,它在构建自己上下文方面的能力也在不断提升。

当我们推出 Agent Skills 时,我们正式提出了渐进式发现(progressive disclosure)的理念,让 agent 可以通过探索来逐步发现相关的上下文。

Claude 可以读取 skill 文件,而这些文件可以引用其他文件,模型可以递归地一层层读下去。事实上,skills 的一个常见用法就是给 Claude 增加更多搜索能力,比如告诉它如何使用某个 API 或查询某个数据库。

一年下来,Claude 从基本不能自己构建上下文,进化到能够跨越多层文件做嵌套搜索,精确找到它需要的上下文。

渐进式发现现在是我们在不添加工具的情况下扩展功能时常用的技术。

Claude Code 目前有大约 20 个工具,我们一直在问自己是否真的需要这么多。添加新工具的门槛很高,因为这会给模型多一个需要考虑的选项。

举个例子,我们注意到 Claude 对 Claude Code 自身了解不够。如果你问它怎么添加 MCP 或者某个 slash command 是干什么的,它回答不上来。我们可以把所有这些信息都放到 system prompt 里,但考虑到用户很少问这类问题,这样做会增加上下文噪音,干扰 Claude Code 的主要工作:写代码。

所以我们尝试了一种渐进式发现的方式。我们给了 Claude 一个指向文档的链接,它可以加载文档来搜索更多信息。这种方式有效,但我们发现 Claude 会把大量搜索结果加载到上下文中来寻找正确答案,而实际上你只需要那个答案本身。

于是我们构建了 Claude Code Guide 子 agent,当你问 Claude 关于它自身的问题时就会调用这个子 agent。这个子 agent 有详细的搜索文档指令和返回内容的规范。

虽然这还不完美——Claude 在你问它如何设置自身时仍然可能搞混——但比以前好多了!我们在没有添加工具的情况下,成功扩展了 Claude 的行动空间。

是艺术,不是科学

如果你期望这篇文章能给出一套严格的工具设计规则,抱歉这并不是那种指南。为你的模型设计工具,与其说是科学,不如说是艺术。它在很大程度上取决于你使用的模型、agent 的目标以及它所处的运行环境。多实验,多看输出,多尝试新东西。像 agent 一样看世界。


原文链接:Lessons from Building Claude Code: Seeing like an Agent