系列第 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 优先被压
为什么?三个原因:
- 大:单块几千 token,压缩收益高
- 冗余:thinking 内容大量是"探索性思考",最终结论已在 text/tool_use 里体现
- 历史价值低:模型当下需要的是结论和工具结果,不是"我曾经怎么想的"
所以压缩算法的优先级排序里,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 设计者的可迁移规则
- thinking 不免费:每段都计费、都进 cache、都进上下文
- 不能单独删 thinking:要么整 message 保留,要么整 message 摘要替换
- budget 不动态调:参与 cache key
- 按任务类型选 budget:simple 2k、medium 8k、complex 16k
- interleaved 按需启用:不要全局开
- UI 默认折叠:用户能看但不强制看
- prompt 里别再写 step-by-step:thinking 已经做了
- signature 必须完整保留:序列化、重放不要碰
- thinking 优先压缩:长会话压缩时 thinking 块是首选目标
- 日志 thinking 块要脱敏:可能含敏感信息
12. 一句话总结
extended thinking 把"模型的内心独白"变成上下文的一部分 —— 它带来推理质量提升,也带来 token、压缩、序列化的新约束。把它当成"特殊类型的 block"而不是"魔法功能",工程上才能驾驭。