Claude Sonnet 4.6からSonnet 5移行後にBedrock Converse APIでエラーが出たので調査してみた
はじめに
最近Amazon BedrockでSonnet 5が使えるようになりましたね。
これを機にSonnet 4.6を5に移行しよう!と思ったら、躓いた話をします。
何が起きたかというと、Sonnet 4.6までは問題なく動いていたコードが、Sonnet 5に移行したところ、KeyError: 'text'というエラーを出すようになりました。
しかも毎回ではなく、数回に1回程度の頻度のこの事象が発生していました。
先に結論
原因はSonnet 5のレスポンス形式の変更によるものでした。
以下のブログも参考になるかと思います。
temperature/top_p/top_kはSonnet 5で非対応。設定すると400エラーになる- Converse APIの
content配列に入るblockはtextだけではない - Claude Sonnet 5はadaptive thinkingが常にオン(無効化不可)であり、
reasoningContentがcontent[0]に入る場合がある content[0]で決め打ちでtextを取得しようとするとKeyErrorになる可能性がある
AWS公式ブログでもClaudeのSDK for Python (Boto3)のサンプルコードを見るとcontent[0]でtextを取得するサンプルコードが書かれているものもあります。
AIにドキュメントを参考にコードを書かせたりしているとこの記事に書いてあるようなエラーになることが予想されます。
試してみる
今回はシンプルにpythonでBedrockのConverse APIを実行して、レスポンスを解析してみたいと思います。
やることは単純で、Converse APIにプロンプトを投げて、返ってきたcontentからテキストを取り出すだけです。
AIとのチャット機能を持ったアプリではよく見る構成かと思います。
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()
デバッグ用にmodel・prompt・content blockの構造を出力しています。
model: us.anthropic.claude-sonnet-4-6
prompt: S3はすべての操作に対して強整合性(Strong Consistency)を提供しますが、...
content blocks 数: 1
content[0] keys: ['text']
content[0]['text'] → 成功 (5733 文字)
response: # S3強整合性の誤解と競合状態の問題
## 結論から言うと「データが失われる」...
content blockが1つでtextのみ返ってきてます。
これをcontent[0]["text"]で取り出します。これがAIからの回答テキストで、チャット画面に表示する内容になります。
modelIdだけSonnet 5に変更する
ここからが本題で、BedrockでSonnet 5が使えるようになったので、既存のSonnet 4.6をSonnet 5に変更します。
modelIdをus.anthropic.claude-sonnet-5に変えます。
response = client.converse(
modelId="us.anthropic.claude-sonnet-5", # ← ここだけ変更
...
inferenceConfig={"maxTokens": 4096, "temperature": 0.0},
)
実行すると、プロンプトの内容に関係なくエラーになりました。
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.
temperatureが廃止されてる?と思ってドキュメントを確認すると、Sonnet 5ではtemperature・top_p・top_kが非対応になっていました。
サンプリングパラメータ (temperature、top_p、top_k)をデフォルト値以外に設定すると400エラーが返されます。
参考 https://platform.claude.com/docs/en/about-claude/models/whats-new-sonnet-5
Sonnet 4.6まで普通に使っていたパラメータが使えなくなっていました。
細かいパラメータで回答精度を調整しているアプリケーションの場合この変更は辛いかもしれませんね
今回はinferenceConfigからこれらを削除します。
temperatureを削除して再実行
inferenceConfig={"maxTokens": 4096},
簡単な質問では成功します
model: us.anthropic.claude-sonnet-5
prompt: S3のバケット名の最大文字数は?
content blocks 数: 1
content[0] keys: ['text']
content[0]['text'] → 成功 (497 文字)
response: # S3バケット名の最大文字数
**63文字**です。...
一見問題ないように見えます。
直った!と思って質問を続けると...
model: us.anthropic.claude-sonnet-5
prompt: S3はすべての操作に対して強整合性(Strong Consistency)を提供しますが、...
content blocks 数: 2
content[0] keys: ['reasoningContent']
content[1] keys: ['text']
Traceback (most recent call last):
...
raw_text = content[0]["text"].strip()
~~~~~~~~~~^^^^^^^^
KeyError: 'text'
なぜかエラーが発生しました。
よく見るとcontent blockが2つに増えており、content[0]がreasoningContentになっています。
content[1]にtextは存在しているのに、content[0]["text"]でKeyErrorが発生します。
reasoningContentって何だろうとドキュメントを確認してみると、Sonnet 5ではadaptive thinking(思考モード)がデフォルトでオンになっているようで、無効化もできないようです。
理由:サポートされています(適応思考は常に有効で無効にすることはできません。努力レベルは設定可能です)
参考: https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5.html
何が起きていたかというとSonnet 5はモデルが推論を必要と判断した場合、その思考過程をreasoningContentとしてcontent配列の先頭に追加します。
今回エラーが出た質問ではadaptive thinkingが必要な難しい問題だったようです。
Sonnet 4.6の実装ではcontent配列の1番目(content[0])が常にtextだったのでcontent[0]["text"]で取り出せていましたが、Sonnet 5ではcontent[0]がreasoningContentになるケースがあり、そこがエラーになるポイントでした。
重要なのは毎回ではないということで、
簡単な質問ではtextのみが返るため今までのコードで動いてましたが、Sonnet 5が推論を必要と判断した場合にだけreasoningContentが先頭に入るためエラーになります。
content blockはtextだけではない
Converse APIのレスポンスに含まれるcontentは配列で、要素は複数返ることがあります。
具体的には以下の要素があります。
| blockの種類 | 説明 |
|---|---|
text |
テキスト応答 |
reasoningContent |
推論過程(Chain of Thought) |
toolUse |
ツール呼び出しリクエスト |
toolResult |
ツール実行結果 |
image |
画像 |
document |
ドキュメント |
citationsContent |
引用付きテキスト |
guardContent |
ガードレール評価用 |
公式ドキュメント: ContentBlock - Amazon Bedrock API Reference
今回の場合、実際に返ってきたレスポンスは以下のような構造でした
{
"output": {
"message": {
"content": [
{
"reasoningContent": {
"reasoningText": {
"text": "ユーザーはS3の強整合性について質問しています...",
"signature": "WaUjzkypQ2mUEVM36O2TxuC06KN8xyfbJwyem2dw..."
}
}
},
{
"text": "# S3の強整合性と並行更新問題\n..."
}
]
}
}
}
content[0]がreasoningContent、content[1]がtext。
content[0]["text"]で取り出そうとすれば当然KeyErrorになります。
修正方針
修正の方針としては、content[0]を決め打ちせず、content配列をループしてtextキーを持つblockのみを取り出す形にします。reasoningContentやtoolUseなどは無視します。
また、textが1件も取れなかった場合に空文字で処理を続行するとサイレント不具合になるので、例外を投げるようにします。
修正後のコード
from typing import Any
def extract_text_from_converse_response(response: dict[str, Any]) -> str:
"""
Converse API のレスポンスから text block のみを取り出す。
reasoningContent / toolUse 等は無視する。
"""
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()
修正後の実行結果
先ほどKeyErrorになった同じプロンプトを、修正後のコードで実行してみます。
model: us.anthropic.claude-sonnet-5
prompt: S3はすべての操作に対して強整合性(Strong Consistency)を提供しますが、...
content blocks 数: 2
content[0] keys: ['reasoningContent']
content[1] keys: ['text']
extract_text_from_converse_response → 成功 (3476 文字)
response: # S3の強整合性とRead-Modify-Writeパターンの問題...
reasoningContentをスキップしてtext blockを正しく取得できました。
まとめ
Sonnet 4.6 → 5への移行で気をつけるポイントとしては、まずtemperature / top_p / top_kが廃止されているのでinferenceConfigから削除する必要があります。
次に、adaptive thinkingが常にオンのためcontent[0]がtextとは限りません。content[0]で決め打ちでtextを取得しようとすると、reasoningContentが先頭に入ったタイミングでKeyErrorになる可能性があります。
contentをループしてtextblockを探す実装にしましょう。
公式ドキュメントのサンプルコードでもcontent[0]["text"]と書かれていますが、あくまで簡易サンプルです。
Sonnet 5以降のモデルを使う場合は、content blockの構造を意識した実装にしておくと安心かと思います。






