Cambiaste el modelo de tus llamadas a la API de Claude por uno más reciente y ahora aparece el error de abajo. O el texto de la respuesta llega vacío. La causa suele ser la misma: el primer bloque del contenido (content[0]) ya no es texto, sino un bloque de razonamiento (thinking).
AttributeError: 'ThinkingBlock' object has no attribute 'text'
Si lees response.content[0].text con el SDK de Python, obtienes este error. En JavaScript, el mismo código devuelve undefined, así que en lugar de fallar, una cadena vacía recorre tu aplicación. Lo engañoso: con entradas sencillas el primer bloque sigue siendo texto, así que las pruebas con entradas ligeras pasan.
El problema en tres líneas
Qué pasa
El primer bloque ya no es texto
Sobre todo con entradas pesadas, la respuesta empieza con un bloque de razonamiento (a veces también con entradas ligeras). content[0].text se rompe.
Causa
Razona sin que se lo pidas
Los modelos Claude 5 y posteriores razonan cuando lo necesitan aunque no indiques thinking. Los modelos Claude 4 no razonan si no lo indicas.
Solución
Busca los bloques por tipo
No des por hecho el primer bloque. Recoge los bloques cuyo type es "text" y comprueba también stop_reason.
1. Cuándo ocurre
El caso típico es una migración en la que solo cambias el nombre del modelo. Por ejemplo, pasas de Claude Sonnet 4.6 a Claude Sonnet 5.5 reescribiendo solo el valor de model. Suponiendo que la forma de la respuesta es la misma, no tocas el resto del código. Entonces ocurre lo siguiente (el tercer caso ocurre incluso con código bien escrito).
| Cómo lo lees | Qué pasa |
|---|---|
content[0].text en el SDK de Python | Falla con AttributeError: 'ThinkingBlock' object has no attribute 'text' |
content[0].text en JavaScript | Sin error; se convierte en undefined y acaba un texto vacío en pantalla o en el almacenamiento |
| Buscando el bloque de texto | Si el modelo razona mucho y llega a max_tokens, puede que no haya ningún bloque de texto |
El tercer caso ocurre incluso con código bien escrito. Los tokens de razonamiento cuentan para max_tokens, así que si el límite es pequeño, la respuesta se corta antes de que el modelo escriba ningún texto.
2. Causa: Claude 5 y posteriores razonan sin que se lo pidas
La documentación «Thinking» de Anthropic indica, modelo por modelo, qué ocurre cuando la solicitud no incluye thinking. La línea divisoria es Claude 5.
| Modelo | Sin thinking | Contenido del razonamiento |
|---|---|---|
| Claude 5 y posteriores (Opus 5.5, Sonnet 5.5, Haiku 5.5, Fable 5.1, etc.) | Razona (razonamiento adaptativo) | Vacío por defecto (display: "omitted") |
| Opus 4.8, Opus 4.7 | No razona | Solo razona si indicas thinking: {type: "adaptive"}; el contenido está vacío por defecto ("omitted") |
| Opus 4.6, Sonnet 4.6 | No razona | Solo razona si indicas thinking: {type: "adaptive"}; devuelve un resumen por defecto ("summarized") |
Fuente: la tabla por modelo y la descripción de display en «Thinking» de Anthropic (consultado el 1 de noviembre de 2026). Opus 4.5, Sonnet 4.5 y Haiku 4.5 no aceptan {type: "adaptive"}; para que razonen, usa {type: "enabled"} con budget_tokens.
El modelo decide en cada entrada si razona. La documentación «Steering thinking» explica que, ante preguntas factuales sencillas, el modelo puede responder sin bloque de razonamiento. Y advierte que no construyas la lógica de tu aplicación suponiendo que toda respuesta empieza con un bloque de razonamiento. Es decir, el primer bloque a veces es texto y a veces razonamiento. El código que dé por hecha cualquiera de las dos cosas se romperá en algún momento.
El contenido de la respuesta se ordena como se muestra abajo. El contenido del razonamiento está vacío por defecto, pero el bloque en sí se sigue devolviendo.
Entrada ligera (p. ej., ¿la capital de Japón?)
[0]textTokioEntrada pesada (p. ej., encontrar todos los números que cumplen las condiciones)
[0]thinkingVacío, solo la firma[1]textLa respuestaEntrada pesada que llega a max_tokens
[0]thinkingVacío, solo la firmaSin textostop_reason: "max_tokens"3. Lo que encontré al probarlo
Lo probé en mi propio entorno con el SDK de Python (anthropic 1.14.0) el 1 de noviembre de 2026. Envié a cuatro modelos una entrada ligera y una pesada una vez cada una, tanto sin thinking como con {type: "adaptive"}. Fijé max_tokens en 8000. La entrada ligera era una pregunta factual sencilla que pedía la capital de Japón en una sola palabra (la hicimos en japonés); la pesada era un problema que pedía todos los enteros de cuatro cifras que cumplen cinco condiciones.
| Modelo | Ajuste de thinking | content (entrada ligera) | content (entrada pesada) |
|---|---|---|---|
| Sonnet 4.6 | Sin indicar | text | text |
| Sonnet 4.6 | adaptive | text | solo thinking (llegó al límite) |
| Haiku 5.5 | Sin indicar | text | solo thinking (llegó al límite) |
| Haiku 5.5 | adaptive | text | solo thinking (llegó al límite) |
| Sonnet 5.5 | Sin indicar | text | thinking → text |
| Sonnet 5.5 | adaptive | text | thinking → text |
| Opus 5.5 | Sin indicar | text | thinking → text |
| Opus 5.5 | adaptive | thinking → text | thinking → text |
En rojo, las combinaciones en las que content[0].text lanzó AttributeError: 'ThinkingBlock' object has no attribute 'text'. Cada una es el resultado de una sola ejecución; que el modelo razone o no puede variar de una ejecución a otra, incluso con la misma entrada.
Tres conclusiones.
Primera: los modelos Claude 5 y posteriores razonaron con la entrada pesada incluso sin thinking. Con Sonnet 4.6, omitirlo mantuvo el texto como primer bloque. Esto confirma lo que dice la documentación: cambiar solo el nombre del modelo cambia la forma de la respuesta.
Segunda: con la entrada ligera, el primer bloque fue texto en casi todas las combinaciones. Si tus pruebas solo usan entradas ligeras, no encontrarás este problema. En cambio, Opus 5.5 con adaptive razonó incluso con la entrada ligera. En Claude 5 y posteriores, omitir thinking e indicar adaptive son la misma configuración, así que lo más probable es que esta diferencia se deba a la variación entre ejecuciones. No se puede afirmar que «las entradas ligeras no hacen razonar».
Tercera: al llegar al límite, no volvió nada de texto. Con la entrada pesada, Haiku 5.5 gastó los 8000 tokens en razonar, tanto sin thinking como con adaptive, y no devolvió ningún bloque de texto. Sonnet 4.6 con adaptive también llegó al límite. En Claude 5 y posteriores el contenido del razonamiento también está vacío por defecto, así que el usuario no ve nada, pero tú sigues pagando los tokens de salida.
4. Cómo solucionarlo
La solución es dejar de suponer qué bloque va primero y recoger los bloques de tipo texto (type es "text"). Comprueba también stop_reason para no tratar como respuesta normal una que se cortó en el límite.
Python
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=16000,
messages=[{"role": "user", "content": prompt}],
)
# No des por hecho el primer bloque: recoge solo los bloques de texto
text = "".join(b.text for b in response.content if b.type == "text")
if response.stop_reason == "max_tokens":
# Cortado en el límite. text puede estar vacío
raise RuntimeError("Se alcanzó max_tokens. Sube el límite o reduce el razonamiento")
if response.stop_reason == "end_turn" and not text:
# En flujos con herramientas, stop_reason es "tool_use" y el texto vacío es normal
raise RuntimeError("No hay bloque de texto")
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 }],
});
// Filtra por tipo antes de leer 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("Se alcanzó max_tokens. Sube el límite o reduce el razonamiento");
}
// En flujos con herramientas, stop_reason es "tool_use" y el texto vacío es normal
if (response.stop_reason === "end_turn" && text === "") {
throw new Error("No hay bloque de texto");
}
En el SDK de TypeScript, los elementos de content son una unión de tipos como texto, razonamiento y uso de herramientas. Escribir content[0].text directamente normalmente no pasa la comprobación de tipos. Pero si fuerzas el tipo con as, el error se cuela.
Fija max_tokens contando el razonamiento
La documentación «Steering thinking» explica que max_tokens es un límite conjunto para el razonamiento y el texto de la respuesta. También señala que un límite elegido suponiendo que el modelo no razonará suele quedarse corto para entradas difíciles. Cuando recibes stop_reason: "max_tokens", hay dos soluciones.
Los 16000 de los ejemplos anteriores son orientativos. En mis pruebas, incluso 8000 se gastaron a veces enteros en razonar. Para producción, prueba con entradas pesadas y fija el valor mirando usage.output_tokens_details.thinking_tokens (el número de tokens gastados en razonar) en la respuesta.
- Subir el límite: cuando el razonamiento mejora la calidad de la respuesta en las entradas que se cortaron
- Bajar el esfuerzo: cuando el modelo simplemente razona de más y la calidad de la respuesta no cambia (el esfuerzo se fija con
output_config.effort)
Al continuar una conversación, devuelve también los bloques de razonamiento
Cuando añadas una respuesta al historial de la conversación, no extraigas solo el texto; devuelve content tal cual. Sobre todo en flujos con herramientas, el bloque de razonamiento contiene el motivo de la llamada a la herramienta. La documentación pide devolver los bloques recibidos sin modificarlos, y los bloques modificados se rechazan con un error 400. Lo más seguro es mantener separados el texto que muestras y el content que devuelves en el historial.
5. ¿Se puede desactivar el razonamiento sin más?
Es tentador pensar «si el razonamiento estorba, lo desactivo», pero si se puede depende del modelo. Así se trata thinking: {type: "disabled"}.
| Modelo | {type: "disabled"} |
|---|---|
| Opus 5.5, Fable 5.1, Fable 5 | Error 400 (no se puede desactivar) |
| Sonnet 5.5 | Error 400 (en su lugar, {type: "between_tools"} omite el razonamiento inicial cuando el esfuerzo es high o inferior) |
| Haiku 5.5, Opus 5 | Se puede desactivar (solo con esfuerzo high o inferior; error 400 con xhigh y max) |
| Sonnet 5 | Se puede desactivar |
Fuente: «Thinking» de Anthropic (consultado el 1 de noviembre de 2026). También puedes comprobar si un modelo lo acepta con capabilities.thinking.types.disabled.supported en la Models API. El esfuerzo por defecto es medium en Opus 5.5 y Haiku 5.5 y high en la mayoría de los demás modelos, así que los valores por defecto cumplen la condición de «high o inferior».
Incluso en los modelos donde se puede desactivar, hacerlo puede bajar la calidad de la respuesta en entradas difíciles. Y el mismo problema vuelve cada vez que cambias de modelo. La solución de fondo es un código que lea la respuesta en cualquier orden. Si quieres reducir el razonamiento, baja primero el esfuerzo. Según la documentación de Anthropic, pedirle a Sonnet 5.5 en el prompt del sistema que razone menos no reduce el razonamiento de forma fiable, y Haiku 5.5 siguió razonando aunque se le pidiera responder directamente (en las pruebas de Anthropic). En ambos casos, ajústalo con el esfuerzo.
6. Cómo no pasarlo por alto en las pruebas
Este problema se escapa en las pruebas con entradas ligeras. Al cambiar de modelo, comprobar en este orden hace más difícil que se te pase.
- Busca
content[0]en tu código: enumera todos los sitios que leen por posición, comocontent[0],content[-1]y.content[0].text, no solo en el código de producción sino también en las pruebas y los registros - Añade entradas pesadas a tus pruebas: incluye como entradas de prueba las entradas más largas de producción y las tareas más difíciles (con solo entradas ligeras, el primer bloque sigue siendo texto y el fallo queda oculto)
- Usa respuestas simuladas que empiecen con un bloque de razonamiento: en las pruebas unitarias que no llaman a la API, prepara una respuesta
[thinking, text]y otra solo conthinkingystop_reason"max_tokens", para poder probar sin depender de si el modelo razona - Despliega de forma gradual: no lo cambies todo a la vez; pasa primero parte de la carga al nuevo modelo, vigila los recuentos de texto vacío y de excepciones, y luego amplía
⚠️ Cuando varios servicios o equipos cambian de modelo a la vez, añade una línea a la solicitud de migración: «El primer bloque de la respuesta no siempre es texto. Búscalo por tipo». El cambio parece un simple cambio de nombre, y si esperas a que cada equipo lo descubra por su cuenta, el código que pasó las pruebas con entradas ligeras se romperá en producción.
Esto no es lo único que hay que revisar al cambiar de modelo. Para lo que cambió al migrar desde generaciones anteriores, consulta «Guía de migración a Claude Opus 4.7» y «Los 3 cambios incompatibles de Claude Fable 5.1». El esfuerzo en la API se explica en la documentación «Effort» de Anthropic, y cómo elegir el esfuerzo en Claude Code, en «El ajuste de esfuerzo (effort) de Claude Code».
Resumen
AttributeError: 'ThinkingBlock' object has no attribute 'text'se produce cuando el primer bloque de la respuesta es un bloque de razonamiento y intentas leer el texto concontent[0].text- Los modelos Claude 5 y posteriores razonan cuando lo necesitan incluso sin
thinking, así que basta con cambiar el nombre del modelo desde uno de Claude 4 para que cambie la forma de la respuesta - Con entradas ligeras, el primer bloque suele seguir siendo texto, así que las pruebas ligeras pasan
- La solución es recoger los bloques cuyo
typees"text"y comprobar sistop_reasones"max_tokens" - Algunos modelos no permiten desactivar el razonamiento, así que la solución de fondo no es desactivarlo, sino escribir código que lea la respuesta en cualquier orden
Preguntas frecuentes
P. Nunca indiqué thinking. ¿Por qué recibo un bloque de razonamiento?
R. En los modelos Claude 5 y posteriores, el razonamiento adaptativo está activo incluso sin thinking, y el modelo razona en las entradas en las que decide que lo necesita. El contenido del razonamiento está vacío por defecto, pero el bloque en sí se incluye en la respuesta. Los modelos Claude 4, como Claude Opus 4.8 y Sonnet 4.6, no razonan si no lo indicas.
P. ¿Por qué recibo una respuesta sin ningún bloque de texto?
R. Los tokens de razonamiento cuentan para max_tokens, y se llegó al límite antes de que el modelo escribiera texto. stop_reason debería ser "max_tokens". Sube el límite o baja el esfuerzo para reducir el razonamiento.
P. ¿Y si quiero leer el contenido del razonamiento?
R. Indica thinking: {type: "adaptive", display: "summarized"} para obtener un resumen del razonamiento. El valor por defecto en Claude 5 y posteriores es "omitted" (el contenido está vacío y solo se incluye la firma). En cualquier caso, se te facturan todos los tokens de razonamiento.
Historial de cambios
- 1 de noviembre de 2026Primera versión. Escrita tras revisar la documentación «Thinking» y «Steering thinking» de Anthropic y probar el orden de la respuesta de cuatro modelos en mi propio entorno.
Fuentes
- Anthropic: Thinking, Steering thinking