Вы переключили вызовы Claude API на более новую модель — и получили ошибку ниже. Или текст ответа приходит пустым. Причина обычно одна и та же: первый блок содержимого (content[0]) теперь не текст, а блок рассуждений (thinking).

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

Если читать response.content[0].text в Python SDK, возникает эта ошибка. В JavaScript тот же код возвращает undefined, поэтому вместо падения по приложению идёт пустая строка. Коварство в том, что на простых запросах первый блок по-прежнему текстовый, и тесты с лёгкими запросами проходят.

Проблема в трёх строках

Что происходит

Первый блок больше не текст

В основном на тяжёлых запросах первым в ответе идёт блок рассуждений (иногда и на лёгких). content[0].text ломается.

Причина

Модель думает без просьбы

Модели Claude 5 и новее рассуждают, когда нужно, даже если thinking не задан. Модели Claude 4 без настройки не рассуждают.

Решение

Искать блоки по типу

Не полагайтесь на первый блок. Собирайте блоки с type, равным "text", и проверяйте stop_reason.

1. Когда это происходит

Типичный случай — миграция, при которой меняется только имя модели. Например, вы переходите с Claude Sonnet 4.6 на Claude Sonnet 5.5, переписав лишь значение model. Считая, что форма ответа осталась прежней, остальной код вы не трогаете. И тогда происходит следующее (третий случай бывает даже с правильно написанным кодом).

Как читаетеЧто происходит
content[0].text в Python SDKПадает с AttributeError: 'ThinkingBlock' object has no attribute 'text'
content[0].text в JavaScriptОшибки нет; получается undefined, и пустой текст попадает на экран или в хранилище
Поиск текстового блокаЕсли модель долго рассуждает и упирается в max_tokens, текстового блока может не быть вовсе

Третий случай бывает даже с правильно написанным кодом. Токены рассуждений входят в 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")

Источник: таблица по моделям и описание display в документации Anthropic «Thinking» (проверено 1 ноября 2026). Opus 4.5, Sonnet 4.5 и Haiku 4.5 не принимают {type: "adaptive"}; чтобы они рассуждали, используйте {type: "enabled"} с budget_tokens.

Рассуждать или нет, модель решает для каждого запроса сама. В документации «Steering thinking» объясняется, что на простые фактические вопросы модель может ответить без блока рассуждений. Там же предупреждают: не стройте логику приложения на предположении, что каждый ответ начинается с блока рассуждений. Иначе говоря, первый блок бывает то текстом, то рассуждениями. Код, рассчитанный на что-то одно, где-нибудь сломается.

Содержимое ответа идёт в порядке, показанном ниже. Содержимое рассуждений по умолчанию пустое, но сам блок всё равно возвращается.

3. Что показала проверка

Я проверил это в своей среде с Python SDK (anthropic 1.14.0) 1 ноября 2026. Четырём моделям я отправил по одному разу лёгкий и тяжёлый запрос — без thinking и с {type: "adaptive"}. max_tokens был равен 8000. Лёгкий запрос — простой фактический вопрос: назвать одним словом столицу Японии (спрашивали по-японски); тяжёлый — задача найти все четырёхзначные целые числа, удовлетворяющие пяти условиям.

МодельНастройка thinkingcontent (лёгкий запрос)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 без этой настройки первым блоком оставался текст. Это подтверждает сказанное в документации: смена одного лишь имени модели меняет форму ответа.

Второе: на лёгком запросе первым блоком почти во всех сочетаниях был текст. Если в тестах только лёгкие запросы, эту проблему не найти. При этом Opus 5.5 с adaptive рассуждал даже на лёгком запросе. На Claude 5 и новее отсутствие thinking и настройка adaptive — одна и та же конфигурация, так что эта разница, скорее всего, разброс между запусками. Сказать «на лёгких запросах рассуждений не бывает» нельзя.

Третье: при достижении лимита текст не возвращался вовсе. На тяжёлом запросе Haiku 5.5 потратил все 8000 токенов на рассуждения — и без thinking, и с adaptive — и не вернул ни одного текстового блока. Sonnet 4.6 с adaptive тоже упёрся в лимит. На Claude 5 и новее содержимое рассуждений по умолчанию тоже пустое, поэтому пользователь не видит ничего, а за выходные токены вы всё равно платите.

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 (число токенов, потраченных на рассуждения) в ответе.

  • Поднять лимит: если на оборванных запросах рассуждения улучшают качество ответа
  • Понизить effort: если модель просто перебарщивает с рассуждениями, а качество ответа не меняется (effort задаётся через output_config.effort)

Продолжая диалог, возвращайте и блоки рассуждений

Добавляя ответ в историю диалога, не вырезайте один текст — передавайте content как есть. Особенно при использовании инструментов: блок рассуждений содержит обоснование вызова инструмента. Документация просит возвращать полученные блоки без изменений, а изменённые блоки отклоняются с ошибкой 400. Надёжнее всего держать текст для показа отдельно от content, который вы передаёте обратно в историю.

5. Можно ли просто отключить рассуждения?

Хочется сказать: «Раз рассуждения мешают, отключим их». Но получится ли, зависит от модели. Вот как обрабатывается thinking: {type: "disabled"}.

Модель{type: "disabled"}
Opus 5.5, Fable 5.1, Fable 5Ошибка 400 (отключить нельзя)
Sonnet 5.5Ошибка 400 (вместо этого {type: "between_tools"} пропускает начальные рассуждения при effort high или ниже)
Haiku 5.5, Opus 5Можно отключить (только при effort high или ниже; при xhigh и max — ошибка 400)
Sonnet 5Можно отключить

Источник: документация Anthropic «Thinking» (проверено 1 ноября 2026). Принимает ли модель эту настройку, можно также узнать по capabilities.thinking.types.disabled.supported в Models API. Effort по умолчанию — medium у Opus 5.5 и Haiku 5.5 и 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». Effort в API описан в документации Anthropic «Effort», а выбор effort в Claude Code — в статье «Настройка effort в Claude Code».

Итоги

  • AttributeError: 'ThinkingBlock' object has no attribute 'text' возникает, когда первый блок ответа — блок рассуждений, а вы пытаетесь прочитать текст через content[0].text
  • Модели Claude 5 и новее рассуждают, когда нужно, даже без thinking, поэтому простая замена имени модели с Claude 4 меняет форму ответа
  • На лёгких запросах первый блок обычно остаётся текстом, поэтому лёгкие тесты проходят
  • Решение — собирать блоки с type, равным "text", и проверять, не равен ли stop_reason значению "max_tokens"
  • У некоторых моделей рассуждения отключить нельзя, поэтому настоящее решение — не отключать их, а писать код, который читает ответ в любом порядке блоков

Частые вопросы

В. Я не задавал thinking. Откуда блок рассуждений?

О. На моделях Claude 5 и новее adaptive thinking включено и без thinking, и модель рассуждает на запросах, где сочтёт это нужным. Содержимое рассуждений по умолчанию пустое, но сам блок входит в ответ. Модели Claude 4, такие как Claude Opus 4.8 и Sonnet 4.6, без настройки не рассуждают.

В. Почему пришёл ответ совсем без текстового блока?

О. Токены рассуждений входят в max_tokens, и лимит был достигнут раньше, чем модель написала текст. stop_reason при этом должен быть "max_tokens". Поднимите лимит или понизьте effort, чтобы сократить рассуждения.

В. А если я хочу прочитать содержимое рассуждений?

О. Задайте thinking: {type: "adaptive", display: "summarized"} — придёт краткое изложение рассуждений. По умолчанию на Claude 5 и новее стоит "omitted" (содержимое пустое, есть только подпись). В любом случае оплачиваются все токены рассуждений.

История обновлений

  • 1 ноября 2026Первая версия. Написана после изучения документации Anthropic «Thinking» и «Steering thinking» и проверки порядка блоков в ответах четырёх моделей в моей среде.

Источники