上下文工程 · 16 · Extended Thinking 模式与 thinking 块

0 阅读10分钟

系列第 16 篇。主文档见 智能体上下文工程实现.md

Claude 4.X 系列支持 extended thinking —— 在生成回答前先做一段"显式思考"。这段思考是上下文里的一个新 block 类型,有特殊的拼接、压缩、缓存规则。这一篇讲它在上下文工程里的特殊位置。


0. extended thinking 是什么

普通推理:

input[模型内部计算] → output

extended thinking:

input[thinking block(可见)][output]
        ↑ 这一段进入 messages 上下文

模型先吐一段"思考过程"(包括分析、计划、自我质疑),再吐最终回复。thinking 块作为独立 content block 出现在 assistant message 里。

API 层的呈现:

{
  "role": "assistant",
  "content": [
    {
      "type": "thinking",
      "thinking": "用户要修登录 bug,先看代码结构。我应该 Glob 找 auth 相关文件...",
      "signature": "<crypto signature>"
    },
    {
      "type": "text",
      "text": "Let me look at the auth code first."
    },
    {
      "type": "tool_use",
      "name": "Glob",
      "input": {"pattern": "**/auth*"}
    }
  ]
}

注意 signature 字段 —— 这是 Anthropic 用来防伪造的加密签名(防止下游伪造 thinking 块影响后续推理)。


1. thinking 块的特殊属性

属性说明
占用 token是(计入 output_tokens)
进入下一轮上下文是(参与 cache 前缀)
可见给用户取决于 UI 实现(默认隐藏)
可压缩受限(详见 §3)
可被 agent 自身读取是(但不应主动读自己的 thinking)
可被验证signature 字段

最关键的两点:

  • 占 token:thinking 不是免费的"思考时间",每个字都计费
  • 进上下文:下一轮 API 调用时,本轮的 thinking 块要回传

2. thinking 进入上下文的拼接规则

2.1 多轮对话里的 thinking

turn 1:
  user: 修复登录 bug
  assistant:
    [thinking: 用户要修...]
    [text: Let me look at...]
    [tool_use: Glob]
turn 2:
  user: [tool_result]
  assistant:
    [thinking: 看到这些文件,下一步应该...]
    [text: Reading auth/login.ts]
    [tool_use: Read]

每轮的 assistant 消息都含 thinking 块,下一轮请求要把之前所有轮的 thinking 一起回传。

2.2 不能丢弃 thinking 然后继续

API 契约要求:assistant message 的 content 要么完整保留(含 thinking),要么完全替换。不能保留 text/tool_use 但删掉 thinking

原因:thinking 的 signature 与同一 message 的其他 block 关联。删 thinking 等于让 signature 失效。

工程后果:

  • 不能"省 token"地把历史 thinking 截掉
  • 压缩 thinking 必须连同整个 assistant message 一起压
  • 重放(13 篇)时如果改了 thinking,signature 会失效

2.3 cache 与 thinking

thinking 进入 cache 前缀。多轮对话里,前 N 轮的 thinking 走 cache 价(约 1/10 价),后 1 轮的新 thinking 全价。

但 thinking 块通常较大(几千 token 一段)。所以即使走 cache,长会话里 thinking 累积成本不小。


3. thinking 与压缩

第 7 篇讲过压缩算法。thinking 块在压缩里有特殊处理:

3.1 thinking 优先被压

为什么?三个原因:

  1. :单块几千 token,压缩收益高
  2. 冗余:thinking 内容大量是"探索性思考",最终结论已在 text/tool_use 里体现
  3. 历史价值低:模型当下需要的是结论和工具结果,不是"我曾经怎么想的"

所以压缩算法的优先级排序里,thinking 块在"高优先级压缩"档位。

3.2 但不能单独压缩

如 §2.2 所说,不能只压 thinking 留 text。所以压缩 thinking 实际是:

压缩前: assistant_message = [thinking, text, tool_use]
压缩后: 整个 message 被替换为摘要 block

代价:text 和 tool_use 也跟着没了。tool_use 没了 → 对应的 tool_result 也得连带处理(第 7 篇 §3.5)。

3.3 选择性保留:保 text 不保 thinking

实操中可以做"选择性重写":

def compact_assistant_message(msg):
    text_blocks = [b for b in msg.content if b.type == "text"]
    tool_uses = [b for b in msg.content if b.type == "tool_use"]
    # thinking 丢弃
    summary_text = f"[Earlier reasoning compacted. Visible output:]\n" + \
                   "\n".join(b.content for b in text_blocks)
    # 保留 tool_use 是因为它要和 tool_result 配对
    return Message(role="assistant", content=[
        Block(type="text", content=summary_text),
        *tool_uses,
    ])

但这违反了"signature 完整性"约束 —— 实际 API 可能拒绝。所以真要压 thinking,就得连同整个 message 摘要替换


4. thinking 的 token 预算

extended thinking 模式下,API 请求要指定 thinking 预算:

{
  "model": "claude-opus-4-7",
  "thinking": {
    "type": "enabled",
    "budget_tokens": 16000
  },
  "messages": [...]
}

budget_tokens 是模型在 thinking 阶段最多用多少 token。

4.1 预算与质量

经验观察:

  • budget < 1024:基本没启用 thinking 的价值
  • budget 1024-4096:基础推理增强
  • budget 4096-16384:复杂多步任务的甜蜜点
  • budget > 32768:边际收益递减

实操默认建议 8192(中等任务)或 16384(复杂调试 / 设计任务)。

4.2 预算不等于实际消耗

模型可能用不满预算就停。例如 budget=16384 但简单任务只用了 2000 → 只算 2000 token。但请求里写的预算决定模型有多大的"思考空间"

4.3 预算与 cache 的关系

budget_tokens 字段参与 cache key。改预算等于让 cache 失效

工程纪律:

  • 同一会话内不要动态改 budget
  • 不同任务类型用不同 agent 配置(每个配置内部 budget 稳定)

5. thinking 与工具调用

extended thinking 支持工具调用,但有特殊行为:

5.1 thinking 后的工具决策

正常流程:

thinking: 我应该先 Glob 找文件
text: Let me search for the auth files.
tool_use: Glob(pattern="**/auth*")

thinking 是工具决策的"内心独白"。模型在 thinking 里推理"该用什么工具、参数怎么填",最后吐出 tool_use。

5.2 thinking 不能直接执行工具

thinking 块本身不能包含 tool_use 子块。tool_use 必须是平级的另一个 content block。

所以模型架构是:thinking 完 → 切到 text/tool_use 模式生成 → 输出。

5.3 工具结果回来后的 thinking

下一轮(user 消息含 tool_result)后,新的 assistant 回应可以再启动 thinking:

turn 2:
  user: [tool_result: 找到 5 个文件]
  assistant:
    thinking: 5 个文件,重点看 login.ts  callback.ts...
    text: I see the structure. Reading login.ts.
    tool_use: Read(file="src/auth/login.ts")

每轮 thinking 是独立的,不跨轮"记忆"。但因为前面的 thinking 在上下文里,模型能看到自己之前的推理。


6. interleaved thinking(交错思考)

Claude 4.5+ 支持 interleaved thinking:在工具调用 之间 也能 thinking。

普通流程:

[thinking][text + tool_use] → 等工具结果 → [thinking][text + tool_use] → ...

interleaved(同一轮内多次切换):

[thinking_1][tool_use_A][tool_result_A][thinking_2][tool_use_B][tool_result_B] → ...

差别:interleaved 让模型在一个 assistant turn 里多次工具调用之间显式思考。这对复杂多步推理(先查一个,再基于结果查另一个)质量提升明显。

6.1 interleaved 的上下文成本

每段 thinking 都进上下文。一个 interleaved turn 可能含 5-10 段 thinking + 5-10 个 tool_use → 单轮就吃掉几万 token。

工程权衡:

  • 简单任务:禁用 interleaved,单段 thinking 够用
  • 复杂调试:启用 interleaved,质量值得成本
  • 不要全局启用:按任务类型决定

6.2 interleaved 的压缩挑战

每段 thinking 都"有价值"(因为它是工具决策的依据),但累积太多。压缩策略:

  • 早期 interleaved thinking 可压(结论已在工具调用里体现)
  • 最近一两段保留(影响当前推理)

7. thinking 在不同模型上的差异

模型thinking 支持默认行为
Opus 4.7默认关闭,按需启用
Sonnet 4.6同上
Haiku 4.5同上(但 budget 低)
旧版 (3.x)不支持

不同模型的 thinking 质量也不同:

  • Opus:thinking 深、容易自我纠错
  • Sonnet:thinking 平衡、性价比高
  • Haiku:thinking 短、适合简单任务的轻度增强

7.1 选模型时的 thinking 考量

def choose_model(task_complexity):
    if task_complexity == "complex":
        return ("claude-opus-4-7", thinking_budget=16384)
    elif task_complexity == "medium":
        return ("claude-sonnet-4-6", thinking_budget=8192)
    elif task_complexity == "simple":
        return ("claude-haiku-4-5", thinking_budget=2048)

决策不是"用最强模型",而是"任务复杂度匹配 thinking 深度"。


8. thinking 是否可见给用户

UI 设计选择:

方案优点缺点
完全隐藏干净用户不知道 agent 在想什么
完全展示透明信息过载、可能暴露内部弱点
折叠 + 可展开平衡实现稍复杂
摘要展示友好需要二次处理

Claude Code 默认走"折叠 + 可展开"。让用户能看但不强制看。

8.1 thinking 的 UX 心理价值

研究表明:让用户看到 agent 的 thinking 提升信任度。即使内容他们看不懂,"agent 在认真想"的体感本身有价值。

但不要过度依赖这个 —— thinking 也可能暴露:

  • 错误的初始假设
  • 对用户意图的误读
  • 知识盲点

如果展示,要有心理预期:用户可能基于 thinking 内容质疑 agent(这是好事,但要准备好)。


9. thinking 与提示工程的咬合

正常 prompt 工程的很多技巧(chain-of-thought、self-critique)在有 extended thinking 的情况下要重新评估:

9.1 不需要再写"think step by step"

旧 prompt: "Let's think step by step before answering."

extended thinking 启用后,模型本来就会逐步思考。再加这句话可能产生反作用:模型可能在 text 输出里也开始 step-by-step,啰嗦。

新做法:让 thinking 处理推理过程,让 text 处理简洁回答。

9.2 不需要再要求 self-critique

旧 prompt: "Before finalizing, critique your own answer."

extended thinking 内部会自然做 self-critique。手动加这句反而双重批判,浪费 token。

9.3 仍然需要的:约束和格式

extended thinking 不会自动学习用户偏好的输出格式。仍然要写:

  • 简洁还是详细
  • 用什么结构
  • 不要做什么

这些是 thinking 之外的"输出约束",仍然写在 System Prompt 或工具描述里。


10. thinking 的失败模式

10.1 thinking 占满整个回合

复杂任务模型在 thinking 里写了 1.5 万 token,结果 budget 用尽,没空间生成实际回答。

防御:

  • budget 设合理(不要 100k 那么大)
  • System Prompt 提示模型"thinking 简洁,结论在 text"

10.2 thinking 重复

模型每轮 thinking 都重新分析整个上下文 → 大量冗余。

防御:

  • 让 thinking 聚焦"本轮新增决策"而非"全局回顾"
  • 这部分靠模型训练,prompt 调控有限

10.3 thinking 暴露隐私

用户不希望被记录的话进入 thinking → thinking 进入日志 → 隐私泄漏。

防御:日志层处理 thinking 块的脱敏(参见 20 篇,隐私)。

10.4 用户基于 thinking 质疑导致争论

用户看到 thinking 里有"用户可能不懂这个" → 觉得被冒犯 → 产生争论。

防御:UI 默认折叠 thinking,避免用户被无关内容刺激。

10.5 signature 失效后无法续传

序列化时 thinking 字段丢失或被改 → 下次 API 调用拒绝。

防御:序列化要包含完整 block + signature。重放时不要修改 thinking 内容。


11. 给 Agent 设计者的可迁移规则

  1. thinking 不免费:每段都计费、都进 cache、都进上下文
  2. 不能单独删 thinking:要么整 message 保留,要么整 message 摘要替换
  3. budget 不动态调:参与 cache key
  4. 按任务类型选 budget:simple 2k、medium 8k、complex 16k
  5. interleaved 按需启用:不要全局开
  6. UI 默认折叠:用户能看但不强制看
  7. prompt 里别再写 step-by-step:thinking 已经做了
  8. signature 必须完整保留:序列化、重放不要碰
  9. thinking 优先压缩:长会话压缩时 thinking 块是首选目标
  10. 日志 thinking 块要脱敏:可能含敏感信息

12. 一句话总结

extended thinking 把"模型的内心独白"变成上下文的一部分 —— 它带来推理质量提升,也带来 token、压缩、序列化的新约束。把它当成"特殊类型的 block"而不是"魔法功能",工程上才能驾驭。

下一篇:17 · 超大 Context 的边际成本