Claude API

思考模式

多轮会话中的思考内容

Claude的haiku模型输出的thinking内容不会做为下一轮的输入tokens。并且,启用与不启用思考功能时,对于相同的问题占用的input tokens不一样,估计是API会针对启用思考模式的请求默认增加一些固定提示词。

将思考内容做为会话历史传给haiku模型是会被haiku忽略的,也不会统计进input tokens。

但是换做opussonnet 4.6+ 模型后,思考内容默认会统计进input tokens,并且计费。 如果不希望将思考内容做为下轮会话的参考内容,则需要手动剥离掉。

下面是未开启思考模式

import os
import dotenv
import anthropic

client = anthropic.Anthropic()
model = 'claude-haiku-4-5'
max_tokens = 20480


messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "How many \"s\" in \"assistant\"?"
                }
            ]
        }
    ]

thinking = {"type": "enabled", "budget_tokens": 16000},


res = client.messages.count_tokens(
    model = 'claude-haiku-4-5',
    messages = messages
)

# res
# MessageTokensCount(input_tokens=17)

下面开启思考模式

res_think = client.messages.count_tokens(
    model = model,
    thinking = thinking,
    messages = messages
)

# res_think
# MessageTokensCount(input_tokens=46)

可以看到,相同的提示词,未开启思考时,input tokens是17,开启思考模式时,input tokens就变成了46.

看一下第二轮会话里将第一次模型回复内容传入的情况:

response = client.messages.create(
    model = model,
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "How many \"s\" in \"assistant\"?"
                }
            ]
        }
    ]
)
print(response.usage.model_dump_json())

下面输出可以看出,输入tokens确实为46

{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"inference_geo":"not_available","input_tokens":46,"output_tokens":209,"server_tool_use":null,"service_tier":"standard"}

这里输出一下完整的response:

{
  "id": "msg_01HiqA6aKsjKkBipXrZqDjCP",
  "container": null,
  "content": [
    {
      "signature": "EpsECm0IDhgCKkCphpOUJMracaF+bjuVoql3e7SHVm4G2n5J1afKBDMydZzxwsqPikTi44eFKiZEjMrTrJBYfrEHBNAaKDTFhb8FMhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nEgwfBp8aRTh/tUb3H+MaDBlh3vs+LG6Sr1KrVyIwpP8VVS7f6rjRYEDG3SnM2aE6hl1uZjvIRNjcNl5/a3A78jmbCdRLPoPCRZPOs+S8KtsCiBLg1bzzQ/bUbbyj0kwiLenBCJWLeGdFKu5hmmbSZxAJGzzLNF0QK2dEF6ebgWzOxsefqVCYlfwJCUFLBYSjqi1IOK/DxKt709kOgMbyB6D0MFb3/IdU4bkbfST2M4MPyTBv9ZXPa71d3Nsx1sksE5Hw8wVfsV6dae/ESLiEZusITD4RmVa8HYLsL1kcXEs8Dz2cqs/waw54U71y8tESYSFEo0CscEizQlXDddgH16Yn8TdRmhTLRqzoU6tSRWuKvPev3X5Rf3cdO+b6Unuh0+HktWOwiNYokt77gjEhkWTwaEFK+DzxiN/s2xODqygL3zJrN36afTl4xm0MZSdhZChEOskovYNScGjSBb5RJkGcoeZ3NED27PjEJNurkt3eS3IOUUV2IwiZ+pgHeZVZdtcxR7Tw8OFAs43711FYQu9QAMZARxHIMDcw09SGKmvTsuKhcc0ptv0w0tIYAQ==",
      "thinking": "Let me count the letter \"s\" in the word \"assistant\".\n\nThe word is: a-s-s-i-s-t-a-n-t\n\nLet me go through each letter:\n- a: not an s\n- s: this is an s (1st s)\n- s: this is an s (2nd s)\n- i: not an s\n- s: this is an s (3rd s)\n- t: not an s\n- a: not an s\n- n: not an s\n- t: not an s\n\nSo there are 3 \"s\" letters in the word \"assistant\".",
      "type": "thinking"
    },
    {
      "citations": null,
      "text": "To count the letter \"s\" in \"assistant\", I'll go through each letter:\n\na-**s**-**s**-i-**s**-t-a-n-t\n\nThere are **3** \"s\" letters in \"assistant\".",
      "type": "text"
    }
  ],
  "model": "claude-haiku-4-5-20251001",
  "role": "assistant",
  "stop_details": null,
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "type": "message",
  "usage": {
    "cache_creation": {
      "ephemeral_1h_input_tokens": 0,
      "ephemeral_5m_input_tokens": 0
    },
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0,
    "inference_geo": "not_available",
    "input_tokens": 46,
    "output_tokens": 209,
    "server_tool_use": null,
    "service_tier": "standard"
  }
}

将模型回复内容(包括思考内容)做为下轮提问的会话历史。

res2_think = client.messages.count_tokens(
    model = model,
    thinking = {"type": "enabled", "budget_tokens": 16000},
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "How many \"s\" in \"assistant\"?"
                }
            ]
        },
        {
            "role": "assistant",
            "content": response.content,
        },
        {
            "role": "user",
            "content": "are you sure?"
        },
        
    ]
)

# res2_think
# MessageTokensCount(input_tokens=112)

下面去掉思考内容,再重新计算tokens

res2_nothink = client.messages.count_tokens(
    model = model,
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "How many \"s\" in \"assistant\"?"
                }
            ]
        },
        {
            "role": "assistant",
            "content": [response.content[1]],
        },
        {
            "role": "user",
            "content": "are you sure?"
        },
        
    ]
)
# res2_nothink
# MessageTokensCount(input_tokens=112)

思考模式的启用方式

不同模型支持的思考启用方式不同。
haiku模型以及sonnet和opus 4.5之前的模型,启用方式为 thinking={"type": "enabled", "budget_tokens": 16000}, 这里budget_tokens不能省略。
对于sonnet和opus 4.5+模式,推荐使用(并且上面那种方式将来会被丢弃) thinking={"type": "adaptive"},并可配合effort参数来控制思考深度。
不做设置的时候,默认是不启用思考模式的。

思考摘要(Summarized thinking)

思考摘要是由另一个模型来生成的,而当前思考的模型其实并不能看到这个思考摘要。(真正的思考内容是在signature中,并且是加密的) 思考摘要由display参数控制,opus 4.7 默认不显示思考摘要(即display="omitted")。而sonnet 4.6和haiku等都是默认显示搞要的,(即display="summarized")。 可以通过手动设置这个参数来控制是否输出摘要。
对于多轮会话场景(比如agent)来说,不显示思考摘要会提升整体的响应速度。

思考模式和工具调用

tool_choice 的选择

当思考与工具同时使用时,tool_choice参数必须为 tool_choice: {"type": "auto"} 或 tool_choice: {"type": "none"}, 不能使用 tool_choice: {"type": "any"} 或 tool_choice: {"type": "tool", "name": "..."}, 因为后两者表示强制使用工具,而思考模式代表着由模型来判断是否使用工具。

tool_use 前的 thinking block要保留

多轮对话场景下,对于assitant turn里伴随着tool_use block出现的thinking block不能丢弃,因为后面模型输出时要了解前面步骤里为何选择某个工具,当时是如何思考的,这些内容都是需要保留的。

在一个assistant turn里不要修改thinking参数。

这里解释一下assistant turn
在Claude的API里,一个assistant turn可以包括多个user_message 和assistant message,中间的user message是tool_result block,意思是模型在回复用户的问题时可能经过多次调用不同的工具,最终结束时的assistant message里是没有tool use block的。而tool result 是要在 user message中返回的,所以中间可能会经历多次API request.
比如下面第二行到第四行,在Claude API语境里整个算一个assistant turn, 但其实要对应两次API request。

User: "What's the weather in Paris?"
Assistant: [thinking] + [tool_use: get_weather]
User: [tool_result: "20°C, sunny"]
Assistant: [text: "The weather in Paris is 20°C and sunny"]

上面的例子展示的还是简单情况,即一次tool use + tool result,其实根据实际场景,可能会有多个tool use和tool result,但它们整体都算一个assistant turn。
而在同一个assistant turn(可能有多次API request)的过程中,不要修改thinking参数。如果修改了,程序虽不报异常,但是可能会让thinking block内容被丢弃,或者启用的thinking模式失效,导致模型可能没经过思考(只没有thinking block),就做了工具调用。

但不同的assistant turn之间是可以使用不同的thinking参数的,比如第一个assistant turn没有启用思考, 但第二个assistant turn启用了思考模式。

User: "What's the weather?"
Assistant: [tool_use] (thinking disabled)
User: [tool_result]
Assistant: [text: "It's sunny"]
User: "What about tomorrow?"
Assistant: [thinking] + [text: "..."] (thinking enabled - new turn)

思考内容的保留

不同模型处理方式不同。 较新的模型都自动保留思考内容,即做为会话历史时,不会被模型忽略。而早期模型以及haiku会自动丢弃会话历史中的的thinking block,这个在最前面的例子中已经演示过。 但当thinking block和tool use一起使用时,只要usermessage返回的是tool_result,前面的thinking内容就会保留,但如果usermessage返回的是tool_result以外的内容,则前面的thinking内容就会被移除。
为保险起见,我们应该始终传回所有的thinking block,由不同模型根据自己的规则去过滤和保留。

下面是一个thinking配合tool_use使用的示例:

第一轮API Call:

TOOLS = [
    {
        "name": "get_weather",
        "description": "Get the current temperature for a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "The city name in English, e.g. 'Beijing' or 'Tokyo'",
                }
            },
            "required": ["city"],
        },
    }
]

turn_1_response = client.messages.create(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
    ]
)

输出结果:

# turn_1_response
Message(id='msg_01CUeDmRTbXVAoRVCXYsaHmy', container=None, content=[ThinkingBlock(signature='EvUDCm0IDhgCKkDkMDKitCVv6kkU5diYXZiq5rvbg/oh3GzK13nHVYSsNMHz/YH7/KIigoJ4DrXMeQ16nyJH+0kcOZm89RhvtS5mMhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nEgx4zIsb5Tt8S9q4Fq0aDLifM/5zQUJ5J5w0ESIwYBVlKu4r80sz9VZXPMdTqXRiCwiJOJmtv2YTdMy4PwCoBBi9V9RxWsqJjq/zWKGAKrUCSDu0KB4X70uBNDh4zaT5ge0ZRAf6574+ZW8S5AlybjPHW8lYVokgfYbO8TF21O3YOhMBKo8Efd0eRc/lUgSSMsvNallEa5quNk8OYrFpgDOlsLxVFS5Lc0Gngzz0ggl8TTCI5XsgxgSCiWw2r59UGAoWmOuLOFW24zFzlB8QHRcwOxre9bFUsK7tz+dv5U7DRqutkY5AHVAE5jwYMbi3fZF0QoNktdyDmjFPiA9FBm3tjayO5bCCnUDhFAhA+RG2rRzG5Xa2zACeRTBGKCJ7zqyYjr+229Jm085m9E04IzuPU+I6OmhQrS1AanKv+WQloRUGMsCFd8MQoY3Dn9o8nC6D4epTGYRozeLlO1nLQb6kBESkMi4nOxEUdJ2NSkqRNIau95tY//Cn2NiyZ1aH5kKwQnyCGAE=', thinking='用户要求我比较北京和东京的气温,并告诉他们哪个更高。我需要使用get_weather函数来查询这两个城市的当前温度。\n\n两个城市是:\n1. 北京 (Beijing)\n2. 东京 (Tokyo)\n\n这两个调用是独立的,没有依赖关系,所以我可以同时进行。', type='thinking'), ToolUseBlock(id='toolu_013gGMKQn1WRFsjUqz6hn3gW', caller=DirectCaller(type='direct'), input={'city': 'Beijing'}, name='get_weather', type='tool_use'), ToolUseBlock(id='toolu_01Tqm9YcXorbxGDRYN9PJGTJ', caller=DirectCaller(type='direct'), input={'city': 'Tokyo'}, name='get_weather', type='tool_use')], model='claude-haiku-4-5-20251001', role='assistant', stop_details=None, stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=0, inference_geo='not_available', input_tokens=646, output_tokens=205, server_tool_use=None, service_tier='standard'))

第二轮API Call:

turn_2_response = client.messages.create(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
        {
            "role": "assistant",
            "content": turn_1_response.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[1].id,
                    "content": "Beijing: 18°C, partly cloudy",
                },
                 {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[2].id,
                    "content": "Tokyo: 22°C, sunny",
                }
            ]
        },
    ]
)

输出结果:

# turn_2_response
Message(id='msg_01C7xb7tEnLZueTAdvKvft3Q', container=None, content=[TextBlock(citations=None, text='根据查询结果,以下是两个城市的气温比较:\n\n| 城市 | 气温 | 天气 |\n|------|------|------|\n| **北京** | 18°C | 多云 |\n| **东京** | 22°C | 晴天 |\n\n**结论:东京的气温更高**\n\n东京(22°C)比北京(18°C)**高4°C**。同时,东京的天气是晴天,北京是多云天气。', type='text')], model='claude-haiku-4-5-20251001', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=0, inference_geo='not_available', input_tokens=932, output_tokens=140, server_tool_use=None, service_tier='standard'))

第三轮API Call:

turn_3_response = client.messages.create(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
        {
            "role": "assistant",
            "content": turn_1_response.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[1].id,
                    "content": "Beijing: 18°C, partly cloudy",
                }
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[2].id,
                    "content": "Tokyo: 22°C, sunny",
                }
            ]
        },
        {
            "role": "assistant",
            "content": turn_2_response.content,
        },
        {
            "role": "user",
            "content": "你觉得哪个地方做为旅游目的地比较合适?"
        }
    ]
)

输出结果:

turn_3_response
Message(id='msg_0175K834ZWzYQg9ydYrqpUvi', container=None, content=[ThinkingBlock(signature='EukFCm0IDhgCKkALbfOBDQvdeot/Z6IuSAEoTAEEOXV4Lp3DMHuSeVn03Ht+B6JvE3ybxKjz6OhsgDcccI4NOAwpZAJ6HZ0lZoBlMhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nEgx8l/FWY1MSdwIf1EoaDF40WJp0phogoZKrySIwF71SbXLuDQSgnIPEnQvB7FQQI9DdZHAGvZ9Isc1C5gq//eg8Glo9KPc08moJNuVlKqkENfGkDP8qri0q2LDRHBgI6AWyf/Vex0Nfz4+zN0g2cdjGl6J8Jm7mpSpMKpjwj7gf6IjbKo/m2H9XSzhc8fR2neUgrNPyrur5BDPOMX+0gTNN93K1khRyPgYqh8oHS0V8jWzvE4a3FrELDdFUxe8usxhekqs8Ze9jxSBSIv0arKbOMDsD6VMpVg8oj/Fm9PxWygPAPAO3u2lhzIbCC2Lxv7voaA5J9gsPsRZqwE8OLTLwmganpq9QXiuWIiJckMoDrgp6CKYTxVPdou0balk2yuXpSC+33Ehc8fBcENnTnQwI6YUOjbOi5c25bSNlzSVfcuI6EoVyT6wylnsqwTIPzywonin3q5QcK57gxRwZdOfonOoIMee+D6JzSzcFCj2VLvlS9Kg//PpCE/5HCoRgFvhGT+wjqXwY75vgiCtZHPCs9TzmyiXGpVsus8USJmXzuy28xYHCWNRVZ/SCM0BwW2ZgIljHpLgQYftX75oUT8jXu+Yh9uQOwk1wXnONUWZvXdNYB9mpx3q8Qi0dZOXeFDcQAZV8Ac2Qt8l081r/8fcv8g2etXr1DqqFwkvjLz2kkilVrK4sXf0dp6H3PKrrHfcUoOGulDT+69bWbv+JOSahw4nSANFlwukBz0Db+kubEFMOgyDSQfaZr2qQULMOIEYMzDeoJC1giKYPwJnY07r03EqcpVUFtmt9/Xk2RBnJ3Idxoe3f2PjCxX8Zt/rAd9roFqIJfQCsZhgB', thinking='用户问我觉得哪个地方作为旅游目的地比较合适。这是一个主观的问题,没有绝对的答案,因为取决于个人偏好。我应该从多个角度分析两个城市的优缺点,给出均衡的建议。\n\n基于刚才的天气数据和一般的旅游知识,我可以这样分析:\n\n北京:18°C,多云\n东京:22°C,晴天\n\n我应该从以下几个方面考虑:\n1. 天气状况\n2. 温度舒适度\n3. 各自的旅游特点\n4. 季节因素\n5. 个人偏好\n\n让我给出一个相对均衡的分析。', type='thinking'), TextBlock(citations=None, text='这是个好问题!基于当前的天气数据,我给你分析一下两个地方作为旅游目的地的优劣:\n\n## **东京(目前更优)**\n**优点:**\n- ✅ 气温更舒适(22°C),天气晴朗,适合户外活动\n- ✅ 四季分明,各季都有特色景观\n- ✅ 现代化与传统相结合,购物和美食体验丰富\n- ✅ 交通便利,公共设施完善\n\n**缺点:**\n- ❌ 消费水平较高\n- ❌ 春夏季节容易遇到雨季\n\n## **北京**\n**优点:**\n- ✅ 历史文化底蕴深厚(长城、故宫、颐和园等)\n- ✅ 消费相对经济实惠\n- ✅ 美食文化丰富\n- ✅ 适合人文历史爱好者\n\n**缺点:**\n- ❌ 当前天气多云,气温相对较低(18°C)\n- ❌ 某些季节容易有雾霾\n\n## **我的建议:**\n- **如果重视舒适的游览体验**:选择 **东京** 👍(晴朗天气,宜人温度)\n- **如果追求历史文化底蕴**:选择 **北京** \n- **如果两个都想体验**:可以考虑都去,但目前东京天气条件更好!\n\n您倾向于哪种类型的旅游呢?', type='text')], model='claude-haiku-4-5-20251001', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=0, inference_geo='not_available', input_tokens=983, output_tokens=685, server_tool_use=None, service_tier='standard'))

下面我们用count_tokens接口比较一下这两轮的tokens,分别计算带think内容与不带think内容的:

turn_2_with_think = client.messages.count_tokens(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
        {
            "role": "assistant",
            "content": turn_1_response.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[1].id,
                    "content": "Beijing: 18°C, partly cloudy",
                },
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[2].id,
                    "content": "Tokyo: 22°C, sunny",
                }
            ]
        },
    ]
)

turn_2_no_think = client.messages.count_tokens(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
        {
            "role": "assistant",
            "content": [
                turn_1_response.content[1],
                turn_1_response.content[2],
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[1].id,
                    "content": "Beijing: 18°C, partly cloudy",
                },
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[2].id,
                    "content": "Tokyo: 22°C, sunny",
                }
            ]
        },
    ]
)

turn_3_with_think = client.messages.count_tokens(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
        {
            "role": "assistant",
            "content": turn_1_response.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[1].id,
                    "content": "Beijing: 18°C, partly cloudy",
                },
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[2].id,
                    "content": "Tokyo: 22°C, sunny",
                }
            ]
        },
        {
            "role": "assistant",
            "content": turn_2_response.content,
        },
        {
            "role": "user",
            "content": "你觉得哪个地方做为旅游目的地比较合适?"
        }
    ]
)

turn_3_no_think = client.messages.count_tokens(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    tools = TOOLS,
    messages = [
        {
            "role": "user",
            "content": "请帮我比较北京和东京的气温, 告诉我哪个更高。需要查询天气。"
        },
        {
            "role": "assistant",
            "content": [
                turn_1_response.content[1],
                turn_1_response.content[2],
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[1].id,
                    "content": "Beijing: 18°C, partly cloudy",
                },
                {
                    "type": "tool_result",
                    "tool_use_id": turn_1_response.content[2].id,
                    "content": "Tokyo: 22°C, sunny",
                }
            ]
        },
        {
            "role": "assistant",
            "content": turn_2_response.content,
        },
        {
            "role": "user",
            "content": "你觉得哪个地方做为旅游目的地比较合适?"
        }
    ]
)

输出结果:

# turn_2_with_think
MessageTokensCount(input_tokens=932)

# turn_2_no_think
MessageTokensCount(input_tokens=787)

# turn_3_with_think
MessageTokensCount(input_tokens=983)

# turn_3_no_think
MessageTokensCount(input_tokens=983)

分析上述结果,我们可以发现,
turn 1 : 用户提问,模型返回了 thinking block 以及 2个 too_use block ,
turn 2 : 时将返回的原始内容做为会话历史的一部分 加上 2个 tool_result block 继续调用API call,模型返回了纯文本内容,即不再有 too_use block。
turn 2 : 在保留所有会话历史内容的基础上,用户继续追问, 模型返回 1个 thinking block + 1个 text block, 也没有 tool use block。

我们再看 token usage 数据:
turn 1 : input_tokens=646, output_tokens=205 646 + 205 = 851
turn 2 : input_tokens=932, output_tokens=140 851 + 2个 too_result = 932, 这个差不多
turn 3 : input_tokens=983, output_tokens=685. 932 + 140 = 1072 > 983, 并且还没有加 turn 3 的用户问题

所以我们可以得出结果, turn 2 时 API处理输入内容时,没有把 turn 1 返回的thinking block移除,因为这里是 thinking block + tool_use block + tool_result block
turn 3 时 user message里不再有 tool_result block, 所以会话历史(context window)中的 thinking block 都被移除,所以导致我们看到的input tokens 小于我们计算的数值。
在使用count_tokens API时,我们得到了同样的结论,即对于turn 3,带和不带 thinking block 计算后的input tokens是相同的。 而对于turn 2,带和不带 thinking block 计算的tokens就不同,因为turn 2中thinking block是和tool_use block一起用的,并且user message只返回tool_result,这时API不会将thinking block内容移除。

交错思考(interleaved thinking)

在一个assistant turn里出现多步工具调用时,每一步调用之前都做思考。这样模型能更充分的基于每次工具调用的结果来判断下一步该做什么,而不是一开始就对后续多个步骤提前做决定。

下面是一个没有交错思考的例子

User: "What's the total revenue if we sold 150 units at $50 each,
       and how does this compare to our average monthly revenue?"

Turn 1: [thinking] "I need to calculate 150 * $50, then check the database..."
        [tool_use: calculator] { "expression": "150 * 50" }
  ↓ tool result: "7500"

Turn 2: [tool_use: database_query] { "query": "SELECT AVG(revenue)..." }
        ↑ no thinking block
  ↓ tool result: "5200"

Turn 3: [text] "The total revenue is $7,500, which is 44% above your
        average monthly revenue of $5,200."
        ↑ no thinking block

做了多步工具调用,但只在最开始思考一次。

下面是一个有交错思考的例子

User: "What's the total revenue if we sold 150 units at $50 each,
       and how does this compare to our average monthly revenue?"

Turn 1: [thinking] "I need to calculate 150 * $50 first..."
        [tool_use: calculator] { "expression": "150 * 50" }
  ↓ tool result: "7500"

Turn 2: [thinking] "Got $7,500. Now I should query the database to compare..."
        [tool_use: database_query] { "query": "SELECT AVG(revenue)..." }
        ↑ thinking after receiving calculator result
  ↓ tool result: "5200"

Turn 3: [thinking] "$7,500 vs $5,200 average - that's a 44% increase..."
        [text] "The total revenue is $7,500, which is 44% above your
        average monthly revenue of $5,200."
        ↑ thinking before final answer

虽然都在一个assistant turn里,但每步工具调用之前都做思考。

交错思考能力在较新模型 sonnet 和 opus 4.6+ 都是具备的,但要求thinking type是 adaptive,而不是enabled。

提示缓存 (prompt caching)

简单提示缓存

下面示例先后两次API call的内容一样,后一次是否能命中缓存呢?

import base64

img_path = '/Users/xxx/image_name.png'
with open(img_path, 'rb') as f:
    base64_str = base64.b64encode(f.read()).decode('utf8')

test_cache_res = client.messages.create(
    model = "claude-sonnet-4-6",
    # thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": base64_str
                    }
                },
                {
                    "type": "text",
                    "text": "这副图中有什么内容?"
                }
            ]
        },
        
    ]
)

输出结果:

# test_cache_res
Message(id='msg_01RApKpXGtKFnwSpWsid7SJa', container=None, content=[TextBlock(citations=None, text='# SN54LVC573A / SN74LVC573A 引脚配置说明\n\n## 文档概述\n这是**德州仪器(Texas Instruments)**的 **SN54LVC573A / SN74LVC573A** 八路透明D锁存器芯片的**引脚配置与功能说明页**(第6章)。\n\n---\n\n## 主要内容\n\n### 📦 封装图示(4种封装形式)\n\n| 封装类型 | 说明 |\n|---------|------|\n| **J, W, DB, DGV, DW, N, NS, PW封装** | 20引脚DIP/SOIC,标准双列直插 |\n| **RGY封装** | 20引脚,顶视图 |\n| **FK封装** | 20引脚,方形封装 |\n| **GQN / ZQN封装** | 5×4球形阵列(BGA),A~E行,1~4列 |\n\n---\n\n### 📋 引脚功能表\n\n| 信号名 | 引脚号(DIP) | 功能描述 |\n|--------|-------------|---------|\n| **OE** | 1 | 输出使能(低有效) |\n| **1D ~ 8D** | 2~9 | 8路数据输入 |\n| **GND** | 10 | 地 |\n| **LE** | 11 | 锁存使能 |\n| **1Q ~ 8Q** | 12~19 | 8路数据输出 |\n| **VCC** | 20 | 电源 |\n\n---\n\n## 芯片功能总结\n> 这是一款 **8位透明D型锁存器**,带有**三态输出**,常用于数据总线缓冲和地址锁存。', type='text')], model='claude-sonnet-4-6', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=0, inference_geo='global', input_tokens=1517, output_tokens=505, server_tool_use=None, service_tier='standard'))

第二次使用相同内容调API call:

test_cache_res2 = client.messages.create(
    model = "claude-sonnet-4-6",
    # thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": base64_str
                    }
                },
                {
                    "type": "text",
                    "text": "这副图中有什么内容?"
                }
            ]
        },
        
    ]
)

输出结果:

# test_cache_res2
Message(id='msg_01MFsei4DeAzmqv6T47EUHtF', container=None, content=[TextBlock(citations=None, text='# SN54LVC573A / SN74LVC573A 芯片引脚配置文档\n\n## 文档概述\n这是德州仪器(Texas Instruments)的**SN54LVC573A / SN74LVC573A** 八路D型锁存器芯片的**第6章:引脚配置与功能**页面。\n\n---\n\n## 主要内容\n\n### 📦 封装类型及引脚图(Top View)\n\n| 封装类型 | 说明 |\n|---------|------|\n| **J / W / FK 封装** (SN54LVC573A) | 20引脚DIP/扁平封装 |\n| **DB / DGV / DW / N / NS / PW / RGY 封装** (SN74LVC573A) | 20引脚各类SMD封装 |\n| **GQN / ZQN 封装** | BGA球栅阵列封装(5×4矩阵) |\n\n---\n\n### 📋 引脚功能表\n\n| 引脚名称 | 描述 |\n|---------|------|\n| **OE** (Pin 1) | 输出使能(低电平有效) |\n| **1D ~ 8D** (Pin 2~9) | 8路数据输入 |\n| **GND** (Pin 10) | 地线 |\n| **LE** (Pin 11) | 锁存使能 |\n| **1Q ~ 8Q** (Pin 12~19) | 8路数据输出 |\n| **VCC** (Pin 20) | 电源引脚 |\n\n---\n\n## 芯片功能简介\n该芯片是一款**8位透明D型锁存器**,具有:\n- 三态输出控制\n- 低压LVC逻辑系列\n- 文档发布于**1993年1月**,最后修订于**2014年5月**', type='text')], model='claude-sonnet-4-6', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=0, inference_geo='global', input_tokens=1517, output_tokens=524, server_tool_use=None, service_tier='standard'))

从usage部分信息中可以看到,完全没有命中缓存。

接下来我们通过显示增加 cache_control 来测试一下效果:
我们先在 image block 上加 cache breakpoint

test_cache_res = client.messages.create(
    model = "claude-sonnet-4-6",
    # thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": base64_str
                    },
                    "cache_control": {"type": "ephemeral"}  # 在image block上增加 cache breakpoint
                },
                {
                    "type": "text",
                    "text": "这副图中有什么内容?"
                }
            ]
        },
        
    ]
)

输出内容:

# test_cache_res
Message(id='msg_01J7LrY4EZvTuP1CbeKYCrE9', container=None, content=[TextBlock(citations=None, text='# SN54LVC573A/SN74LVC573A 引脚配置与功能说明\n\n## 图片主要内容\n\n### 1. 芯片封装图(Pin Configuration)\n\n图中展示了**四种封装形式**的引脚配置:\n\n| 封装类型 | 说明 |\n|---------|------|\n| **J/W/DB/DGV/DW/N/NS/PW Package** | 20引脚DIP/SOIC封装(顶视图) |\n| **RGY Package** | 20引脚QFN封装(顶视图) |\n| **FK Package** | 20引脚PLCC封装(顶视图) |\n| **GQN/ZQN Package** | BGA封装(5×4阵列,顶视图) |\n\n---\n\n### 2. 引脚功能表(Pin Functions)\n\n| 引脚名称 | 功能描述 |\n|---------|---------|\n| **OE** | 输出使能(Enable Pin) |\n| **1D ~ 8D** | 8个数据输入端 |\n| **1Q ~ 8Q** | 8个数据输出端 |\n| **LE** | 锁存使能(Latch Enable) |\n| **GND** | 接地 |\n| **VCC** | 电源 |\n\n---\n\n### 3. 芯片功能概述\n\n该芯片是 **8位透明锁存器**(Octal Transparent Latch),具有:\n- ✅ **三态输出**(由 $\\overline{OE}$ 控制)\n- ✅ **透明锁存功能**(由 LE 控制)\n- ✅ 支持 **LVC** 低压逻辑电平', type='text')], model='claude-sonnet-4-6', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=1503), cache_creation_input_tokens=1503, cache_read_input_tokens=0, inference_geo='global', input_tokens=14, output_tokens=486, server_tool_use=None, service_tier='standard'))

可以看到,这次 input_tokens 很少,但产生了cache_creation_input_tokens且很大,说明图片tokens被写入了缓存,而cache_read_input_tokens=0 说明这个时候命中不到任何缓存。

接下来我们仍然用上面的请求,但是去掉 cache control

test_cache_res2 = client.messages.create(
    model = "claude-sonnet-4-6",
    # thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": base64_str
                    }
                },
                {
                    "type": "text",
                    "text": "这副图中有什么内容?"
                }
            ]
        },
        
    ]
)

输出结果:

# test_cache_res2
Message(id='msg_01FLDJbYyZdctsJSJXhVB8jx', container=None, content=[TextBlock(citations=None, text='# SN54LVC573A / SN74LVC573A 引脚配置文档\n\n## 文档概述\n这是**德州仪器 (Texas Instruments)** 的 **SN54LVC573A/SN74LVC573A** 芯片的**引脚配置与功能说明页**(第6节)。\n\n---\n\n## 主要内容\n\n### 1. 封装类型及引脚图(顶视图)\n\n| 封装类型 | 说明 |\n|---------|------|\n| **J, W, DB, DGV, DW, N, NS, PW 封装** | 20引脚标准DIP/SOIC封装 |\n| **RGY 封装** | 20引脚QFN封装 |\n| **FK 封装** | 20引脚LCCC封装 |\n| **GQN/ZQN 封装** | BGA球栅阵列封装(5×4排列) |\n\n---\n\n### 2. 引脚功能表\n\n| 引脚名称 | 功能描述 |\n|---------|---------|\n| **OE** | 输出使能(Enable Pin) |\n| **1D ~ 8D** | 8个数据输入端 |\n| **1Q ~ 8Q** | 8个数据输出端 |\n| **LE** | 锁存使能(Latch Enable) |\n| **GND** | 接地 |\n| **VCC** | 电源 |\n\n---\n\n### 3. 芯片特点\n- 该芯片是 **8位透明D型锁存器**\n- 具有**三态输出**功能\n- 支持多种封装形式', type='text')], model='claude-sonnet-4-6', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=0, inference_geo='global', input_tokens=1517, output_tokens=463, server_tool_use=None, service_tier='standard'))

这次 input_tokens=1517, cache_creation_input_tokens=0, cache_read_input_tokens=0 说明在不加cache control的请求里是不会主动匹配缓存的,即便内容里有重叠的部分。

下面我们测试一下跟第一次一样的请求,cache control 也加在相同的位置

test_cache_res3 = client.messages.create(
    model = "claude-sonnet-4-6",
    # thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": base64_str
                    },
                    "cache_control": {"type": "ephemeral"} # 在image block上增加 cache breakpoint
                },
                {
                    "type": "text",
                    "text": "这副图中有什么内容?"
                }
            ]
        },
        
    ]
)

输出结果:

# test_cache_res3
Message(id='msg_01RfAeofZpWZnQit9TUVoV7T', container=None, content=[TextBlock(citations=None, text='# 图片内容分析\n\n这是**德州仪器(Texas Instruments)**的集成电路数据手册页面,具体是 **SN54LVC573A / SN74LVC573A** 八路D锁存器(Octal D-Type Latch)的**第6章:引脚配置与功能**。\n\n---\n\n## 主要内容\n\n### 📐 引脚配置图(Pin Configuration)\n展示了**4种封装形式**的引脚排列(俯视图):\n\n| 封装类型 | 说明 |\n|---------|------|\n| **J/W/DB/DGV/DW/N/NS/PW Package** | 20引脚DIP/SOIC封装 |\n| **RGY Package** | 20引脚QFN封装 |\n| **FK Package** | 20引脚LCCC封装 |\n| **GQN/ZQN Package** | BGA封装(5×4阵列) |\n\n---\n\n### 📋 引脚功能表(Pin Functions)\n列出了所有**20个引脚**的功能:\n\n| 引脚名称 | 功能 |\n|---------|------|\n| **OE** | 输出使能(低有效) |\n| **1D~8D** | 8路数据输入 |\n| **1Q~8Q** | 8路数据输出 |\n| **LE** | 锁存使能 |\n| **GND** | 地 |\n| **VCC** | 电源 |\n\n---\n\n## 芯片功能简介\n这是一款**8位透明D型锁存器**,广泛用于数据总线缓冲、地址锁存等数字电路应用。', type='text')], model='claude-sonnet-4-6', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=0), cache_creation_input_tokens=0, cache_read_input_tokens=1503, inference_geo='global', input_tokens=14, output_tokens=476, server_tool_use=None, service_tier='standard'))

这次 cache_creation_input_tokens=0, cache_read_input_tokens=1503, input_tokens=14, 说明 image block 整个命中了缓存,text block没有命中,并且也没加 cache control, 所以text block也不会写入缓存,就会是正常的 input_tokens 。

最后我们测试一下将 cache control 加在 text block 上,也就是最后一个block

test_cache_res4 = client.messages.create(
    model = "claude-sonnet-4-6",
    # thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": base64_str
                    }
                },
                {
                    "type": "text",
                    "text": "这副图中有什么内容?",
                    "cache_control": {"type": "ephemeral"}  # 在最后一个 block上增加 cache breakpoint
                }
            ]
        },
        
    ]
)

输出结果:

# test_cache_res4
Message(id='msg_01WDB9waR2GEcABGUYXDRK7T', container=None, content=[TextBlock(citations=None, text='# SN54LVC573A / SN74LVC573A 引脚配置说明\n\n## 文档概述\n这是**德州仪器(Texas Instruments)**的数据手册第6节,描述了 **SN54LVC573A 和 SN74LVC573A**(8位透明D型锁存器)的引脚配置和功能。\n\n---\n\n## 封装类型及引脚图\n\n### 📦 包含以下封装形式:\n\n| 封装代码 | 适用型号 |\n|---------|---------|\n| **J, W** 封装 | SN54LVC573A |\n| **DB, DGV, DW, N, NS, PW** 封装 | SN74LVC573A |\n| **FK** 封装 | SN54LVC573A |\n| **RGY** 封装 | SN74LVC573A |\n| **GQN / ZQN** 封装 | 球栅阵列(BGA)封装 |\n\n---\n\n## 引脚功能表(Pin Functions)\n\n| 引脚名称 | 功能描述 |\n|---------|---------|\n| **OE**(引脚1) | 输出使能(低电平有效) |\n| **1D ~ 8D**(引脚2~9) | 8路数据输入 |\n| **GND**(引脚10) | 接地 |\n| **LE**(引脚11) | 锁存使能(Latch Enable) |\n| **8Q ~ 1Q**(引脚12~19) | 8路数据输出 |\n| **VCC**(引脚20) | 电源 |\n\n---\n\n## 关键特点\n- 共 **20个引脚**(标准DIP/SOIC封装)\n- 8位**透明锁存器**\n- 具有**三态输出**控制(OE引脚)\n- 文档发布时间:**1993年1月,2014年5月修订**', type='text')], model='claude-sonnet-4-6', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=11), cache_creation_input_tokens=11, cache_read_input_tokens=1503, inference_geo='global', input_tokens=3, output_tokens=548, server_tool_use=None, service_tier='standard'))

可以看到,这回 cache_creation_input_tokens=11, cache_read_input_tokens=1503, input_tokens=3, 这个结果表明,即便缓存中只有 image block 的内容,且请求的 cache breakpoint 打在了 text block 上, 但是 API call 依然可以命中 image block 部分的缓存。 实际上,只要请求中使用了cache control 标记在了block上,即便该block没有匹配到缓存,API会回溯20个block,直到匹配缓存,如果仍未配置,则按正常tokens计价,但打了cache breakpoint的block及之前的所有内容都会写入缓存。
cache_creation_input_tokens=11 说明API将最后那个text block也写入缓存了,而仍然还剩余input_tokens=3,则说明API中有些提示词是Claude SDK或者说Claude API自动帮我们加的,这些内容很少,且不会进入缓存。

思考模式与提示缓存

早期模型以及haiku模型的思考内容在多轮会话的 context windows 里被自动移除, 这种情况下是否还能正常命中缓存呢?

因为haiku模型对缓存要求最低要到4096 tokens,所以这里使用三张图片做为输入。

第一次 API call,cache breakpoint 打在最后一个 block

img_1_path = '/Users/img_1.png'
img_2_path = '/Users/img_2.png'
img_3_path = '/Users/img_3.png'

with open(img_1_path, 'rb') as f:
    img_1_b64 = base64.b64encode(f.read()).decode('utf-8')
with open(img_2_path, 'rb') as f:
    img_2_b64 = base64.b64encode(f.read()).decode('utf-8')
with open(img_3_path, 'rb') as f:
    img_3_b64 = base64.b64encode(f.read()).decode('utf-8')  

think_cache_res1 = client.messages.create(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_1_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_2_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_3_b64
                    }
                },
                {
                    "type": "text",
                    "text": "这些图片中有什么内容?",
                    "cache_control": {"type": "ephemeral"}  # 在最后一个 block上增加 cache breakpoint
                }
            ]
        },
        
    ]
)

输出结果:

# think_cache_res1
Message(id='msg_015QdRxj2hxDTmA8EyYCwfXY', container=None, content=[ThinkingBlock(signature='EtUKCm0IDhgCKkBS07zvf63/EYPx+synb2oAAmLbXCUuT8VJDn2OxB30KAd5qvrrPPZQq0+9LW6misr0fpo7Y7Y1Nm3jgltTtUg3MhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nEgwIuoNVPqxW6kaC4T0aDIQRBncV7OA83qs1fiIwvucwGqXPKoObabgFZu0y8Zye/78a94AkoOvCg53qc1vq79PPOaweXp4usrVxrWndKpUJV37svwGieRi/TnUp2v1liVrzHQMYL1m18mOlwk7+zR9ApoVBlLvJSB3iCdXlRKgpwaXU7OH6dlhyu0ocAThXRbVnh4/Ml3cVG+Z75mK3PWG6hulxBC5SwzvT+T2nVwKQMS5ePUFC5/5prLGISrg8ARP0cbaOwVvJxq//00bHnDmxilDyy5hsTxBHrzKbQUbgCLMdsZNafaJCO2s2TxDmzicVmzG3aumX9mfuh6K4p2ggk0zxVOh1G+bO51ndg6TeAGuN37PkHerxCKCEnXuuED5xUBRklfHdpgI1NELRvbcW+eDhPRFyxUGzXuP6wUswoyayDq1Uj/HzNHglckNz0jYey2fmpiPoeJJqTHqonP9afEQ4B/7JqbVJcicXb9EEkf/ZJn6VxrI6bxTQRi1O/bPtbAKPY0ohevhJW6UIPc+pTCgo0AM+wZf+CNSFjabwrL4mo4gMa7/G5Y2mFTAb5JVmQ2MBxiEQN+u8hXXZXr100NiJ95M2rRNv9nGDfPHBbpRVLywaHdvl6Ox2LvBZr/jn1ZcehQGed/mdwAPksn+Y+Nf1geIjjHGjHXKo4EikqDqtTuDiGvMqRY/p87tXhAhUaW5ShSZ/RNx7Q1+A1SK//uoH5r1BWGobSHp7ZhwFueyVNm7Egf+a9fzY6uiGnhUwvAKUtAj0MUvwb8QSVrqCudC0Er7/1eRdn4hraqUtZdq/Fx6/1aCu0fvGYcwbLu8OgwgsYw4vWIGgRg04B5r5LX3Tn/jaazt4WUhoM+Lu2HT/YnPBpHnkyu/kZdKbr81N9C+gIIr/CM8QPi7vFMpnSScH9RFcqNo0/lRGPEvaCPtEn6jCfxSrLCNnUzacHDDrVMshh+xVpibCw5Pu5H50hVJF5Kbb62XijrzUfSfoEtS26IeVV7Cc8QXcBQESgc+iT+1RbCmQjxyUWbvA89iVEHaaZ5AvEfhioIecoUuY5PNU3R6gExOy7SZ9+GAMU6+5PwabULbQ/0LODeWqJ2c/ly0LL66Y1WKg8fjR2esVSL8O4GItJhL/j+W9cP+R6NZpLDGOHoJ2I5lDMVsKxo/H1YPMq/6ZpyQWyscn11aWd0sBTKNsexOQY/4KJrbslRATlGPvNw6rw4ADAbzNButMUJLkRvBA0k/6H5eHxKPJibZR1cPl6yB7A5s1lflvJuWgU60y6F6XYavETH7h38PZvt7O/YOG7A0X9cpBzx8kMvenKkH9uChERB9GZKCbDG6AtU1ynwJ4lxY5xMtExt5eEoRRXYBLF1lzsc4fpkDpGDSEmy7nm91NZYt2ZMGZQqCXq52Pi5qcnKuIVmJgoAvotiApmzPQt4x02bFsAaGBWt+XLB4g0/oTnnVsfr1wZXOeYMC1Lmj/w4TlEcBH8lADQ0JWDdrbMI4kbGGNyjYcJ8TVFlrjYAwEekgaoX0uGi21BJzSg/vASa1F9Ob8g2Hyhsp0ufMqVqvMTX0/IW7AGCfj72xkvYxCxAUKdMalyibW1aMEdmZ8rDc7BicAmn//GdP1yhA8Ur4Pn0uutjnOZ/gz9L5F34S3nLvu903FuSTYBgmZGAE=', thinking='用户问这些图片中有什么内容。这些是Texas Instruments (TI)的芯片封装和PCB设计文档,具体是PW0020A芯片的TSSOP封装。\n\n让我分别说明三张图片的内容:\n\n**第一张图 - PACKAGE OUTLINE(封装轮廓):**\n- 芯片型号:PW0020A\n- 封装类型:TSSOP (Thin Shrink Small Outline Package)\n- 最大高度:1.2mm\n- 包含详细的技术尺寸和公差\n- 显示了芯片引脚布局(20个引脚)\n- PIN 1标记位置\n- 各种尺寸标注,如6.6mm x 6.2mm等\n- 详细视图A显示了焊脚细节\n\n**第二张图 - EXAMPLE BOARD LAYOUT(示例PCB布局):**\n- 显示了芯片在PCB上的焊垫设计\n- Land pattern(焊垫图案)示例\n- 焊垫间距:20个引脚排列\n- 焊膏面(solder mask)的定义\n- 显示了非焊膏面和焊膏面覆盖的细节\n- 包含焊膏掩膜细节说明\n\n**第三张图 - EXAMPLE STENCIL DESIGN(示例模板设计):**\n- 显示了焊膏模板的设计\n- 基于0.125mm厚的模板\n\n\n- 焊膏印刷孔的尺寸和位置与PCB上的20个焊垫相对应。这些文档共同规范了该芯片的焊接工艺,涵盖尺寸规格、焊垫布局和焊膏分配等关键参数。', type='thinking'), TextBlock(citations=None, text='# 这些图片的内容说明\n\n这是 **Texas Instruments (TI)** 公司的芯片**PW0020A** 在 **TSSOP封装** 的技术文档,共三张图纸:\n\n## 📋 第一张 - 封装轮廓 (PACKAGE OUTLINE)\n- **芯片规格**:TSSOP 20引脚,最大高度1.2mm\n- **详细尺寸**:包括长度(6.6mm)、宽度(6.2mm)、引脚间距等\n- **引脚信息**:显示PIN 1标记位置和引脚排列\n- **公差标注**:遵循ASME Y14.5M标准\n- **焊脚细节**:详细视图A显示了引脚焊脚的形状和尺寸\n\n## 🖨️ 第二张 - PCB布局示例 (EXAMPLE BOARD LAYOUT)\n- **焊垫设计**:芯片在PCB上的焊垫(Land Pattern)尺寸\n- **焊膏掩膜**:显示非焊膏面和焊膏面的覆盖范围\n- **间距标注**:详细标出焊垫之间的距离(5.8mm等)\n- **对称标记**:SYMM(对称)标注\n\n## 🎯 第三张 - 焊膏模板设计 (EXAMPLE STENCIL DESIGN)\n- **模板厚度**:0.125mm\n- **焊膏孔位**:对应PCB上20个焊垫的焊膏印刷孔\n- **印刷参考**:用于通过模板进行焊膏印刷\n\n**用途**:这些文档是 PCB 制造商和电子组装厂的参考指南。', type='text')], model='claude-haiku-4-5-20251001', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=4535), cache_creation_input_tokens=4535, cache_read_input_tokens=0, inference_geo='not_available', input_tokens=10, output_tokens=1019, server_tool_use=None, service_tier='standard'))

第二次 API call,回传所有第一轮模型返回内容,当然包括thinking block,cache breakpoint 仍然打在最后一个 block

think_cache_res2 = client.messages.create(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_1_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_2_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_3_b64
                    }
                },
                {
                    "type": "text",
                    "text": "这些图片中有什么内容?",
                },
            ]
        },
        {
            "role": "assistant",
            "content": think_cache_res1.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "你能按照IPC-7351命名的形式返回结构化的参数信息么?",
                    "cache_control": {"type": "ephemeral"},  # 在最后一个 block上增加 cache breakpoint
                }
            ]
        }
        
    ]
)

输出结果:

# think_cache_res2
Message(id='msg_01E7HvKiaXroDGrPRkjKnVMV', container=None, content=[ThinkingBlock(signature='EroKCm0IDhgCKkCEKQBArUXSUwzQnkK+9G+G+iJtOmYlfmxO3q3UY2lCC9wZxQn+0H7jVKi4OxxHqPUE3jixeYbYVT8hac5vtH28MhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nEgzqfHde9WLgABuTY2YaDILd+ZT+U5xgXPHWaSIwr/qGLdy6jWSQW9t0M1FJ3hSgOUDqI+iVdmO0I9eBGDiIebbuV2+PXj9duzEPKJHRKvoIMfVs9FtzWZpWfGhkeKFOFUxGtCTG+az4wzidbBXA24/PxNlREnKXzZ8eNStmLcUDwpa4BuiGH+spMe5kujItQSkIxkIKzRVU7C3ivc1Gp/TAIS8A1IyZs1ZelCG7/8hYeuhDACbSZ6vFIzphdcRIQsOTOxEoKTwXVs9+5ETTVLfbh7KQuv2n4AZurktEICgkjwcb+DTNozdbCorpvSwTQdglGpor5gf3J8IBxYexyQ5X3Jo7gkyRno5bjIsMRrkAMkIdNxaSnMBJawRJk2fggZH/XGm5YxXZAnkyfYAeJUEcQ/mdGEVEuIiZgBJAyPazFYFJPrzoqzMQHLVOivqzSuiDHGlDeNwg6Fe9UHvPqD3/l4+GWOPklQkYViQ4oyidDR+BKpZBhKQ9C62wdJRkvJwH5e+GKfOR6bn0CqQNAeglcMr0TT0tMXKCJR0g5sXZTDTGSc3k567QBSe+40l+lWehMZdnSUEBsoyCTcB7qGXh2PqGavNJEOhV/HDaFmY9sG6+Gv1xs5DCQJvPavElYZZM91yq0/QJ446bV1jEhjApgjkg14RjauEBS72tIOkSbhnlaOf08xyZUjBIvOZc2W2Nqy7mf1vNpUfSxdmUNroy2qR8lMcnH3nZLxm1XIWseo+jXqEwVZ/3ov9p7J5lF5tYnmeBjVPwHIq0FPk1qsTvIEJCPNn00wWjIgg2JTmE2V2Psz8Zy+lsr4coiPrdBFt3hhhKhnvIjQenE7coxVqOdMF3l7IUtDjd6l0/VCvHaQWyawsKs2E1TSLuGvsv5XZ2L7mP5W2d6Pzos0dmAeh91N+hJcMD8pGIIu9bmd0IkTEAToZlYSQotzsG22IeXr8MCKKyIAQDxo7135Gghf5VJheR1AIa7kpb0LC+U9+ZCNyRguBKNyrHZ7zYt4l3m0xg86s1mmzbbYf6hH+2aifWYKhvRkUKPl6KdGY8aCZaDHUkNCr5Na05jEVsFkiAJzkMgoMaaOGi8CeCcRjuliWbWj8i8X9JV+jby4vxsqh94ciM4X5WVdhQ/GOUUxa/EzTwnsEVP3k2pnNSuoQxYjw1PdCBcE4RZ0Y3S8ntnedBaFloqNJv/Wbl7H5E6NwyK0CpZI+tIaZHvL974H3piwqzgIAtei1tAiKfmIMsn32r2B9yLP+ADRVlM0ATrOvVi4AL7sZoT2GXpu6Ct1WAw8+b3WVxnvizw4N/SaAZ7hXCPNWoEge0ju7xByecbf/HRr3Ah2h0k0QrTEwmGbgpO+T3NLcdmLo45S0J2j61vbJlT+YGIN4UCOCfjiwiwtwt92plQV6KC6y1fLsQ7O5Mv5Th3O9gJ3M2M2EEeBfx3Ad1Hk69yIQmx1/oFNOjMKZjtllALLxG60ROgVfRLyhrYy/oZdR91XrB1Gacx3Ta7hjxje37M4QXRxOCslPkVrteUzMoztp5BmDIa6qHvhIzUtWpGEA71/ttgFNxV7eyR2tH1PtSKJjaJqyAGM5RSPZbDS69v1ZIPex82AEGo8oSEVhjLwjP4uPX1FiYGAE=', thinking='用户要求我按照IPC-7351命名标准的形式返回结构化的参数信息。IPC-7351是关于PCB焊盘设计的标准。\n\nIPC-7351命名形式通常为:\n[Package Type]-[Pin Count]-[Body Code]-[Lead Pitch]\n\n对于TSSOP封装,标准的IPC-7351命名应该是:\nTSSOP20P640X160-20\n\n让我从图纸中提取关键参数并按照IPC-7351的方式结构化呈现:\n\n主要参数:\n- Package Type: TSSOP (Thin Shrink Small Outline Package)\n- Pin Count: 20\n- Body Code/Height: 1.2 mm max\n- Body Length: 6.6 mm (Typ) / 6.2 mm (varies)\n- Body Width: 需要从图中查看\n- Lead Pitch: 0.65 mm (从18X 0.65可以看出)\n- Lead Length/Gull Wing: \n- Pad dimensions\n- Gate plane specifics\n\n从图中可以提取的关键数据:\n- Package: TSSOP\n- Pin count: 20\n- Body length: 6.6 mm (Typ), 6.2 mm (min/max的一个值)\n- Body height: 1.2 mm MAX\n- Lead pitch: 0.65 mm (从 18X 0.65标注)\n- Pin spacing from edge: \n- Pad dimensions from board layout: 1.5mm and 0.45mm spacing mentioned\n\n\n- Gull wing lead形状,0.15mm典型值\n\n根据IPC-7351-7501标准编码,完整的命名应为TSSOP20P640X160-20L。现在需要将所有参数整理成结构化格式。', type='thinking'), TextBlock(citations=None, text='# IPC-7351 结构化参数信息 - PW0020A\n\n## 📐 封装标识\n\n```\nIPC-7351 代码: TSSOP20P640X160-20L\n```\n\n| 参数 | 值 | 单位 |\n|------|-----|------|\n| **Package Type** | TSSOP | - |\n| **Pin Count** | 20 | - |\n| **Reference Designator** | PW0020A | - |\n| **Status** | JEDEC MO-153 | - |\n\n---\n\n## 📏 封装体尺寸\n\n| 参数 | 最小 | 标称 | 最大 | 单位 |\n|------|------|------|------|------|\n| **Body Length (L)** | 6.4 | 6.6 | 6.8 | mm |\n| **Body Width (W)** | - | - | - | mm |\n| **Body Height (H)** | - | - | 1.2 | mm |\n| **Seating Plane to Tip** | - | - | - | mm |\n\n---\n\n## 📍 引脚参数\n\n| 参数 | 值 | 单位 |\n|------|-----|------|\n| **Pin Pitch (e)** | 0.65 | mm |\n| **Number of Pins** | 20 | - |\n| **Pin Length** | - | mm |\n| **Pin Width** | - | mm |\n| **Gull Wing Bend Radius** | ≈ 0.15 | mm |\n\n---\n\n## 🔧 焊盘(Pad)参数\n\n| 参数 | 值 | 单位 |\n|------|-----|------|\n| **Pad Length** | 1.5 | mm |\n| **Pad Width** | 0.45 | mm |\n| **Pad Pitch** | 0.65 | mm |\n| **Exposed Metal** | 0.05 | mm |\n\n---\n\n## 💧 焊膏(Solder Paste)参数\n\n| 参数 | 值 | 单位 |\n|------|-----|------|\n| **Solder Paste Opening** | 0.05 ~ 1.0 × 0.65 | mm |\n| **Stencil Thickness** | 0.125 | mm |\n| **Interflash** | ≤ 0.25 | mm |\n\n---\n\n## ✅ 关键公差\n\n| 项目 | 公差范围 | 标准 |\n|------|---------|------|\n| **陶瓷体翘曲** | ± 0.1 | ASME Y14.5M |\n| **引脚共面性** | 0.19 ~ 0.30 | - |\n| **不包括模具飞边** | ≤ 0.15 | mm |\n\n---\n\n## 📋 引脚定义\n\n```\nPIN 1: 标记位置(左上角,带索引圆点)\nPINS 1-10: 左侧(从上到下)\nPINS 11-20: 右侧(从下到上)\n```', type='text')], model='claude-haiku-4-5-20251001', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=544), cache_creation_input_tokens=544, cache_read_input_tokens=4535, inference_geo='not_available', input_tokens=10, output_tokens=1302, server_tool_use=None, service_tier='standard'))

第三次 API call,回传所有前两轮模型返回内容,包括所有 thinking block,cache breakpoint 仍然打在最后一个 block

think_cache_res3 = client.messages.create(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_1_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_2_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_3_b64
                    }
                },
                {
                    "type": "text",
                    "text": "这些图片中有什么内容?",
                },
            ]
        },
        {
            "role": "assistant",
            "content": think_cache_res1.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "你能按照IPC-7351命名的形式返回结构化的参数信息么?",
                }
            ]
        },
        {
            "role": "assistant",
            "content": think_cache_res2.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "那么这三张图都是指的是同一个封装么?是不是每个封装都需要三种图纸",
                    "cache_control": {"type": "ephemeral"},  # 在最后一个 block上增加 cache breakpoint
                }
            ]
        },
    ]
)

输出结果:

# think_cache_res3
Message(id='msg_01VWCJxo56iHP86hVzMrSofA', container=None, content=[ThinkingBlock(signature='Et0GCm0IDhgCKkCpGHe0T350PGTsww7ENh9XX8rMuxdzgGoNVn7ojn1/P0bAAURzfk+9D+C9cVVhVGTF3wM5W6w3YbPHIUbf3NUtMhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nEgzLCDFXI8Db+APf6pIaDGo+2Lx/m5NL9Ja0MCIwzC0FHHbFV9h1bgCcvqPa89cF4VzfEK7BShpJQ9lO3gKIz/EZB7jVqd2t0OuMK1JnKp0F0qK78sBD9w573/ysV5emNnYC1xyPuhV92eIQMqi2hIMaceE8Rd/lyOeTOZr8TffB2jy+UJk0lpRGLJXcPThU2t3fXJtNiDcm6bTIhFuAuZyNbQHjPO/DOLqj3wOvboSYoofLaIR6vaiEjseeh1M5FtBnax6U7GwyaL5iYU0b6qZvKv/ePKh5Q2LEU+ejdgarrf4hHi8FgYwOo/3tgxVT9pM+l2Q2BbuGpo5bDN0k795LFCAMceOWWZ6ViOMbnvNaNU94MZURQXHBHBoA6NUdumV9y4Kdlb9cpUUGYZbfpGYpCOhbjaK21SWDwLVxtWhQqJ4n7y8TbOoKB1FxHU+uPFbq6EjtjjlImP991K/vS2GF6fAoRsJ9W5KwgouURksuaSF6vuK2HObQXJSCgjAVshxB9rH/FTJpt/UNAcdwT80bHaBkj1RXYTGkhcdUuVsln6CpV7+TYLeDXt20OTPcacyf2fn2EC34T0WdM+c9QoaoEEmdga831Ij/bhSTgQF9zYK5+Qgg5He+BOHp0i3Yf+5lAlJf/csNDF7L37jkFh0rS6ggMwz8Ri1J1yqS1nTJ3b+PdhhQGp+JuHvA+UpCjy2WhxGspJeJ03B95TGQluqYsjzndD5BwZQkDuYJWXFOW71g7ND5OZiWa2ufM7OLtk+IOA6m0x+OBJVqFHZ1P9ZGnKumXuL1SL9zzlyzLhOwtDyEFbK8HPXN4Xdoa4rUS/lHkYG7j0G5oZ4gUGEzhU/chGBg8vfpMcbaIZcjUpvRHLbrRRviibQXJUW/om1ApXbH/CBV7cuXblDYM6SqWgTyRj7xBLrQayPLnEI/CcdqiA4UoJdMczCI7PmbPUkBSRmLkuplyvFpzasF/j+2epPaC8uqblcUWYdkmpTNGAE=', thinking='用户问的是这三张图是否都是同一个封装,以及是否每个封装都需要三种图纸。让我来分析一下:\n\n这三张图确实都是关于 PW0020A TSSOP 封装的,但它们的用途不同:\n\n1. 第一张 (PACKAGE OUTLINE) - 这是芯片本体的机械尺寸图\n2. 第二张 (EXAMPLE BOARD LAYOUT) - 这是PCB上的焊垫设计\n3. 第三张 (EXAMPLE STENCIL DESIGN) - 这是焊膏模板设计\n\n所以它们都是关于同一个TSSOP20P640X160-20L封装,但在整个制造流程中的不同阶段使用。\n\n关于是否每个封装都需要这三种图纸,这取决于使用场景和工艺流程。让我详细解释一下。', type='thinking'), TextBlock(citations=None, text='# 解答:三张图纸的关系\n\n## ✅ 是的,都是同一个封装\n\n这三张图纸都描述的是 **PW0020A TSSOP-20 1.2mm** 这一个芯片封装,但它们在**电子组装流程的不同阶段**使用。\n\n---\n\n## 📊 三张图纸的关系和用途\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                    同一个芯片封装 (PW0020A)              │\n├─────────────────────────────────────────────────────────┤\n│                                                         │\n├─ ① PACKAGE OUTLINE (封装轮廓)                          │\n│  用途: 芯片制造商 → 芯片本体的机械规格                 │\n│  对象: 晶圆厂、封装厂                                  │\n│  内容: 芯片的长宽高、引脚尺寸、公差                   │\n│                                                         │\n├─ ② BOARD LAYOUT (PCB布局)                              │\n│  用途: PCB设计 → 焊垫设计规范                          │\n│  对象: PCB设计工程师、PCB制造厂                         │\n│  内容: 印制板上的焊垫尺寸、间距、焊膏掩膜             │\n│                                                         │\n├─ ③ STENCIL DESIGN (焊膏模板)                           │\n│  用途: 焊接工艺 → 焊膏印刷模板设计                     │\n│  对象: PCBA制造厂、模板制造厂                          │\n│  内容: 焊膏孔位尺寸、模板厚度、间距                    │\n│                                                         │\n└─────────────────────────────────────────────────────────┘\n```\n\n---\n\n## 🔄 完整的电子制造流程\n\n```\n芯片制造 ─→ PCB设计 ─→ PCB制造 ─→ 焊膏印刷 ─→ 贴片 ─→ 焊接\n   ①          ②       ②        ③        ③    ③\n```\n\n| 流程阶段 | 需要的图纸 | 说明 |\n|---------|-----------|------|\n| **芯片采购** | ① | 确认芯片尺寸是否符合设计要求 |\n| **原理图设计** | ① | 确定引脚功能和放置规则 |\n| **PCB布局** | ① ② | 设计焊垫大小和位置 |\n| **PCB制造** | ② | 制造PCB焊垫 |\n| **模板制造** | ③ | 制造焊膏印刷模板 |\n| **焊接生产** | ③ | 用模板印刷焊膏,贴片焊接 |\n\n---\n\n## ❓ 每个封装都需要三种图纸吗?\n\n### 答案:**通常是的,但取决于生产方式**\n\n#### 📌 必需三种图纸的情况:\n- **复杂产品**:量产的消费类电子产品\n- **高精度要求**:医疗、航空、汽车级应用\n- **需要优化**:成本控制、良率提升\n\n#### 📌 可能只需一种图纸的情况:\n- **小批量生产**:样品、试制\n- **采用BGA、焊球**:预定义焊盘,无需BOARD LAYOUT设计\n- **使用自动机器**:直接参考JEDEC标准\n\n#### 📌 业界规范:\n```\nIPC-7351 标准建议:\n✓ 至少提供 ① ② 两种\n✓ 量产推荐提供 ① ② ③ 三种\n✓ ③ 可由 ② 推导出来(但建议提供参考)\n```\n\n---\n\n## 📋 总结\n\n| 图纸 | 是否必需 | 重要程度 | 设计者 |\n|-----|---------|---------|--------|\n| ① Package Outline | ✅ 必需 | ⭐⭐⭐⭐⭐ | 芯片制造商 |\n| ② Board Layout | ✅ 必需 | ⭐⭐⭐⭐⭐ | PCB设计者 |\n| ③ Stencil Design | ⚠️ 推荐 | ⭐⭐⭐⭐ | PCBA工程师 |', type='text')], model='claude-haiku-4-5-20251001', role='assistant', stop_details=None, stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=866), cache_creation_input_tokens=866, cache_read_input_tokens=5079, inference_geo='not_available', input_tokens=10, output_tokens=1580, server_tool_use=None, service_tier='standard'))

对第三轮分别统计带thinking block与不带thinking block的tokens:

res3_tokens_with_think = client.messages.count_tokens(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_1_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_2_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_3_b64
                    }
                },
                {
                    "type": "text",
                    "text": "这些图片中有什么内容?",
                },
            ]
        },
        {
            "role": "assistant",
            "content": think_cache_res1.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "你能按照IPC-7351命名的形式返回结构化的参数信息么?",
                }
            ]
        },
        {
            "role": "assistant",
            "content": think_cache_res2.content,
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "那么这三张图都是指的是同一个封装么?是不是每个封装都需要三种图纸",
                    "cache_control": {"type": "ephemeral"},  # 在最后一个 block上增加 cache breakpoint
                }
            ]
        },
    ]
)

res3_tokens_no_think = client.messages.count_tokens(
    model = "claude-haiku-4-5",
    thinking = {"type": "enabled", "budget_tokens": 16000},
    # max_tokens = 20480,
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_1_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_2_b64
                    }
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": img_3_b64
                    }
                },
                {
                    "type": "text",
                    "text": "这些图片中有什么内容?",
                },
            ]
        },
        {
            "role": "assistant",
            "content": [
                {
                   "type": "text",
                   "text": think_cache_res1.content[1].text,
                }
            ]
            
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "你能按照IPC-7351命名的形式返回结构化的参数信息么?",
                }
            ]
        },
        {
            "role": "assistant",
            "content": [
                {
                   "type": "text",
                   "text": think_cache_res2.content[1].text,
                }
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "那么这三张图都是指的是同一个封装么?是不是每个封装都需要三种图纸",
                    "cache_control": {"type": "ephemeral"},  # 在最后一个 block上增加 cache breakpoint
                }
            ]
        },
    ]
)

输出结果:

# res3_tokens_with_think
MessageTokensCount(input_tokens=5955)

# res3_tokens_no_think
MessageTokensCount(input_tokens=5955)

# 回看第三轮的usage
# think_cache_res3.usage
Usage(cache_creation=CacheCreation(ephemeral_1h_input_tokens=0, ephemeral_5m_input_tokens=866), cache_creation_input_tokens=866, cache_read_input_tokens=5079, inference_geo='not_available', input_tokens=10, output_tokens=1580, server_tool_use=None, service_tier='standard')

我们发现,5079 + 10 + 866 = 5955, 说明第三轮里从缓存读的内容是不含 thinking 内容的。
所以,对于早期模型以及haiku模型,thinking 内容是不进缓存的。

下面我们再试下带 tool_use 的情况。

结论:

posted @ 2026-05-24 18:43  RolandHe  阅读(14)  评论(0)    收藏  举报