Chapter 04

Claude 扩展思考模式:Extended Thinking API

Anthropic 于 2025 年在 Claude 3.7 Sonnet 中引入扩展思考,并持续在后续版本中增强。这是目前工程上最易用、最透明的推理模型 API。

Extended Thinking 的核心特性

扩展思考(Extended Thinking)
Claude 的推理模式,通过 API 参数 thinking: {type: "enabled", budget_tokens: N} 启用。启用后,Claude 会在生成最终答案前产生一段内部推理过程(thinking block),这个过程对开发者可见。
budget_tokens(思考预算)
控制推理过程可以使用的最大 token 数量。这是"上限"而非"保证"——Claude 会根据问题复杂度自主决定实际使用多少。简单问题即使预算 10000,实际可能只用 500。推荐起始值:5000-8000。
Thinking Block(思考块)
响应中 type 为 "thinking" 的 content block,包含 Claude 的完整推理过程。这是推理模型相比 o1 的独特优势:OpenAI o1 完全隐藏思维链,而 Claude 允许开发者查看完整推理,便于调试和理解。
Text Block(文本块)
响应中 type 为 "text" 的 content block,包含最终的用户可见答案。在多轮对话中,必须将两种 block 都传回给 API,Claude 依赖 thinking block 保持推理连贯性。

基本使用:最简示例

import anthropic

client = anthropic.Anthropic()

# 最简单的扩展思考调用
response = client.messages.create(
    model="claude-sonnet-4-6",  # 支持思考的模型
    max_tokens=16000,           # max_tokens 必须 > budget_tokens(容纳思考+回答)
    thinking={
        "type": "enabled",
        "budget_tokens": 10000  # 最多允许思考 10000 个 token
    },
    messages=[{
        "role": "user",
        "content": "证明:√2 是无理数"
    }]
)

# 遍历响应中的所有 content block
for block in response.content:
    if block.type == "thinking":
        # thinking 包含 Claude 的完整推理过程(草稿)
        print("[思考过程(前200字)]")
        print(block.thinking[:200] + "...")
        print(f"[思考共 {len(block.thinking)} 字符]")
    elif block.type == "text":
        # text 是最终用户可见的答案
        print("\n[最终答案]")
        print(block.text)

# 查看 token 使用量
print(f"\n输入 Token: {response.usage.input_tokens}")
print(f"输出 Token(含思考): {response.usage.output_tokens}")

响应结构完整解析

# 完整响应结构(JSON 示意)
{
  "id": "msg_01Xfn...",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "thinking",
      # thinking 字段是 Claude 的"草稿纸"内容
      # 注意:内容可能包含错误的中间步骤(这是正常的!)
      # Claude 可能探索多条路径,最终只采用最优路径的结论
      "thinking": "我需要证明 √2 是无理数...\n假设 √2 = p/q(最简分数)...\n则 2 = p²/q²...\n所以 p² = 2q²,p² 是偶数,所以 p 是偶数...\n设 p = 2k...\n则 4k² = 2q²,q² = 2k²...\nq 也是偶数!\n但 p/q 是最简分数,p 和 q 不能都是偶数,矛盾!\n所以假设错误,√2 是无理数。"
    },
    {
      "type": "text",
      "text": "**证明 √2 是无理数(反证法)**\n\n**假设** √2 是有理数,则可以写成最简分数 p/q..."
    }
  ],
  "usage": {
    "input_tokens": 30,           # 用户输入的 token 数
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0,
    "output_tokens": 8420        # 包含思考 token + 文本 token
  }
}

budget_tokens 的影响与选择

budget_tokens 是最重要的参数,直接影响答案质量和成本:

budget_tokens适用场景平均实际思考量相对成本
0(禁用)不需要推理的任务(翻译、摘要)0基准
1,000 – 2,000简单多步问题、基本验证500-15003-6x
5,000 – 8,000中等复杂度(日常推理,推荐默认值)2000-600010-20x
10,000 – 16,000数学竞赛、复杂代码分析5000-1200020-40x
32,000+极复杂研究问题、多步骤规划10000-3000040-100x
budget_tokens 是上限,不是保证

设置 budget_tokens=10000 不代表模型一定会思考 10000 个 token。Claude 会根据问题复杂度自主决定实际使用多少。对简单问题设置很高的预算只会浪费"等待时间",不会提高答案质量(但不会额外收费)。

另外,max_tokens 必须设置为大于 budget_tokens 的值(因为 thinking + text 都计入 max_tokens)。推荐设置:max_tokens = budget_tokens + 4000

流式输出:实时显示思考过程

def stream_with_thinking(prompt: str, budget: int = 8000) -> tuple[str, str]:
    """流式输出,实时展示思考进度,返回 (thinking, answer)"""
    thinking_parts = []
    answer_parts = []
    current_block_type = None

    with client.messages.stream(
        model="claude-sonnet-4-6",
        max_tokens=budget + 4000,
        thinking={"type": "enabled", "budget_tokens": budget},
        messages=[{"role": "user", "content": prompt}]
    ) as stream:
        for event in stream:
            event_type = getattr(event, 'type', None)

            if event_type == 'content_block_start':
                # 新 block 开始,记录类型
                current_block_type = event.content_block.type
                if current_block_type == 'thinking':
                    print("\n[思考中]", end="", flush=True)
                elif current_block_type == 'text':
                    print("\n\n[回答]\n", flush=True)

            elif event_type == 'content_block_delta':
                # 内容增量,区分 thinking 和 text
                if current_block_type == 'thinking':
                    # 思考时只显示进度点,不显示原始推理(可选择显示)
                    print(".", end="", flush=True)
                    thinking_parts.append(event.delta.thinking)
                elif current_block_type == 'text':
                    # 最终答案实时流式输出
                    print(event.delta.text, end="", flush=True)
                    answer_parts.append(event.delta.text)

            elif event_type == 'message_stop':
                print()  # 换行

    return "".join(thinking_parts), "".join(answer_parts)

多轮对话:如何保留思考上下文

这是 Extended Thinking 最容易出错的地方。在多轮对话中,必须将上一轮的完整 content(包含 thinking block)传回给 API——Claude 依赖 thinking block 中的推理来保持对话连贯性。

class ThinkingConversation:
    """多轮对话管理器,正确处理 thinking block 的历史传递"""

    def __init__(self, budget: int = 5000):
        self.history = []          # 完整的对话历史
        self.budget = budget

    def chat(self, user_message: str) -> str:
        """发送消息并获取回复,自动管理 thinking block"""
        self.history.append({
            "role": "user",
            "content": user_message
        })

        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=self.budget + 4000,
            thinking={"type": "enabled", "budget_tokens": self.budget},
            messages=self.history
        )

        # 关键:将完整响应(含 thinking block)加入历史
        # 不能只保存 text block!Claude 需要 thinking 来理解上下文
        self.history.append({
            "role": "assistant",
            "content": response.content  # 传入 list,包含 thinking+text blocks
        })

        # 只返回 text block 给用户显示
        text_blocks = [b for b in response.content if b.type == "text"]
        return text_blocks[0].text if text_blocks else ""

    def get_thinking_history(self) -> list[str]:
        """获取所有轮次的推理过程(用于调试)"""
        thinking_list = []
        for msg in self.history:
            if msg["role"] == "assistant" and isinstance(msg["content"], list):
                for block in msg["content"]:
                    if hasattr(block, 'type') and block.type == "thinking":
                        thinking_list.append(block.thinking)
        return thinking_list

# 使用示例
conv = ThinkingConversation(budget=5000)
r1 = conv.chat("请解释什么是 P vs NP 问题")
r2 = conv.chat("如果 P=NP 被证明,会有什么影响?")  # 基于上下文继续
r3 = conv.chat("给出一个具体的密码学影响案例")   # 继续深入

Prompt Caching + Extended Thinking 组合

对于需要固定长系统提示的应用(如专业知识库、代码审查规范),Prompt Caching 可以大幅降低重复输入成本:

# 组合使用:Prompt Caching + Extended Thinking
# 适合:固定系统提示 + 多个用户查询的场景

SYSTEM_PROMPT = """你是一位资深数学导师,专注于帮助学生理解高等数学。
你会用清晰的步骤解释每个概念,并检查推理过程的严密性。
对于证明题,你会:
1. 先分析证明思路
2. 选择合适的证明方法(直接证明/反证法/数学归纳法)
3. 逐步写出严格的证明步骤
4. 最后检验证明的完整性
"""  # 这个 prompt 超过 1024 tokens 时缓存最有价值

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=14000,
    thinking={"type": "enabled", "budget_tokens": 8000},
    # System prompt 使用列表格式以启用缓存
    system=[{
        "type": "text",
        "text": SYSTEM_PROMPT,
        "cache_control": {"type": "ephemeral"}  # 标记为可缓存
    }],
    messages=[{"role": "user", "content": question}]
)

# 检查缓存效果
usage = response.usage
print(f"缓存写入: {usage.cache_creation_input_tokens} tokens(首次调用)")
print(f"缓存命中: {usage.cache_read_input_tokens} tokens(后续调用)")
# 缓存命中时,这些 token 费率约为普通输入的 10%!
# 对于 2000 token 的系统提示,每次节省 ~90% 的输入费用

o1 vs Claude Extended Thinking 对比

特性OpenAI o1/o3Claude Extended Thinking
思维链可见性完全隐藏(安全考虑)完全暴露给开发者
思考预算控制不支持(自动决定)支持 budget_tokens 精确控制
结构化输出部分支持需要 Two-Stage 方案(见第7章)
多轮对话标准 API需要传回完整 content(含 thinking)
Prompt Caching不支持支持,可大幅降低重复调用成本
流式输出支持支持,thinking 和 text 分别流式
调试友好性低(黑盒)高(可查看完整推理过程)
何时选择 Claude Extended Thinking

如果你的应用需要:(1) 调试和优化推理质量,(2) 向用户展示推理过程(提高可信度),(3) 精确控制思考预算,(4) 结合 Prompt Caching 降低成本——那么 Claude Extended Thinking 是更好的选择。如果你只需要"结果"而不关心推理过程,o1 的隐式推理也同样有效。

本章小结

Extended Thinking API 通过 budget_tokens 控制思考深度(上限而非保证),响应包含 thinking(推理草稿)和 text(最终答案)两种 block。多轮对话必须将完整 content 传回 API。Prompt Caching 与 Extended Thinking 可以组合使用,降低重复调用成本。Claude 相比 o1 的独特优势是思维链对开发者可见,便于调试和优化。下一章进入推理模型的提示工程技巧——很多普通模型的最佳实践在推理模型上适得其反。