从构建 Claude Code 中得到的经验:像 Agent 一样看世界

0 阅读7分钟

这篇文章是Claud Code的核心开发人员Thariq在X发表的一篇文章也是目前 agent engineering 最重要的一篇工程文章之一

image.png

构建 Agent 时最困难的问题之一

构建一个 agent 框架时,最困难的事情之一是 设计它的动作空间(action space)

Claude 通过 Tool Calling(工具调用) 来执行操作。而在 Claude API 中,工具可以通过多种方式构建,例如:

  • bash
  • skills
  • 最近新增的 code execution

面对这些选项,你应该如何设计 agent 的工具?

只需要一个工具(例如 bash 或 code execution)就够了吗?
还是需要 50 个工具,每种场景一个?

一个简单的类比:解一道数学题

为了站在模型的角度思考问题,我常常会做这样一个想象:

假设你被给了一道非常困难的数学题。
你会希望手边有哪些工具?

答案取决于 你的能力

  • 纸和笔:最低限度可以解决问题,但只能依靠手算。
  • 计算器:更快,但前提是你知道如何使用高级功能。
  • 电脑:最强大的工具,但前提是你能编写并运行代码。

这个类比对于设计 agent 的工具非常有用:

你应该给 agent 提供与其能力相匹配的工具。

但问题是:

你如何知道模型的能力边界在哪里?

答案很简单:

  • 观察它
  • 阅读它的输出
  • 不断实验

最终,你要学会 像 agent 一样看世界

下面是我们在构建 Claude Code 时得到的一些经验。

提升信息获取能力:AskUserQuestion 工具

在设计 AskUserQuestion 工具时,我们的目标是提升 Claude 提问的能力(elicitation)。

Claude 本身当然可以用普通文本提问。
但我们发现这种方式存在一个问题:

用户回答这些问题通常需要 额外的时间和精力

于是我们开始思考:

如何减少沟通摩擦,提高用户与 Claude 之间的信息带宽?

尝试 1:修改 ExitPlanTool

我们最初的尝试是:

ExitPlanTool 中新增一个参数,让它在输出计划时,同时包含一组问题。

实现起来很简单,但结果并不好。

原因是:

Claude 同时被要求:

  • 生成一个计划
  • 询问关于该计划的问题

这会导致一些困惑,例如:

  • 如果用户的回答与计划冲突怎么办?
  • Claude 是否需要再次调用 ExitPlanTool?

因此我们需要一种新的方法。

尝试 2:改变输出格式

接下来,我们尝试修改 Claude 的输出格式。

例如:

让 Claude 输出一种 特定的 Markdown 结构,用来表示问题,例如:

  • 使用 bullet list
  • 在括号中提供多个选项

然后我们可以解析这个格式,并生成 UI。

这种方法的优点是:

  • 非常通用
  • Claude 大多数情况下能够遵循

但问题是:

它并不可靠。

Claude 有时会:

  • 添加额外句子
  • 漏掉选项
  • 使用不同的格式

结果是解析经常失败。

尝试 3:AskUserQuestion 工具

最终,我们选择创建一个专门的工具:

AskUserQuestion

Claude 可以在任何时候调用它,但我们特别鼓励它在 plan mode 中使用。

当工具触发时:

  • UI 会弹出一个对话框
  • 显示问题
  • agent 循环暂停
  • 等待用户回答

这种方式带来了几个好处:

  1. Claude 可以输出 结构化问题
  2. 用户可以看到 多个可选答案
  3. 功能可以被复用(例如在 Agent SDK 或 skills 中)

最重要的是:

Claude 似乎很喜欢调用这个工具

即使是最精心设计的工具,如果 Claude 不理解如何调用它,也不会成功。

随模型能力演进:Tasks 与 Todos

在 Claude Code 的早期版本中,我们意识到:

模型需要一个 Todo 列表 来保持任务进度。

因此我们设计了一个工具:

TodoWrite

它允许 Claude:

  • 写入 Todo
  • 更新 Todo
  • 展示给用户

同时,Claude 可以在完成任务时勾选它们。

问题:模型仍然会忘记任务

尽管如此,我们仍然经常看到 Claude 忘记要做的事情

于是我们采取了一个简单的办法:

5 次对话 插入系统提醒,告诉 Claude 当前目标。

模型能力提升后的变化

随着模型能力提升,这种设计反而变成了问题。

因为:

  • Claude 已经 不再需要提醒
  • Todo 反而 限制了它

模型会误以为:

它必须严格遵守 Todo 列表。

而不是修改它。

此外,新的模型(如 Opus 4.5)已经能够很好地使用 subagents

那么问题来了:

多个 agent 如何共享 Todo?

解决方案:Task Tool

因此我们将 TodoWrite 替换为:

Task Tool

区别在于:

Todos → 用于跟踪任务
Tasks → 用于 agent 之间协作

Task 可以:

  • 定义依赖关系
  • 在多个 subagent 之间共享更新
  • 被修改或删除

一个重要的经验是:

随着模型能力提高,过去有用的工具可能会变成限制。

因此你必须 不断重新审视工具设计

搜索工具:让 Agent 自己构建上下文

对于 Claude 来说,最重要的一类工具是:

搜索工具

因为它们决定了 Claude 如何构建上下文。

第一阶段:RAG 向量数据库

Claude Code 最初使用:

RAG + 向量数据库

来查找相关上下文。

优点:

  • 强大

缺点:

  • 需要索引
  • 配置复杂
  • 容易在不同环境中失效

更重要的问题是:

Claude 不是自己找上下文,而是 被动接收上下文

第二阶段:Grep

后来我们想到:

如果 Claude 能搜索互联网,
为什么不能搜索代码库?

于是我们给 Claude 一个工具:

Grep

这样 Claude 可以:

  • 搜索文件
  • 自己构建上下文

随着模型变强,它越来越擅长做这件事。

Progressive Disclosure(渐进式披露)

在引入 Agent Skills 后,我们正式提出了一个概念:

Progressive Disclosure

意思是:

agent 通过探索逐步发现信息。

例如:

  • Claude 可以读取 skill 文件
  • 这些文件可以引用其他文件
  • Claude 可以递归读取

这使得 Claude 可以在多层文件结构中搜索信息。

一年时间里,Claude 从:

  • 几乎不能构建上下文

变成:

  • 可以进行多层嵌套搜索

如今,progressive disclosure 是我们添加功能的重要方式。

因为:

它不需要新增工具。

一个案例:Claude Code Guide Agent

目前 Claude Code 大约有 20 个工具

我们一直在问自己:

这些工具真的都需要吗?

增加工具的门槛很高,因为:

每个工具都会增加模型的决策复杂度。

一个实际问题

Claude 并不知道如何使用 Claude Code。

例如:

如果你问:

  • 如何添加 MCP?
  • slash command 是什么?

Claude 可能回答不了。

解决方案

我们没有把这些内容写进系统 prompt。

因为:

用户很少问这些问题。

如果放进去,会造成:

context rot(上下文腐化)

影响 Claude 的主要任务:

写代码。

Guide Subagent

因此我们创建了一个 Guide 子 agent

当用户询问 Claude Code 自身的问题时:

Claude 会调用这个 subagent。

这个 agent:

  • 有完整的文档
  • 知道如何搜索文档
  • 返回精确答案

这样我们:

扩展了 Claude 的能力,但没有增加新的工具。

设计 Agent 工具是一门艺术

如果你希望这篇文章给出一套严格规则,
那可能会失望。

因为:

设计 AI 工具既是科学,也是艺术。

它取决于:

  • 模型能力
  • agent 的目标
  • 所处环境

真正有效的方法只有:

  • 不断实验
  • 阅读输出
  • 持续调整

最终:

学会 像 agent 一样看世界