I investigated an error that occurred in the Bedrock Converse API after migrating from Claude Sonnet 4.6 to Sonnet 5
This page has been translated by machine translation. View original
Introduction
Sonnet 5 has recently become available on Amazon Bedrock.
I'll share a story about the stumbling blocks I encountered when trying to migrate from Sonnet 4.6 to 5.
What happened was that code which had been working fine up through Sonnet 4.6 started throwing a KeyError: 'text' error after migrating to Sonnet 5.
Moreover, this issue didn't occur every time — it happened roughly once every few attempts.
Conclusion First
The cause was a change in Sonnet 5's response format.
The following blog post may also be helpful.
temperature/top_p/top_kare not supported in Sonnet 5. Setting them will result in a 400 error- The blocks included in the
contentarray of the Converse API are not limited totext - Claude Sonnet 5 has adaptive thinking always on (cannot be disabled), and
reasoningContentmay appear incontent[0] - Attempting to retrieve text by hardcoding
content[0]can result in aKeyError
Even in the official AWS blog, some sample code for Claude's SDK for Python (Boto3) retrieves text using content[0].
If you have AI write code based on documentation, it's likely you'll encounter the kind of error described in this article.
Let's Try It Out
This time, I'd like to simply execute the Bedrock Converse API in Python and analyze the response.
The task is straightforward: send a prompt to the Converse API and extract text from the returned content.
This is a common pattern seen in applications with AI chat functionality.
Code That Worked with Sonnet 4.6
import boto3
client = boto3.client("bedrock-runtime", region_name="us-east-1")
response = client.converse(
modelId="us.anthropic.claude-sonnet-4-6",
messages=[
{
"role": "user",
"content": [{"text": prompt}],
}
],
inferenceConfig={"maxTokens": 4096, "temperature": 0.0},
)
raw_text = response["output"]["message"]["content"][0]["text"].strip()
For debugging purposes, the model, prompt, and content block structure are printed.
model: us.anthropic.claude-sonnet-4-6
prompt: S3 provides Strong Consistency for all operations, but...
content blocks count: 1
content[0] keys: ['text']
content[0]['text'] → success (5733 characters)
response: # Misconceptions About S3 Strong Consistency and Race Condition Issues
## In conclusion, "data is lost"...
One content block is returned containing only text.
This is extracted using content[0]["text"]. This is the response text from the AI and is the content displayed on the chat screen.
Changing Only the modelId to Sonnet 5
Here's the main topic: now that Sonnet 5 is available on Bedrock, let's update the existing Sonnet 4.6 to Sonnet 5.
Change modelId to us.anthropic.claude-sonnet-5.
response = client.converse(
modelId="us.anthropic.claude-sonnet-5", # ← only this was changed
...
inferenceConfig={"maxTokens": 4096, "temperature": 0.0},
)
When executed, an error occurred regardless of the prompt content.
Traceback (most recent call last):
...
botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling
the Converse operation: The model returned the following errors:
`temperature` is deprecated for this model.
Thinking that temperature had been deprecated, I checked the documentation and found that temperature, top_p, and top_k are no longer supported in Sonnet 5.
Reason: Setting sampling parameters (temperature, top_p, top_k) to non-default values will return a 400 error.
Reference: https://platform.claude.com/docs/en/about-claude/models/whats-new-sonnet-5
Parameters that had been used without issue up through Sonnet 4.6 were no longer available.
For applications that fine-tune response accuracy with detailed parameters, this change may be quite painful.
For now, we'll remove these from inferenceConfig.
Re-running After Removing temperature
inferenceConfig={"maxTokens": 4096},
Simple questions succeed.
model: us.anthropic.claude-sonnet-5
prompt: What is the maximum number of characters in an S3 bucket name?
content blocks count: 1
content[0] keys: ['text']
content[0]['text'] → success (497 characters)
response: # Maximum Characters in an S3 Bucket Name
It is **63 characters**....
At first glance it looks fine.
Thinking it's fixed, I continue asking questions...
model: us.anthropic.claude-sonnet-5
prompt: S3 provides Strong Consistency for all operations, but...
content blocks count: 2
content[0] keys: ['reasoningContent']
content[1] keys: ['text']
Traceback (most recent call last):
...
raw_text = content[0]["text"].strip()
~~~~~~~~~~^^^^^^^^
KeyError: 'text'
An error occurred for some reason.
Looking closely, the content blocks have increased to two, and content[0] is now reasoningContent.
Even though text exists in content[1], a KeyError occurs when trying to access content[0]["text"].
Checking the documentation to understand what reasoningContent is, it appears that adaptive thinking (thinking mode) is enabled by default in Sonnet 5 and cannot be disabled.
Reason: Supported (adaptive thinking is always enabled and cannot be disabled. The effort level is configurable.)
Reference: https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5.html
What was happening is that when Sonnet 5 determines that a prompt requires reasoning, it prepends the thought process as reasoningContent at the beginning of the content array.
The question that triggered the error was apparently a difficult problem requiring adaptive thinking.
In the Sonnet 4.6 implementation, the first element of the content array (content[0]) was always text, so it could be retrieved with content[0]["text"]. However, in Sonnet 5 there are cases where content[0] becomes reasoningContent, and that was the point causing the error.
The important thing to note is that this doesn't happen every time.
Simple questions only return text, so the previous code worked fine. The error only occurs when Sonnet 5 determines that reasoning is required and inserts reasoningContent at the beginning.
Content Blocks Are Not Just text
The content included in the Converse API response is an array, and multiple elements can be returned.
Specifically, the following elements exist.
| Block Type | Description |
|---|---|
text |
Text response |
reasoningContent |
Reasoning process (Chain of Thought) |
toolUse |
Tool call request |
toolResult |
Tool execution result |
image |
Image |
document |
Document |
citationsContent |
Text with citations |
guardContent |
For guardrail evaluation |
Official documentation: ContentBlock - Amazon Bedrock API Reference
In this case, the actual response structure returned was as follows.
{
"output": {
"message": {
"content": [
{
"reasoningContent": {
"reasoningText": {
"text": "The user is asking about S3 strong consistency...",
"signature": "WaUjzkypQ2mUEVM36O2TxuC06KN8xyfbJwyem2dw..."
}
}
},
{
"text": "# S3 Strong Consistency and Concurrent Update Issues\n..."
}
]
}
}
}
content[0] is reasoningContent and content[1] is text.
Naturally, attempting to retrieve content[0]["text"] will result in a KeyError.
Fix Approach
The approach for the fix is to avoid hardcoding content[0] and instead loop through the content array to extract only blocks containing the text key. Blocks like reasoningContent and toolUse are ignored.
Additionally, if no text is found, continuing with an empty string would result in a silent bug, so we'll throw an exception instead.
Fixed Code
from typing import Any
def extract_text_from_converse_response(response: dict[str, Any]) -> str:
"""
Extracts only text blocks from a Converse API response.
Ignores reasoningContent, toolUse, etc.
"""
content = response["output"]["message"]["content"]
texts: list[str] = []
for block in content:
if "text" in block:
texts.append(block["text"])
if not texts:
raise ValueError(
f"No text block found. keys in each block: "
f"{[list(b.keys()) for b in content]}"
)
return "\n".join(texts).strip()
Execution Results After Fix
Let's run the same prompt that previously caused a KeyError using the fixed code.
model: us.anthropic.claude-sonnet-5
prompt: S3 provides Strong Consistency for all operations, but...
content blocks count: 2
content[0] keys: ['reasoningContent']
content[1] keys: ['text']
extract_text_from_converse_response → success (3476 characters)
response: # S3 Strong Consistency and Read-Modify-Write Pattern Issues...
The reasoningContent was skipped and the text block was retrieved correctly.
Summary
Key points to keep in mind when migrating from Sonnet 4.6 to 5: first, temperature / top_p / top_k have been deprecated, so they need to be removed from inferenceConfig.
Next, since adaptive thinking is always on, content[0] is not guaranteed to be text. Attempting to retrieve text by hardcoding content[0] risks a KeyError whenever reasoningContent appears at the beginning.
Make sure to implement logic that loops through content to find text blocks.
While even official documentation sample code writes content[0]["text"], that is only a simplified sample.
When using Sonnet 5 or later models, implementing with awareness of the content block structure should give you more peace of mind.
