Вы переключили вызовы 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» объясняется, что на простые фактические вопросы модель может ответить без блока рассуждений. Там же предупреждают: не стройте логику приложения на предположении, что каждый ответ начинается с блока рассуждений. Иначе говоря, первый блок бывает то текстом, то рассуждениями. Код, рассчитанный на что-то одно, где-нибудь сломается.
Содержимое ответа идёт в порядке, показанном ниже. Содержимое рассуждений по умолчанию пустое, но сам блок всё равно возвращается.
Лёгкий запрос (например, столица Японии?)
[0]textТокиоТяжёлый запрос (например, найти все числа, удовлетворяющие условиям)
[0]thinkingПусто, только подпись[1]textОтветТяжёлый запрос, упёршийся в max_tokens
[0]thinkingПусто, только подписьНет textstop_reason: "max_tokens"3. Что показала проверка
Я проверил это в своей среде с Python SDK (anthropic 1.14.0) 1 ноября 2026. Четырём моделям я отправил по одному разу лёгкий и тяжёлый запрос — без thinking и с {type: "adaptive"}. max_tokens был равен 8000. Лёгкий запрос — простой фактический вопрос: назвать одним словом столицу Японии (спрашивали по-японски); тяжёлый — задача найти все четырёхзначные целые числа, удовлетворяющие пяти условиям.
| Модель | Настройка thinking | content (лёгкий запрос) | content (тяжёлый запрос) |
|---|---|---|---|
| Sonnet 4.6 | Не задана | text | text |
| Sonnet 4.6 | adaptive | text | только thinking (упёрся в лимит) |
| Haiku 5.5 | Не задана | text | только thinking (упёрся в лимит) |
| Haiku 5.5 | adaptive | text | только thinking (упёрся в лимит) |
| Sonnet 5.5 | Не задана | text | thinking → text |
| Sonnet 5.5 | adaptive | text | thinking → text |
| Opus 5.5 | Не задана | text | thinking → text |
| Opus 5.5 | adaptive | thinking → text | thinking → 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. Как не пропустить это в тестах
Эта проблема проскальзывает мимо тестов с лёгкими запросами. При смене модели проверка в таком порядке снижает риск её пропустить.
- Найдите
content[0]в коде: перечислите все места, где ответ читается по позиции —content[0],content[-1],.content[0].text, — не только в рабочем коде, но и в тестах и логировании - Добавьте в тесты тяжёлые запросы: включите в тестовые входные данные самые длинные запросы из продакшена и самые трудные задачи (на одних лёгких запросах первый блок остаётся текстом, и поломка не видна)
- Подавайте поддельные ответы, начинающиеся с блока рассуждений: в модульных тестах без вызова API подготовьте ответ
[thinking, text]и ответ из одногоthinkingсоstop_reason"max_tokens", чтобы тестировать независимо от того, рассуждает ли модель - Переходите постепенно: не переключайте всё сразу; сначала переведите на новую модель часть нагрузки, следите за числом пустых текстов и исключений, затем расширяйте
⚠️ Если модель меняют сразу несколько сервисов или команд, добавьте в запрос на миграцию одну строку: «Первый блок ответа не всегда текст. Ищите его по типу». Изменение выглядит как простое переименование, и если ждать, пока каждая команда заметит это сама, код, прошедший тесты на лёгких запросах, сломается в продакшене.
Это не единственное, что стоит проверить при смене модели. Что менялось при переходе с прошлых поколений, см. в статьях «Руководство по миграции на 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» и проверки порядка блоков в ответах четырёх моделей в моей среде.
Источники
- Anthropic: Thinking, Steering thinking