Vous avez fait passer vos appels à l'API Claude sur un modèle plus récent, et vous obtenez maintenant l'erreur ci-dessous. Ou bien le texte de la réponse revient vide. La cause est généralement la même : le premier bloc du contenu (content[0]) n'est plus du texte, mais un bloc de réflexion.
AttributeError: 'ThinkingBlock' object has no attribute 'text'
Si vous lisez response.content[0].text avec le SDK Python, vous obtenez cette erreur. En JavaScript, le même code renvoie undefined : au lieu d'échouer, une chaîne vide circule dans votre application. Le piège : avec des entrées simples, le premier bloc reste du texte, donc les tests qui utilisent des entrées légères passent.
Le problème en trois lignes
Ce qui se passe
Le premier bloc n'est plus du texte
Surtout avec des entrées lourdes, un bloc de réflexion arrive en tête de la réponse (parfois aussi avec des entrées légères). content[0].text casse.
Cause
Il réfléchit sans qu'on le lui demande
Les modèles Claude 5 et suivants réfléchissent quand c'est nécessaire, même si vous ne définissez pas thinking. Les modèles Claude 4 ne réfléchissent pas si vous ne le définissez pas.
Correction
Chercher les blocs par type
Ne supposez pas le premier bloc. Rassemblez les blocs dont le type est "text", et vérifiez aussi stop_reason.
1. Quand cela se produit
Le cas typique est une migration où vous changez seulement le nom du modèle. Par exemple, vous passez de Claude Sonnet 4.6 à Claude Sonnet 5.5 en réécrivant uniquement la valeur de model. En supposant que la forme de la réponse est la même, vous ne touchez pas au reste du code. Il se passe alors ce qui suit (le troisième cas se produit même avec un code correctement écrit).
| Manière de lire | Ce qui se passe |
|---|---|
content[0].text avec le SDK Python | Échoue avec AttributeError: 'ThinkingBlock' object has no attribute 'text' |
content[0].text en JavaScript | Pas d'erreur : la valeur devient undefined, et un texte vide finit à l'écran ou en base |
| Recherche du bloc de texte | Si le modèle réfléchit beaucoup et atteint max_tokens, il peut n'y avoir aucun bloc de texte |
Le troisième cas se produit même avec un code correctement écrit. Les jetons de réflexion comptent dans max_tokens : si la limite est basse, la réponse est coupée avant que le modèle n'écrive le moindre texte.
2. Cause : Claude 5 et suivants réfléchissent sans qu'on le demande
La documentation « Thinking » d'Anthropic indique, modèle par modèle, ce qui se passe quand la requête ne contient pas thinking. La ligne de partage est Claude 5.
| Modèle | Sans thinking | Contenu de la réflexion |
|---|---|---|
| Claude 5 et suivants (Opus 5.5, Sonnet 5.5, Haiku 5.5, Fable 5.1, etc.) | Réfléchit (réflexion adaptative) | Vide par défaut (display: "omitted") |
| Opus 4.8, Opus 4.7 | Ne réfléchit pas | Réfléchit seulement si vous définissez thinking: {type: "adaptive"} ; contenu vide par défaut ("omitted") |
| Opus 4.6, Sonnet 4.6 | Ne réfléchit pas | Réfléchit seulement si vous définissez thinking: {type: "adaptive"} ; renvoie un résumé par défaut ("summarized") |
Source : le tableau par modèle et la description de display dans « Thinking » d'Anthropic (vérifié le 1er novembre 2026). Opus 4.5, Sonnet 4.5 et Haiku 4.5 n'acceptent pas {type: "adaptive"} ; pour les faire réfléchir, utilisez {type: "enabled"} avec budget_tokens.
Le modèle décide pour chaque entrée s'il réfléchit ou non. La documentation « Steering thinking » explique que, pour de simples questions factuelles, le modèle peut répondre sans bloc de réflexion. Elle recommande ensuite de ne pas construire la logique de votre application sur l'hypothèse que chaque réponse commence par un bloc de réflexion. Autrement dit, le premier bloc est tantôt du texte, tantôt de la réflexion. Un code qui suppose l'un ou l'autre cassera quelque part.
Le contenu de la réponse est ordonné comme ci-dessous. Le contenu de la réflexion est vide par défaut, mais le bloc lui-même est quand même renvoyé.
Entrée légère (ex. : la capitale du Japon ?)
[0]textTokyoEntrée lourde (ex. : trouver tous les nombres qui remplissent les conditions)
[0]thinkingVide, signature seule[1]textLa réponseEntrée lourde qui atteint max_tokens
[0]thinkingVide, signature seulePas de textestop_reason: "max_tokens"3. Ce que j'ai constaté en test
J'ai testé cela dans mon propre environnement avec le SDK Python (anthropic 1.14.0) le 1er novembre 2026. J'ai envoyé à quatre modèles une entrée légère et une entrée lourde, une fois chacune, sans thinking puis avec {type: "adaptive"}. J'ai fixé max_tokens à 8000. L'entrée légère était une simple question factuelle demandant la capitale du Japon en un seul mot (posée en japonais) ; l'entrée lourde était un problème demandant tous les entiers à quatre chiffres qui remplissent cinq conditions.
| Modèle | Réglage de thinking | content (entrée légère) | content (entrée lourde) |
|---|---|---|---|
| Sonnet 4.6 | Non défini | text | text |
| Sonnet 4.6 | adaptive | text | thinking seul (limite atteinte) |
| Haiku 5.5 | Non défini | text | thinking seul (limite atteinte) |
| Haiku 5.5 | adaptive | text | thinking seul (limite atteinte) |
| Sonnet 5.5 | Non défini | text | thinking → text |
| Sonnet 5.5 | adaptive | text | thinking → text |
| Opus 5.5 | Non défini | text | thinking → text |
| Opus 5.5 | adaptive | thinking → text | thinking → text |
En rouge, les combinaisons où content[0].text a levé AttributeError: 'ThinkingBlock' object has no attribute 'text'. Chaque ligne est le résultat d'une seule exécution ; le fait que le modèle réfléchisse ou non peut varier d'une exécution à l'autre, même avec la même entrée.
Trois enseignements.
Premièrement, les modèles Claude 5 et suivants ont réfléchi sur l'entrée lourde même sans thinking. Avec Sonnet 4.6, l'omettre laissait le texte en premier bloc. Cela confirme ce que dit la documentation : changer seulement le nom du modèle change la forme de la réponse.
Deuxièmement, avec l'entrée légère, le premier bloc était du texte dans presque toutes les combinaisons. Si vos tests n'utilisent que des entrées légères, vous ne trouverez pas ce problème. En revanche, Opus 5.5 avec adaptive a réfléchi même sur l'entrée légère. Sur Claude 5 et suivants, omettre thinking et définir adaptive reviennent à la même configuration : cette différence relève très probablement de la variation d'une exécution à l'autre. On ne peut pas dire que « les entrées légères ne déclenchent pas de réflexion ».
Troisièmement, quand la limite était atteinte, aucun texte n'est revenu. Sur l'entrée lourde, Haiku 5.5 a consacré les 8000 jetons à la réflexion, sans thinking comme avec adaptive, et n'a renvoyé aucun bloc de texte. Sonnet 4.6 avec adaptive a aussi atteint la limite. Sur Claude 5 et suivants, le contenu de la réflexion est en outre vide par défaut : les utilisateurs ne voient rien, mais vous payez quand même les jetons de sortie.
4. Comment corriger
La correction consiste à ne plus supposer quel bloc arrive en premier et à rassembler les blocs de type texte (type vaut "text"). Vérifiez aussi stop_reason pour ne pas traiter une réponse coupée à la limite comme une réponse normale.
Python
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=16000,
messages=[{"role": "user", "content": prompt}],
)
# Ne pas supposer le premier bloc : ne garder que les blocs de texte
text = "".join(b.text for b in response.content if b.type == "text")
if response.stop_reason == "max_tokens":
# Coupé à la limite. text peut être vide
raise RuntimeError("max_tokens atteint. Augmentez la limite ou réduisez la réflexion")
if response.stop_reason == "end_turn" and not text:
# Avec les outils, stop_reason vaut "tool_use" et un texte vide est normal
raise RuntimeError("Aucun bloc de texte")
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 }],
});
// Restreindre par type avant de lire 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 atteint. Augmentez la limite ou réduisez la réflexion");
}
// Avec les outils, stop_reason vaut "tool_use" et un texte vide est normal
if (response.stop_reason === "end_turn" && text === "") {
throw new Error("Aucun bloc de texte");
}
Dans le SDK TypeScript, les éléments de content sont une union de types comme le texte, la réflexion et l'utilisation d'outils. Écrire directement content[0].text devrait normalement échouer à la vérification de types. Mais si vous forcez le type avec as, l'erreur passe inaperçue.
Fixer max_tokens en comptant la réflexion
La documentation « Steering thinking » explique que max_tokens est une limite commune à la réflexion et au texte de la réponse. Elle note aussi qu'une limite choisie en supposant que le modèle ne réfléchira pas est souvent trop basse pour les entrées difficiles. Quand vous obtenez stop_reason: "max_tokens", il y a deux solutions.
Le 16000 des exemples ci-dessus est un ordre de grandeur. Dans mes tests, même 8000 a parfois été entièrement consommé par la réflexion. Pour la production, lancez des entrées lourdes et fixez la valeur en regardant usage.output_tokens_details.thinking_tokens (le nombre de jetons consacrés à la réflexion) dans la réponse.
- Augmenter la limite : quand la réflexion améliore la qualité de la réponse sur les entrées qui ont été coupées
- Baisser l'effort : quand le modèle réfléchit simplement trop et que la qualité de la réponse ne change pas (réglez l'effort avec
output_config.effort)
Pour poursuivre une conversation, renvoyez aussi les blocs de réflexion
Quand vous ajoutez une réponse à l'historique de la conversation, n'en extrayez pas seulement le texte : renvoyez content tel quel. En particulier avec les outils, le bloc de réflexion contient le raisonnement qui a mené à l'appel d'outil. La documentation demande de renvoyer les blocs reçus sans les modifier, et les blocs modifiés sont rejetés avec une erreur 400. Le plus sûr est de séparer le texte que vous affichez du content que vous renvoyez dans l'historique.
5. Peut-on simplement désactiver la réflexion ?
On est tenté de se dire « si la réflexion gêne, il suffit de la désactiver », mais c'est possible ou non selon le modèle. Voici comment thinking: {type: "disabled"} est traité.
| Modèle | {type: "disabled"} |
|---|---|
| Opus 5.5, Fable 5.1, Fable 5 | Erreur 400 (impossible de la désactiver) |
| Sonnet 5.5 | Erreur 400 (à la place, {type: "between_tools"} saute la réflexion initiale quand l'effort est high ou inférieur) |
| Haiku 5.5, Opus 5 | Désactivable (seulement quand l'effort est high ou inférieur ; erreur 400 en xhigh et max) |
| Sonnet 5 | Désactivable |
Source : « Thinking » d'Anthropic (vérifié le 1er novembre 2026). Vous pouvez aussi vérifier si un modèle l'accepte avec capabilities.thinking.types.disabled.supported dans l'API Models. L'effort par défaut est medium pour Opus 5.5 et Haiku 5.5 et high pour la plupart des autres modèles : les valeurs par défaut remplissent donc la condition « high ou inférieur ».
Même sur les modèles où c'est possible, la désactiver peut dégrader la qualité des réponses sur les entrées difficiles. Et le même problème revient à chaque changement de modèle. La vraie correction, c'est un code capable de lire une réponse quel que soit l'ordre de ses blocs. Si vous voulez réduire la réflexion, baissez d'abord l'effort. Selon la documentation d'Anthropic, demander à Sonnet 5.5 dans le prompt système de moins réfléchir ne réduit pas la réflexion de façon fiable, et Haiku 5.5 a continué à réfléchir même quand on lui demandait de répondre directement (dans les tests d'Anthropic). Pour les deux, ajustez avec l'effort.
6. Comment ne pas le rater en test
Ce problème échappe aux tests avec des entrées légères. Lors d'un changement de modèle, vérifier dans l'ordre suivant le rend plus difficile à manquer.
- Repérer
content[0]dans votre code : listez tous les endroits qui lisent par position, commecontent[0],content[-1]et.content[0].text, pas seulement dans le code de production mais aussi dans les tests et la journalisation - Ajouter des entrées lourdes à vos tests : incluez vos entrées de production les plus longues et vos tâches les plus difficiles comme entrées de test (avec des entrées légères seules, le premier bloc reste du texte et la casse reste cachée)
- Injecter de fausses réponses qui commencent par un bloc de réflexion : dans les tests unitaires qui n'appellent pas l'API, préparez une réponse
[thinking, text]et une réponse composée uniquement dethinkingavecstop_reasonà"max_tokens", afin de tester indépendamment du fait que le modèle réfléchisse ou non - Déployer progressivement : ne basculez pas tout d'un coup ; faites passer d'abord une partie des traitements sur le nouveau modèle, surveillez le nombre de textes vides et d'exceptions, puis étendez
⚠️ Quand plusieurs services ou équipes changent de modèle ensemble, ajoutez une ligne à la demande de migration : « Le premier bloc de la réponse n'est pas toujours du texte. Cherchez-le par type. » Le changement ressemble à un simple renommage, et si vous attendez que chaque équipe s'en aperçoive seule, du code qui a passé les tests avec des entrées légères cassera en production.
Ce n'est pas la seule chose à vérifier lors d'un changement de modèle. Pour ce qui a changé lors des migrations depuis les générations précédentes, voir « Claude Opus 4.7 : guide de migration » et « Les 3 changements cassants de Claude Fable 5.1 ». L'effort dans l'API est décrit dans la documentation « Effort » d'Anthropic, et le choix de l'effort dans Claude Code dans « Claude Code : le réglage « Effort » expliqué ».
Résumé
AttributeError: 'ThinkingBlock' object has no attribute 'text'survient quand le premier bloc de la réponse est un bloc de réflexion et que vous essayez de lire le texte aveccontent[0].text- Les modèles Claude 5 et suivants réfléchissent quand c'est nécessaire même sans
thinking: changer simplement le nom du modèle depuis un modèle Claude 4 change donc la forme de la réponse - Avec des entrées légères, le premier bloc reste généralement du texte, donc les tests légers passent
- La correction consiste à rassembler les blocs dont le
typeest"text"et à vérifier sistop_reasonvaut"max_tokens" - Certains modèles ne permettent pas de désactiver la réflexion : la vraie correction n'est donc pas de la désactiver, mais d'écrire un code qui lit une réponse quel que soit l'ordre de ses blocs
FAQ
Q. Je n'ai jamais défini thinking. Pourquoi est-ce que je reçois un bloc de réflexion ?
R. Sur les modèles Claude 5 et suivants, la réflexion adaptative est active même sans thinking, et le modèle réfléchit sur les entrées où il le juge nécessaire. Le contenu de la réflexion est vide par défaut, mais le bloc lui-même figure dans la réponse. Les modèles Claude 4 comme Claude Opus 4.8 et Sonnet 4.6 ne réfléchissent pas si vous ne le définissez pas.
Q. Pourquoi est-ce que je reçois une réponse sans aucun bloc de texte ?
R. Les jetons de réflexion comptent dans max_tokens, et la limite a été atteinte avant que le modèle n'écrive le moindre texte. stop_reason devrait valoir "max_tokens". Augmentez la limite, ou baissez l'effort pour réduire la réflexion.
Q. Et si je veux lire le contenu de la réflexion ?
R. Définissez thinking: {type: "adaptive", display: "summarized"} pour obtenir un résumé de la réflexion. La valeur par défaut sur Claude 5 et suivants est "omitted" (le contenu est vide et seule la signature est incluse). Dans les deux cas, tous les jetons de réflexion vous sont facturés.
Historique des mises à jour
- 1er novembre 2026Première version. Rédigée après examen des documentations « Thinking » et « Steering thinking » d'Anthropic et test de l'ordre des réponses de quatre modèles dans mon propre environnement.
Sources
- Anthropic : Thinking, Steering thinking