Amazon Bedrock の Converse API は、Claude・Nova・Llama など Bedrock 上のチャット系モデルを同じリクエスト形式で呼び出せる統一インターフェースです。モデルごとに異なる InvokeModel の JSON を書き分ける必要がなくなり、モデルを差し替えてもアプリ側のコードはほぼそのまま使えます。この記事では、boto3 での最小実装、ConverseStream によるストリーミング、Tool Use(外部ツール連携)のループ、そして見落としやすい IAM 権限までを、公式仕様に沿った実コードで整理します。検索でよく打ち込まれる bedrock:converse というアクション名の誤解も、あわせて解消します。
まとめ:Converse APIの要点
先に結論だけ押さえます。詳細は各セクションでコードとともに解説します。
- エンドポイントとメソッド:
bedrock-runtimeクライアントのconverse(一括応答)とconverse_stream(逐次ストリーミング)。リクエスト本文は両者共通。 - 会話状態はアプリ側で保持:Bedrock は状態を持たない。毎ターン
messagesに全履歴を積み、返ってきたoutput.messageをそのまま追記する。 - IAM の要注意点:
bedrock:Converseというアクションは存在しない。converseはbedrock:InvokeModel、converse_streamはbedrock:InvokeModelWithResponseStreamで認可される。 - Tool Use は決定論的ループ:
stopReasonがtool_useになったらツールを実行し、toolResultを返して再度呼ぶ。
Converse APIとは:モデル差を吸収する統一インターフェース
Converse API は bedrock-runtime エンドポイント上の2つの操作、Converse と ConverseStream として提供されます。どちらも messages・system・inferenceConfig・toolConfig という共通のリクエスト形を取り、レスポンスも output・stopReason・usage・metrics に正規化されて返ります。従来の InvokeModel がモデルごとに独自のボディ構造を要求したのに対し、Converse は「一度書けば別モデルでも動く」設計になっています。
InvokeModelとの違いとConverseを選ぶ判断基準
InvokeModel は Anthropic なら anthropic_version や max_tokens、Cohere なら別のキー、というようにモデル提供元のスキーマをそのまま渡します。Converse はこの差を inferenceConfig(全モデル共通の maxTokens・temperature・topP・stopSequences)と additionalModelRequestFields(top_k などモデル固有パラメータ)に分離して吸収します。新規のチャット・エージェント実装なら Converse を既定に選ぶべきです。InvokeModel を選ぶ理由が残るのは、Converse がまだ対応していない特殊なレスポンス項目を生で扱いたい場合や、既存資産の互換維持が優先される場合に限られます。Bedrock 自体の始め方はAmazon Bedrockの使い方(料金・モデル・APIの始め方)を参照してください。
messages・output・stopReasonのリクエスト/レスポンス構造
messages は role(user または assistant)と content(テキスト・画像・ドキュメント・ツール結果などのブロック配列)を持つオブジェクトの配列です。レスポンスでは生成メッセージが output.message.content に入り、生成が止まった理由が stopReason に入ります。取り得る値は end_turn・tool_use・max_tokens・stop_sequence・guardrail_intervened・content_filtered です。トークン数は usage(inputTokens・outputTokens・totalTokens)で確認できます。
boto3でConverse APIを呼び出す最小実装
「boto3 converse」で検索される実装部分です。ポイントはクライアント名が bedrock-runtime であること。boto3.client('converse') のような呼び出しは誤りで、そのようなサービスは存在しません。clientの生成方法やリトライの既定値といったSDK側の前提は、boto3のclientとresourceの違い・認証・リトライの実装で整理しています。
bedrock-runtimeクライアントとconverseメソッド
次のコードは system プロンプトと推論パラメータを付けて1回の応答を得る最小例です。additionalModelRequestFields で Claude 固有の top_k を渡しています。
import boto3
bedrock = boto3.client(service_name="bedrock-runtime", region_name="ap-northeast-1")
response = bedrock.converse(
modelId="apac.anthropic.claude-sonnet-4-5-20250929-v1:0",
system=[{"text": "あなたは簡潔に答える技術サポートです。"}],
messages=[
{"role": "user", "content": [{"text": "Converse APIとInvokeModelの違いを一言で。"}]}
],
inferenceConfig={"maxTokens": 512, "temperature": 0.3},
additionalModelRequestFields={"top_k": 200},
)
print(response["output"]["message"]["content"][0]["text"])
print(response["stopReason"], response["usage"]["totalTokens"])
modelId には推論プロファイルID(先頭に apac. や global. が付く形)を指定します。多くの新しい Claude モデルはオンデマンドの素のモデルIDだと ValidationException になり、クロスリージョン推論プロファイルの指定が必須です。利用可能なIDはリージョンで変わるため、最新はマネジメントコンソールの「Cross-region inference」で確認してください。
マルチターン会話のstateless維持
Bedrock は会話状態を保存しません。データはその応答の生成にだけ使われ、サーバー側に残りません。したがって会話の継続はアプリ側の責任です。前ターンで返った output.message を messages にそのまま追記し、次のユーザー発話を足して再送します。role の交互(user → assistant → user …)が崩れるとエラーになるため、モデル応答を加工せず丸ごと積むのが安全です。
messages = [{"role": "user", "content": [{"text": "AWSの生成AIサービスは?"}]}]
resp = bedrock.converse(modelId=MODEL_ID, messages=messages)
# 返答をそのまま履歴に追加してから次の発話を足す
messages.append(resp["output"]["message"])
messages.append({"role": "user", "content": [{"text": "その中でチャット向けはどれ?"}]})
resp = bedrock.converse(modelId=MODEL_ID, messages=messages)
履歴が長くなると入力トークン=コストが増えます。固定の長い system プロンプトやツール定義を繰り返し送るなら、Amazon Bedrockのプロンプトキャッシュで cachePoint を挟むと入力トークンを節約できます。
IAM権限の設定:「bedrock:Converse」というアクションは存在しない
bedrock:converse や bedrock.converse は実際によく検索される表記ですが、ここには見落とされがちな事実があります。Converse 専用のIAMアクションは無いのです。認可は既存の推論アクションで行われます。
| API操作 | 必要なIAMアクション |
|---|---|
| Converse | bedrock:InvokeModel |
| ConverseStream | bedrock:InvokeModelWithResponseStream |
そのため、ポリシーに bedrock:Converse と書いても効果はなく、逆に bedrock:InvokeModel を付け忘れると AccessDeniedException になります。推論プロファイル経由で呼ぶ場合は、プロファイルARNと、その背後の基盤モデルARNの両方に許可が要る点にも注意してください。なお、ClaudeをMessages API(bedrock-mantle)経由で呼ぶ場合の認可は bedrock-mantle:CreateInference という別の名前空間になり、経路ごとの違いはBedrock Claudeの使い方|Messages API・InvokeModel・Claude Codeの設定で比べています。最小構成の例は次のとおりです。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/*",
"arn:aws:bedrock:*:123456789012:inference-profile/*"
]
}]
}
特定モデルへのアクセスを止めたいときは、この2つのアクションを Deny すれば、Converse・ConverseStream 経由の呼び出しも同時に遮断できます(基盤アクションを塞ぐと上位の Converse も塞がるため)。
ConverseStreamでストリーミング応答を実装する
「converse stream」「conversestream」系のクエリが示すとおり、逐次表示の需要は高い領域です。チャットUIで生成テキストを1文字ずつ流したいなら converse_stream を使います。リクエスト本文は converse と同じで、違いは応答の受け取り方だけです。
converse_streamのイベント列を処理する
応答は stream フィールドのイベント列として届きます。順序は messageStart →(各コンテンツブロックごとに contentBlockStart・contentBlockDelta・contentBlockStop)→ messageStop(stopReason を運ぶ)→ metadata(usage と metrics)です。テキストは contentBlockDelta の delta.text に少しずつ入ります。
resp = bedrock.converse_stream(
modelId=MODEL_ID,
messages=[{"role": "user", "content": [{"text": "Bedrockを3行で紹介して。"}]}],
)
for event in resp["stream"]:
if "contentBlockDelta" in event:
print(event["contentBlockDelta"]["delta"].get("text", ""), end="")
elif "messageStop" in event:
print("\n停止理由:", event["messageStop"]["stopReason"])
elif "metadata" in event:
print("トークン:", event["metadata"]["usage"]["totalTokens"])
ストリーミング時のTool Useは部分JSONで届く
ストリーミングとツール利用を組み合わせる場合、注意点があります。ツール呼び出しの入力(toolUse の input)は完成した JSON ではなく、contentBlockDelta の中に分割された部分文字列として順次届く点です。各 contentBlockIndex ごとに delta.toolUse.input を連結し、contentBlockStop を受け取ってから json.loads でパースします。途中の断片を単体でパースしようとすると壊れるため、ブロック単位でバッファリングするのが定石です。
tool_input = ""
for event in resp["stream"]:
if "contentBlockDelta" in event:
delta = event["contentBlockDelta"]["delta"]
if "toolUse" in delta:
tool_input += delta["toolUse"]["input"] # 部分JSONを連結
elif "contentBlockStop" in event:
if tool_input:
args = json.loads(tool_input) # ブロック確定後にパース
tool_input = ""
逐次表示が不要ならツール利用は非ストリーミングの converse で書いた方が、前掲の実行ループが素直になります。
Tool Use(外部ツール連携)をConverseで実装する
Tool Use は、天気APIやデータベース照会などモデル単体では答えられない情報を取りに行かせる仕組みです。Bedrock ではモデルがツールを直接実行するわけではなく、モデルは「このツールをこの引数で呼びたい」と要求するだけで、実行はアプリ側(クライアントサイド)が担います。
toolConfigでツールを定義する
ツールは toolConfig.tools[].toolSpec に、名前・説明・入力スキーマ(inputSchema.json)で定義します。入力スキーマは JSON Schema で書き、required で必須引数を指定します。toolChoice でモデルの選択方針を auto(任せる)・any(必ず何かのツール)・tool(特定ツール指定)から選べます。
tool_config = {
"tools": [{
"toolSpec": {
"name": "get_weather",
"description": "指定都市の現在の天気を返す",
"inputSchema": {"json": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名。例: Tokyo"}
},
"required": ["city"]
}}
}
}],
"toolChoice": {"auto": {}}
}
stopReason=tool_useを起点にした実行ループ
流れは決定論的です。モデルが stopReason を tool_use で返したら、content 内の toolUse ブロック(toolUseId・name・input)を取り出してツールを実行し、結果を toolResult(同じ toolUseId を付ける)として返して再度 converse を呼びます。ここで1回の応答に複数の toolUse が含まれることがある(Claude は並列でツールを要求する)ため、各結果を別々の user メッセージで積むと ValidationException になります。複数の toolResult は1つの user メッセージにまとめて返すのが正解です。モデルがツール結果を見てさらにツールを要求する多段のケースに備え、tool_use でなくなるまで while でループさせます。失敗時は status に error を付けるとモデルが再試行や別応答を判断できます。
def run(bedrock, user_text):
messages = [{"role": "user", "content": [{"text": user_text}]}]
resp = bedrock.converse(modelId=MODEL_ID, messages=messages, toolConfig=tool_config)
messages.append(resp["output"]["message"])
# tool_useが続く限りツールを実行し結果を返し続ける
while resp["stopReason"] == "tool_use":
tool_results = []
for block in resp["output"]["message"]["content"]:
if "toolUse" not in block:
continue
tu = block["toolUse"]
try:
data = get_weather(tu["input"]["city"]) # 自作の実処理
tool_results.append({"toolUseId": tu["toolUseId"],
"content": [{"json": data}]})
except Exception as e:
tool_results.append({"toolUseId": tu["toolUseId"],
"content": [{"text": str(e)}], "status": "error"})
# 複数ツールの結果は1つのuserメッセージにまとめて返す(別々に積むとValidationException)
messages.append({"role": "user",
"content": [{"toolResult": r} for r in tool_results]})
resp = bedrock.converse(modelId=MODEL_ID, messages=messages, toolConfig=tool_config)
messages.append(resp["output"]["message"])
return "".join(b["text"] for b in resp["output"]["message"]["content"] if "text" in b)
ツールの実行主体をアプリではなく Bedrock 側に寄せたい(Lambda 登録などで集中管理したい)場合は、サーバーサイド Tool Use を持つAmazon Bedrock AgentCoreの利用が選択肢になります。
実装時の判断と失敗パターン
最後に、公式手順どおりに書いても詰まりやすい点を、判断込みで挙げます。
- ValidationException(modelId):新しめの Claude をオンデマンドの素のIDで呼ぶと失敗する。
apac.やglobal.付きの推論プロファイルIDに変える。 - AccessDeniedException:IAMに
bedrock:InvokeModel/bedrock:InvokeModelWithResponseStreamが無い、またはモデルのアクセス有効化(コンソールの Model access)が未実施。 - 会話が壊れる:モデル応答を要約・整形して履歴に積むと
role交互やツールIDの対応が崩れる。output.messageは無加工で追記する。 - トークン肥大:全履歴を毎回送る仕様上、長い会話ほど入力コストが増える。古い発話の切り詰めやプロンプトキャッシュで抑える。
逆に Converse を使わない方がよい場面もあります。単発のバッチ推論で会話文脈もツールも不要なら、Converse の正規化は恩恵が薄く、料金や検索を組み合わせるならRAG(KendraとBedrockの組み合わせ)のような別構成が適します。「チャット的な多ターン対話」か「ツールを挟む」なら Converse、それ以外は用途に合わせて選ぶのが実務的な線引きです。
よくある質問
bedrock:converse というIAMアクションはありますか?
ありません。Converse は bedrock:InvokeModel、ConverseStream は bedrock:InvokeModelWithResponseStream で認可されます。ポリシーにはこの2つのアクションを記述してください。
ConverseとConverseStreamの違いは何ですか?
リクエスト本文は同一で、応答の返り方だけが違います。converse は生成完了後に一括で返し、converse_stream はイベント列(contentBlockDelta など)として逐次返します。UIで文字を流したいなら後者を使います。
boto3でConverseを呼ぶクライアント名は何ですか?
bedrock-runtime です(boto3.client(service_name="bedrock-runtime"))。converse と converse_stream メソッドが生えています。bedrock(管理系)や存在しない converse サービスと混同しないよう注意してください。
会話の履歴はどう保持しますか?
Bedrock は状態を持たないため、アプリ側で messages に全ターンを積みます。モデルの返答は output.message をそのまま追記し、次のユーザー発話を足して再送します。
Tool Useはどのモデルで使えますか?
Converse のツール利用は、ツール呼び出しに対応した複数モデル(Anthropic Claude、Amazon Nova、Cohere Command R など)で使えます。対応状況はモデルごとに異なるため、利用モデルの対応可否を公式のモデル一覧で確認してください。Converse の対応可否を含めた選定の考え方はAWS Bedrockのモデル選定|Claude・Nova・OpenAI・Gemmaの選び分けと切替判断で扱っています。