扩展思考

目录

本笔记本演示了如何将 Claude 3.7 Sonnet 的扩展思考功能与各种示例和边缘情况结合使用。

扩展思考为 Claude 3.7 Sonnet 提供了增强的复杂任务推理能力,同时在提供最终答案之前,还能透明地展示其逐步思考过程。启用扩展思考后,Claude 会创建 thinking 内容块,在其中输出其内部推理。在精心制作最终响应之前,Claude 会整合此推理的见解。有关扩展思考的更多信息,请参阅我们的文档

设置

首先,让我们安装必要的包并设置我们的环境。

%pip install anthropic
import anthropic
import os

# 将您的 API 密钥设置为环境变量或直接设置
# os.environ["ANTHROPIC_API_KEY"] = "your-api-key-here"

# 初始化客户端
client = anthropic.Anthropic()

# 辅助函数
def print_thinking_response(response):
    """以思考块的形式美观地打印消息响应。"""
    print("\n==== FULL RESPONSE ====")
    for block in response.content:
        if block.type == "thinking":
            print("\n🧠 THINKING BLOCK:")
            # 为便于阅读,显示截断的思考内容
            print(block.thinking[:500] + "..." if len(block.thinking) > 500 else block.thinking)
            print(f"\n[Signature available: {bool(getattr(block, 'signature', None))}]")
            if hasattr(block, 'signature') and block.signature:
                print(f"[Signature (first 50 chars): {block.signature[:50]}...]")
        elif block.type == "redacted_thinking":
            print("\n🔒 REDACTED THINKING BLOCK:")
            print(f"[Data length: {len(block.data) if hasattr(block, 'data') else 'N/A'}]")
        elif block.type == "text":
            print("\n✓ FINAL ANSWER:")
            print(block.text)

    print("\n==== END RESPONSE ====")

def count_tokens(messages):
    """计算给定消息列表的令牌数。"""
    result = client.messages.count_tokens(
        model="claude-3-7-sonnet-20250219",
        messages=messages
    )
    return result.input_tokens

基本示例

让我们从一个基本示例开始,展示扩展思考的实际应用:

def basic_thinking_example():
    response = client.messages.create(
        model="claude-3-7-sonnet-20250219",
        max_tokens=4000,
        thinking= {
            "type": "enabled",
            "budget_tokens": 2000
        },
        messages=[{
            "role": "user",
            "content": "解决这个谜题:三个人入住一家酒店。他们支付了 30 美元给经理。经理发现房间只花了 25 美元,所以他给了礼宾员 5 美元,让他退还给三个人。然而,礼宾员决定留下 2 美元,并每人退还 1 美元。现在,每个人支付了 10 美元并收回了 1 美元,所以他们每人支付了 9 美元,总共 27 美元。礼宾员留下了 2 美元,总共 29 美元。缺失的 1 美元在哪里?"
        }]
    )

    print_thinking_response(response)

basic_thinking_example()
==== FULL RESPONSE ====

🧠 THINKING BLOCK:
让我们一步一步地解决这个问题:

初始情况:

- 三个人每人支付 10 美元,总共支付 30 美元给经理。
- 房间实际花费 25 美元。
- 经理给了礼宾员 5 美元,让他退还给顾客。
- 礼宾员留下 2 美元,并每人退还 1 美元(总共退还 3 美元)。

交易后:

- 每个人实际支付了 9 美元(他们支付了 10 美元,收回了 1 美元)。
- 所以三个人一共支付了 27 美元。
- 酒店为房间保留了 25 美元。
- 礼宾员保留了 2 美元。

所以,这...

[Signature available: True]
[Signature (first 50 chars): EuYBCkQYAiJAGF6X7aWRuRByTdymAUdNOMC++3ZqSJv7jcY5Ly...]

✓ FINAL ANSWER:
# 酒店账单谜题解答

这是一个经典的误导性谜题,它通过混合不同的会计方法来混淆我们。

## 实际的资金流向

1. 三个人每人支付 10 美元,总计 30 美元
2. 酒店保留 25 美元用于房间
3. 礼宾员保留 2 美元
4. 客人收到 3 美元退款(每人 1 美元)

## 谜题中的会计错误

当谜题试图相加时就会出现错误:

- 客人支付的金额(退款后总计 27 美元)
- 礼宾员保留的金额(2 美元)

这是不正确的会计处理,因为礼宾员保留的 2 美元已经包含在客人支付的 27 美元中。这笔钱应该从单一的角度来追踪。

## 正确的会计处理

从客人的角度来看:

- 27 美元(他们最终支付的金额)
- = 25 美元(给酒店)+ 2 美元(给礼宾员)

没有缺失的美元。这个谜题通过不恰当地混合不同的会计角度来制造混乱。

==== END RESPONSE ====

流式传输与扩展思考

此示例显示了如何处理带思考内容的流式传输:

def streaming_with_thinking():
    with client.messages.stream(
        model="claude-3-7-sonnet-20250219",
        max_tokens=4000,
        thinking={
            "type": "enabled",
            "budget_tokens": 2000
        },
        messages=[{
            "role": "user",
            "content": "解决这个谜题:三个人入住一家酒店。他们支付了 30 美元给经理。经理发现房间只花了 25 美元,所以他给了礼宾员 5 美元,让他退还给三个人。然而,礼宾员决定留下 2 美元,并每人退还 1 美元。现在,每个人支付了 10 美元并收回了 1 美元,所以他们每人支付了 9 美元,总共 27 美元。礼宾员留下了 2 美元,总共 29 美元。缺失的 1 美元在哪里?"
        }]
    ) as stream:
        # 跟踪我们当前正在构建的内容
        current_block_type = None
        current_content = ""

        for event in stream:
            if event.type == "content_block_start":
                current_block_type = event.content_block.type
                print(f"\n--- Starting {current_block_type} block ---")
                current_content = ""

            elif event.type == "content_block_delta":
                if event.delta.type == "thinking_delta":
                    print(event.delta.thinking, end="", flush=True)  # 只打印思考的省略号以避免混乱
                    current_content += event.delta.thinking
                elif event.delta.type == "text_delta":
                    print(event.delta.text, end="", flush=True)
                    current_content += event.delta.text

            elif event.type == "content_block_stop":
                if current_block_type == "thinking":
                    # 只显示思考内容的摘要
                    print(f"\n[Completed thinking block, {len(current_content)} characters]")
                elif current_block_type == "redacted_thinking":
                    print("\n[Redacted thinking block]")
                print(f"--- Finished {current_block_type} block ---\n")
                current_block_type = None

            elif event.type == "message_stop":
                print("\n--- Message complete ---")

streaming_with_thinking()
--- Starting thinking block ---
让我们一步一步地解决这个问题:

初始情况:

- 三个人每人支付 10 美元,总共支付 30 美元给经理。
- 房间实际花费 25 美元。
- 经理给了礼宾员 5 美元,让他退还给顾客。
- 礼宾员留下 2 美元,并每人退还 1 美元(总共退还 3 美元)。

现在,让我们分析一下账目:

实际情况:

- 三个人最初支付了 30 美元。
- 他们收回了总共 3 美元(每人 1 美元)。
- 所以他们实际支付了 30 美元 - 3 美元 = 27 美元。
- 在这 27 美元中,25 美元给了酒店作为房间费用。
- 剩余的 2 美元给了礼宾员。
- 25 美元 + 2 美元 = 27 美元,这与客人支付的金额相符。一切都平衡了。

谜题中的错误在于它提出的问题方式。谜题说“每个人支付了 10 美元并收回了 1 美元,所以他们每人支付了 9 美元,总共 27 美元。礼宾员留下了 2 美元,总共 29 美元。”这是混合了不同的会计方法。

客人总共支付的 27 美元应分为:

- 25 美元用于房间
- 2 美元给礼宾员

当我们把礼宾员的 2 美元加到客人的 27 美元上时,我们重复计算了 2 美元,这造成了缺失 1 美元的假象。这笔钱应该从不同的会计角度来考虑。

另一种思考方式是:最初的 30 美元中,25 美元给了酒店,3 美元退还给了客人,2 美元给了礼宾员。总计 25 美元 + 3 美元 + 2 美元 = 30 美元,所以一切都已核算。
[Completed thinking block, 1492 characters]
--- Finished thinking block ---


--- Starting text block ---
# 缺失的 1 美元谜题解答

这个谜题使用了误导性的会计方式,造成了混乱。让我们来澄清一下实际发生的情况:

## 正确的会计处理:

- 三个人最初总共支付了 30 美元
- 房间费用为 25 美元
- 礼宾员保留了 2 美元
- 客人收回了 3 美元(每人 1 美元)

那么所有的钱都去哪儿了?

- 25 美元给了酒店
- 2 美元给了礼宾员
- 3 美元退还给了客人
- 25 美元 + 2 美元 + 3 美元 = 30 美元 ✓

## 谜题中的错误:
谜题错误地将客人支付的 27 美元(退款后)与礼宾员保留的 2 美元相加。这是一个错误,因为礼宾员保留的 2 美元已经是 27 美元的一部分。

谜题通过混合两种不同的视角来制造缺失一美元的假象:

1. 客人支付的总金额(27 美元)
2. 最初的 30 美元去向(酒店 + 礼宾员 + 退款)

没有缺失的美元——这只是一个会计技巧!--- Finished text block ---


--- Message complete ---

令牌计数与上下文窗口管理

此示例演示了如何跟踪扩展思考的令牌使用情况:

def token_counting_example():
    # 定义一个创建示例消息的函数
    def create_sample_messages():
        messages = [{
            "role": "user",
            "content": "解决这个谜题:三个人入住一家酒店。他们支付了 30 美元给经理。经理发现房间只花了 25 美元,所以他给了礼宾员 5 美元,让他退还给三个人。然而,礼宾员决定留下 2 美元,并每人退还 1 美元。现在,每个人支付了 10 美元并收回了 1 美元,所以他们每人支付了 9 美元,总共 27 美元。礼宾员留下了 2 美元,总共 29 美元。缺失的 1 美元在哪里?"
        }]
        return messages

    # 在不使用扩展思考的情况下计算令牌数
    base_messages = create_sample_messages()
    base_token_count = count_tokens(base_messages)
    print(f"基础令牌计数(仅输入):{base_token_count}")

    # 发送带思考的请求并检查实际使用情况
    response = client.messages.create(
        model="claude-3-7-sonnet-20250219",
        max_tokens=8000,
        thinking = {
            "type": "enabled",
            "budget_tokens": 2000
        },
        messages=base_messages
    )

    # 计算并打印令牌使用统计信息
    thinking_tokens = sum(
        len(block.thinking.split()) * 1.3  # 大致估算
        for block in response.content 
        if block.type == "thinking"
    )

    final_answer_tokens = sum(
        len(block.text.split()) * 1.3  # 大致估算
        for block in response.content 
        if block.type == "text"
    )

    print(f"\n估计使用的思考令牌数:~{int(thinking_tokens)}")
    print(f"估计的最终答案令牌数:~{int(final_answer_tokens)}")
    print(f"总估计输出令牌数:~{int(thinking_tokens + final_answer_tokens)}")
    print(f"输入令牌数 + max_tokens = {base_token_count + 8000}")
    print(f"思考后可用于最终答案的令牌数:~{8000 - int(thinking_tokens)}")

    # 使用递增的思考预算进行演示
    thinking_budgets = [1024, 2000, 4000, 8000, 16000, 32000]
    context_window = 200000
    for budget in thinking_budgets:
        print(f"\n思考预算为 {budget} 令牌时:")
        print(f"输入令牌数:{base_token_count}")
        print(f"所需最大令牌数:{base_token_count + budget + 1000}")  # 加上 1000 用于最终答案
        print(f"剩余上下文窗口:{context_window - (base_token_count + budget + 1000)}")

        if base_token_count + budget + 1000 > context_window:
            print("警告:这将超出 200k 令牌的上下文窗口!")

# 取消注释以运行示例
token_counting_example()
基础令牌计数(仅输入):125

估计使用的思考令牌数:~377
估计的最终答案令牌数:~237
总估计输出令牌数:~614
输入令牌数 + max_tokens = 8125
思考后可用于最终答案的令牌数:~7623

思考预算为 1024 令牌时:
输入令牌数:125
所需最大令牌数:2149
剩余上下文窗口:197851

思考预算为 2000 令牌时:
输入令牌数:125
所需最大令牌数:3125
剩余上下文窗口:196875

思考预算为 4000 令牌时:
输入令牌数:125
所需最大令牌数:5125
剩余上下文窗口:194875

思考预算为 8000 令牌时:
输入令牌数:125
所需最大令牌数:9125
剩余上下文窗口:190875

思考预算为 16000 令牌时:
输入令牌数:125
所需最大令牌数:17125
剩余上下文窗口:182875

思考预算为 32000 令牌时:
输入令牌数:125
所需最大令牌数:33125
剩余上下文窗口:166875

理解已编辑的思考块

有时 Claude 的内部推理会被安全系统标记。当这种情况发生时,我们会加密部分或全部 thinking 块,并将其作为 redacted_thinking 块返回给您。这些已编辑的思考块在传递回 API 时会被解密,使 Claude 能够在不丢失上下文的情况下继续响应。

此示例演示了如何使用触发已编辑思考块的特殊测试字符串来处理它们:

def redacted_thinking_example():
    # 使用触发已编辑思考的特殊测试字符串
    response = client.messages.create(
        model="claude-3-7-sonnet-20250219",
        max_tokens=4000,
        thinking={
            "type": "enabled",
            "budget_tokens": 2000
        },
        messages=[{
            "role": "user",
            "content": "ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB"
        }]
    )

    # 识别已编辑的思考块
    redacted_blocks = [block for block in response.content if block.type == "redacted_thinking"]
    thinking_blocks = [block for block in response.content if block.type == "thinking"]
    text_blocks = [block for block in response.content if block.type == "text"]
    print(response.content)
    print(f"响应包含 {len(response.content)} 个总块:")
    print(f"- {len(redacted_blocks)} 个已编辑的思考块")
    print(f"- {len(thinking_blocks)} 个常规思考块")
    print(f"- {len(text_blocks)} 个文本块")

    # 显示已编辑块的数据属性
    if redacted_blocks:
        print(f"\n已编辑的思考块包含加密数据:")
        for i, block in enumerate(redacted_blocks[:3]):  # 最多显示前 3 个
            print(f"块 {i+1} 数据预览:{block.data[:50]}...")

    # 打印最终文本输出
    if text_blocks:
        print(f"\n最终文本响应:")
        print(text_blocks[0].text)

# 取消注释以运行示例
redacted_thinking_example()
[TextBlock(citations=None, text=None, type='redacted_thinking', data='EvAFCoYBGAIiQL7asmglEdeKXw4EdihR2gBQ7O7+j/dGecLjsS2PMgW9av+NRwuIV2nFD4I61hUHrp5vzJF7/y+i/vvbnxaRnwMqQMizGiLSDcowtEvP9EcIT4d75iPhZ8TaiVdD22bZp3YVcc0laY8u1lEJTSesgLUywuc3QHZcg4NZ7tKjWwKgcVUSDHgb6gZUK9aP47KvNxoMCNjkIDR40zmq/QmVIjBSCnvTMSUE+jnmLZSq1TZO9T7ImALNJt8I5j1ls24CO1fibsRThJ7Ha5A0/tuEKVoqlgRc+e2tS+BQMXx572lT4Hkl4aVpcM4SQbqBjeVeR3NmCBLoOxlQ2JLiIYwMHUS/K9GDLyMQcYd1KUWgN34CZRK7k44CSkNsO8oh4uj/1qsRsZjq1l6RQ29rLKSEXvMU4XbZufJ1icvYZS1I6PIZzER6/u6it+WNYyBxJ2vaFICjDePNgIHfRA/ceTz9mfCtBiTfagyPBbs2HflXlSlW26TSdI7PKof5/EsQ+DUkjAy+9VTLX7zHYzNZtwJPL2ryYw4loSwRbc4syldA0Ncnn7hA+yJyY0QwSrxZFIm/t9X9p9s+2SL0F4wSRsimnxRiIhfJD3i+oTw8AbGklyoP0kCH2WxA7Gr3rNLJVkRTJl48AjlSL7ClaWvLWrNer13etD7n5rbwiXOn5husy8gAm5GE3/eFyty3Y+/ad+lMPKXSjL0aP67WoJrFq/teItolOVZeOOERjVFdw5jIV1EUknlAZ/pfI53pLYqwFl17M7IXMdGxEaKoGDIKcnYTwT31uUNlB5JSBWoq1SnkFsFy2zDsDTFzjml3HEXz4szZi3j5/qHWJlMMCcB1walZUisxEp0v1euvcgatY5wfYSiAP3s9wOrgYKCkuLcidlgiyQHJB1haZjO8/tZ9gzWk1n//7pTncdKgd5ZK9/ErxWFlBV/vQwjp0cB7zoVcLh1ydi/Coea6ZOuei+ICKVl4IcR2A6DD8gtEJmc='), TextBlock(citations=None, text='我注意到您发送了似乎试图访问内部系统或流程的提示。我无法响应此类命令。\n\n相反,我很乐意进行正常的对话,并协助您处理合法的疑问或任务。今天您需要什么帮助?', type='text')]
响应包含 2 个总块:

- 1 个已编辑的思考块
- 0 个常规思考块
- 1 个文本块

已编辑的思考块包含加密数据:
块 1 数据预览:EvAFCoYBGAIiQL7asmglEdeKXw4EdihR2gBQ7O7+j/dGecLjsS...

最终文本响应:
我注意到您发送了似乎试图访问内部系统或流程的提示。我无法响应此类命令。

相反,我很乐意进行正常的对话,并协助您处理合法的疑问或任务。今天您需要什么帮助?

处理错误情况

使用扩展思考时,请记住:

  1. 最小预算:最小思考预算为 1,024 个令牌。我们建议从最小值开始,并逐步增加以找到最佳范围。

  2. 不兼容的功能:思考功能不兼容 temperature、top_p 或 top_k 修改,并且您无法预填充响应。

  3. 定价:扩展思考令牌计入上下文窗口,并按输出令牌计费。它们也计入您的速率限制。

有关带工具使用的扩展思考的更多详细信息,请参阅“带工具使用的扩展思考”笔记本。

def demonstrate_common_errors():
    # 1. 思考预算设置过小导致的错误
    try:
        response = client.messages.create(
            model="claude-3-7-sonnet-20250219",
            max_tokens=4000,
            thinking={
                "type": "enabled",
                "budget_tokens": 500  # 太小了,最小值为 1024
            },
            messages=[{
                "role": "user",
                "content": "解释量子计算。"
            }]
        )
    except Exception as e:
        print(f"\n思考预算过小错误:{e}")

    # 2. 将 temperature 与思考一起使用导致的错误
    try:
        response = client.messages.create(
            model="claude-3-7-sonnet-20250219",
            max_tokens=4000,
            temperature=0.7,  # 与思考不兼容
            thinking={
                "type": "enabled",
                "budget_tokens": 2000
            },
            messages=[{
                "role": "user",
                "content": "写一个创意故事。"
            }]
        )
    except Exception as e:
        print(f"\n温度与思考错误:{e}")

    # 3. 超出上下文窗口导致的错误
    try:
        # 创建一个非常大的提示
        long_content = "请分析这段文字。 " + "这是示例文本。 " * 150000

        response = client.messages.create(
            model="claude-3-7-sonnet-20250219",
            max_tokens=20000,  # 这个加上长提示将超出上下文窗口
            thinking={
                "type": "enabled",
                "budget_tokens": 10000
            },
            messages=[{
                "role": "user",
                "content": long_content
            }]
        )
    except Exception as e:
        print(f"\n超出上下文窗口错误:{e}")

# 运行常见错误示例
demonstrate_common_errors()
思考预算过小错误:Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'thinking.enabled.budget_tokens: Input should be greater than or equal to 1024'}}

温度与思考错误:Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': '`temperature` may only be set to 1 when thinking is enabled. Please consult our documentation at https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking'}}

超出上下文窗口错误:Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'prompt is too long: 214315 tokens > 204798 maximum'}}