把 Claude API 调用切换到较新的模型后,出现了下面的错误;或者响应的文本变成了空的。原因通常是同一个:第一个内容块(content[0])不再是文本,而是思考块(thinking)。

AttributeError: 'ThinkingBlock' object has no attribute 'text'

在 Python SDK 中读取 response.content[0].text 就会出现这个错误。在 JavaScript 中,同样的代码返回 undefined,所以不会报错,而是让空字符串流进应用。麻烦之处在于:输入简单时第一个块仍是文本,所以用轻量输入的测试会通过。

三句话说清问题

现象

第一个块不再是文本

主要在重输入时,响应的第一个块是思考块(轻量输入有时也会)。content[0].text 因此出错。

原因

不指定也会思考

Claude 5 及以后的模型即使不设置 thinking,也会在需要时思考。Claude 4 系列不设置就不思考。

解决

按类型查找块

不要假定第一个块。收集 type 为 "text" 的块,并检查 stop_reason。

1. 什么时候会出现

典型情况是只改模型名的迁移。例如,只改写 model 的值,从 Claude Sonnet 4.6 换到 Claude Sonnet 5.5。以为响应的形式不变,其余代码保持原样。这时会出现以下情况(第三种即使代码写得正确也会发生)。

读取方式结果
Python SDK 中的 content[0].text以 AttributeError: 'ThinkingBlock' object has no attribute 'text' 失败
JavaScript 中的 content[0].text不报错,变成 undefined,空文本被显示在界面上或被保存
查找文本块如果模型思考很多并达到 max_tokens,可能根本没有文本块

第三种情况即使代码写得正确也会发生。思考的 token 计入 max_tokens,所以上限较小时,模型还没写出任何文本,响应就被截断了。

2. 原因:Claude 5 及以后不指定也会思考

Anthropic 的“Thinking”文档按模型列出了请求中不含 thinking 时的行为。分界线是 Claude 5。

模型不写 thinking 时思考的内容
Claude 5 及以后
(Opus 5.5、Sonnet 5.5、Haiku 5.5、Fable 5.1 等)
会思考(adaptive thinking)默认为空(display: "omitted")
Opus 4.8、Opus 4.7不思考只有设置 thinking: {type: "adaptive"} 时才思考;内容默认为空("omitted")
Opus 4.6、Sonnet 4.6不思考只有设置 thinking: {type: "adaptive"} 时才思考;默认返回摘要("summarized")

出处:Anthropic“Thinking”中的按模型表格和 display 的说明(2026 年 11 月 1 日确认)。Opus 4.5、Sonnet 4.5、Haiku 4.5 不接受 {type: "adaptive"};要让它们思考,请使用 {type: "enabled"} 加 budget_tokens。

是否思考由模型针对每个输入自行决定。“Steering thinking”文档说明,对于简单的事实性问题,模型可能不经思考块直接回答,并提醒不要把应用逻辑建立在“每个响应都以思考块开头”的假设上。也就是说,第一个块有时是文本,有时是思考。假定其中任何一种的代码,总会在某处出错。

响应内容的顺序如下。思考的内容默认为空,但块本身仍会返回。

3. 实测结果

2026 年 11 月 1 日,我在自己的环境中用 Python SDK(anthropic 1.14.0)做了测试。向 4 个模型分别发送一次轻量输入和重输入,分为不设置 thinking 和设置 {type: "adaptive"} 两种情况。max_tokens 设为 8000。轻量输入是一个简单的事实性问题,要求用一个词回答日本的首都(用日语提问);重输入是一道题,要求找出满足 5 个条件的所有四位整数。

模型thinking 设置content(轻量输入)content(重输入)
Sonnet 4.6未设置texttext
Sonnet 4.6adaptivetext只有 thinking(达到上限)
Haiku 5.5未设置text只有 thinking(达到上限)
Haiku 5.5adaptivetext只有 thinking(达到上限)
Sonnet 5.5未设置textthinking → text
Sonnet 5.5adaptivetextthinking → text
Opus 5.5未设置textthinking → text
Opus 5.5adaptivethinking → textthinking → text

红色表示 content[0].text 引发 AttributeError: 'ThinkingBlock' object has no attribute 'text' 的组合。每项都是一次运行的结果;即使输入相同,模型是否思考也可能因运行而异。

三点结论。

第一,Claude 5 及以后的模型即使不设置 thinking,在重输入上也进行了思考。Sonnet 4.6 不设置时,第一个块仍是文本。这印证了文档的说法:只改模型名,响应的形式就会改变。

第二,轻量输入下,几乎所有组合的第一个块都是文本。如果测试只用轻量输入,就发现不了这个问题。另一方面,设置了 adaptive 的 Opus 5.5 在轻量输入上也进行了思考。在 Claude 5 及以后,不写 thinking 和设置 adaptive 是同一种配置,所以这个差异很可能只是运行之间的波动。不能说“轻量输入就不会思考”。

第三,达到上限时,完全没有返回文本。在重输入上,Haiku 5.5 无论是不设置 thinking 还是设置 adaptive,都把 8000 个 token 全部用在思考上,没有返回文本块。设置了 adaptive 的 Sonnet 4.6 也达到了上限。在 Claude 5 及以后,思考的内容默认也是空的,所以用户什么也看不到,而输出 token 仍然要付费。

4. 修复方法

修复方法是不再假定哪个块在前,而是收集文本类型的块(type 为 "text")。同时检查 stop_reason,避免把在上限处被截断的响应当作正常回答。

Python

import anthropic

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=16000,
    messages=[{"role": "user", "content": prompt}],
)

# 不假定第一个块:只收集文本块
text = "".join(b.text for b in response.content if b.type == "text")

if response.stop_reason == "max_tokens":
    # 在上限处被截断。text 可能为空
    raise RuntimeError("达到 max_tokens。请提高上限或减少思考")
if response.stop_reason == "end_turn" and not text:
    # 使用工具时 stop_reason 为 "tool_use",text 为空是正常的
    raise RuntimeError("没有文本块")

TypeScript

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const response = await client.messages.create({
  model: "claude-sonnet-5-5",
  max_tokens: 16000,
  messages: [{ role: "user", content: prompt }],
});

// 先按类型收窄再读取 text
const text = response.content
  .filter((b): b is Anthropic.TextBlock => b.type === "text")
  .map((b) => b.text)
  .join("");

if (response.stop_reason === "max_tokens") {
  throw new Error("达到 max_tokens。请提高上限或减少思考");
}
// 使用工具时 stop_reason 为 "tool_use",text 为空是正常的
if (response.stop_reason === "end_turn" && text === "") {
  throw new Error("没有文本块");
}

在 TypeScript SDK 中,content 的元素是文本、思考、工具调用等类型的联合类型。直接写 content[0].text 通常无法通过类型检查。但如果用 as 强制指定类型,错误就会漏过去。

设置 max_tokens 时要把思考算进去

“Steering thinking”文档说明,max_tokens 是思考和回答文本合计的上限,并指出按“模型不会思考”的前提定下的上限,对困难的输入往往不够。收到 stop_reason: "max_tokens" 时,有两种办法。

上面示例中的 16000 只是一个参考。在我的测试中,即使是 8000,有时也会全部用在思考上。用于生产环境时,请实际运行重输入,参考响应中的 usage.output_tokens_details.thinking_tokens(用于思考的 token 数)来确定数值。

  • 提高上限:当思考能提高被截断输入的回答质量时
  • 降低 effort:当模型只是想得太多、回答质量并无变化时(effort 用 output_config.effort 设置)

继续对话时,思考块也要一并传回

把响应追加到对话历史时,不要只提取文本,而要原样传回 content。尤其在使用工具的流程中,思考块保存着调用工具的推理过程。文档要求把收到的块不加修改地返回,修改过的块会被 400 错误拒绝。最稳妥的做法是把用于显示的文本和传回历史的 content 分开保存。

5. 能不能直接关掉思考?

很容易想到“思考碍事的话,关掉就好”,但能否关掉取决于模型。thinking: {type: "disabled"} 的处理如下。

模型{type: "disabled"}
Opus 5.5、Fable 5.1、Fable 5400 错误(无法关闭)
Sonnet 5.5400 错误(作为替代,effort 为 high 或更低时,{type: "between_tools"} 会跳过开头的思考)
Haiku 5.5、Opus 5可以关闭(仅限 effort 为 high 或更低;xhigh 和 max 时为 400 错误)
Sonnet 5可以关闭

出处:Anthropic“Thinking”(2026 年 11 月 1 日确认)。也可以通过 Models API 的 capabilities.thinking.types.disabled.supported 确认模型是否接受。effort 的默认值在 Opus 5.5 和 Haiku 5.5 上为 medium,在其他大多数模型上为 high,所以默认值满足“high 或更低”的条件。

即使在可以关闭的模型上,关闭也可能降低困难输入的回答质量。而且每次换模型,同样的问题都会再次出现。真正的解决办法是写出无论块的顺序如何都能读取响应的代码。想减少思考的话,先降低 effort。根据 Anthropic 的文档,在系统提示词中要求 Sonnet 5.5 少思考,并不能可靠地减少思考;Haiku 5.5 即使被要求直接回答也仍会思考(Anthropic 的测试)。两者都请用 effort 来调整。

6. 如何在测试中不漏掉

这个问题会漏过轻量输入的测试。换模型时,按以下顺序检查更不容易漏掉。

  1. 在代码中查找 content[0]:列出所有按位置读取的地方,如 content[0]、content[-1]、.content[0].text,不仅是生产代码,也包括测试和日志
  2. 在测试中加入重输入:把生产环境中最长的输入和最难的任务加入测试输入(只用轻量输入时,第一个块仍是文本,问题会被掩盖)
  3. 输入以思考块开头的伪造响应:在不调用 API 的单元测试中,准备 [thinking, text] 的响应,以及只有 thinking、stop_reason 为 "max_tokens" 的响应,这样无论模型是否思考都能测试
  4. 分阶段切换:不要一次全部切换;先把部分业务迁到新模型,观察空文本和异常的数量,再逐步扩大

⚠️ 多个服务或团队一起换模型时,在迁移通知中加一句:“响应的第一个块不一定是文本,请按类型查找。”这个改动看起来只是改个名字,如果等各团队自己发现,通过了轻量输入测试的代码就会在生产环境中出错。

换模型时要检查的不止这一点。从前几代迁移时的变化,请参阅“Claude Opus 4.7 迁移指南”和“Claude Fable 5.1 的3个破坏性变更与迁移指南”。API 中的 effort 见 Anthropic 的“Effort”文档,Claude Code 中 effort 的选择见“Claude Code 的 effort(投入度)设置”。

总结

  • AttributeError: 'ThinkingBlock' object has no attribute 'text' 在响应的第一个块是思考块、而你用 content[0].text 读取文本时出现
  • Claude 5 及以后的模型即使不写 thinking 也会在需要时思考,所以只把模型名从 Claude 4 系列换掉,响应的形式就会改变
  • 轻量输入下第一个块通常仍是文本,所以轻量测试会通过
  • 修复方法是收集 type 为 "text" 的块,并检查 stop_reason 是否为 "max_tokens"
  • 有些模型无法关闭思考,所以真正的解决办法不是关掉它,而是写出无论块的顺序如何都能读取响应的代码

常见问题

Q. 我没有设置 thinking,为什么会出现思考块?

A. 在 Claude 5 及以后的模型上,即使不写 thinking,adaptive thinking 也是开启的,模型会在它判断需要的输入上思考。思考的内容默认为空,但块本身包含在响应中。Claude Opus 4.8、Sonnet 4.6 等 Claude 4 系列不设置就不思考。

Q. 为什么会返回完全没有文本块的响应?

A. 思考的 token 计入 max_tokens,在模型写出文本之前就达到了上限。此时 stop_reason 应为 "max_tokens"。请提高上限,或降低 effort 以减少思考。

Q. 想读取思考的内容怎么办?

A. 设置 thinking: {type: "adaptive", display: "summarized"},就会返回思考的摘要。Claude 5 及以后的默认值是 "omitted"(内容为空,只含签名)。无论哪种,思考的 token 都会全额计费。

更新记录

  • 2026 年 11 月 1 日初版。查阅 Anthropic 的“Thinking”和“Steering thinking”文档,并在我的环境中实测了 4 个模型的响应顺序后撰写。

出处