生成AIの出力をシステムで扱う場合、自然な文章よりも、決められた項目を持つJSONが必要になることがあります。しかし、プロンプトに「JSONで返してください」と書くだけでは、JSONの前後に説明文やコードフェンスが付く、必須項目が欠ける、想定と異なる型が返るといった問題が残ります。
この問題に対して、これまではプロンプトの工夫やAssistant Prefillで出力を誘導してきました。2026年2月にはAmazon BedrockでStructured Outputsが一般提供され、JSON Schemaに準拠した出力を生成段階で保証できるようになりました(AWS「Structured outputs now available in Amazon Bedrock」)。
Structured Outputsの基本概念と初歩的な実装は、以下の記事で紹介しています。
本記事では、Amazon BedrockでJSONを得る方法の変遷について、次の4つの実装例と実行結果を比較します。
3と4の方法は、Amazon BedrockのStructured Outputsを構成する2つの仕組みです。単純な優劣ではなく、保証する対象が異なります。1と2は方式の変遷を理解するための比較対象であり、構造化出力を必要とする新規実装には推奨しません。まずStructured Outputsに対応するモデル、API、エンドポイントを選定することが基本方針です。
プロンプトとAssistant Prefillは、モデルにJSON形式での出力を促す方法です。Structured Outputsは、JSON Schemaに沿うよう生成結果そのものを制約します。Structured Outputsには、最終回答に使うJSON Schema出力形式と、ツール入力に使うstrict tool useがあります。
プロンプトでJSONを依頼
↓
Assistant PrefillでJSONの開始位置を誘導
↓
Structured Outputsでスキーマ準拠を保証
├─ JSON Schema出力形式:最終回答を保証
└─ strict tool use:ツール入力を保証
| 方法 | JSON Schemaによる保証 | 対象 | 位置付け |
|---|---|---|---|
| プロンプトだけ | なし | 最終回答 | 比較・検証用。構造化出力には原則として使わない |
| Assistant Prefill | なし | 最終回答の書き始め | 既存実装向け。新規実装には使わない |
| JSON Schema出力形式 | あり(正常終了時) | 最終回答 | 情報抽出、分類、APIレスポンスの第一候補 |
| strict tool use | あり(正常終了時) | ツール名と入力 | 関数呼び出し、エージェント、外部API連携 |
ここでいう「保証あり」は、正常終了した出力がJSON Schemaに準拠するよう制約されるという意味です。リクエストが常に有効なJSONで完了することを100%保証するものではなく、最大トークン数への到達や、同じ内容を繰り返す出力ループなどによってJSONが未完了になる可能性があります。出力ループの実例と、トークン上限、再試行、監視による対策は、以下の記事で紹介しています。
Amazon Bedrockの公式ドキュメントでも、JSON Schema出力形式とstrict tool useは「2つの補完的な仕組み」と説明されています。同じリクエストで併用することも可能です。

以下では、Claude Haiku 4.5に同じ問い合わせを送り、4つの方法を比較します。
最初は、プロンプトに項目、候補値、出力形式を文章で記載する方法です。Converse APIへ通常のメッセージを送り、モデルが返したテキストをそのまま表示します。
import os
import boto3
from dotenv import load_dotenv
PROMPT = """
次の問い合わせをJSONに変換してください。説明文は付けないでください。
項目:
- category: 障害、請求、契約、その他のいずれか
- priority: high、medium、lowのいずれか
- summary: 問い合わせ内容の要約
問い合わせ:
今朝から管理画面へログインできません。
業務に影響するため、至急確認をお願いします。
"""
def main() -> None:
load_dotenv()
client = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"])
response = client.converse(
modelId=os.environ["BEDROCK_MODEL_ID"],
messages=[{"role": "user", "content": [{"text": PROMPT}]}],
inferenceConfig={"maxTokens": 500, "temperature": 0},
)
print(response["output"]["message"]["content"][0]["text"])
if __name__ == "__main__":
main()
今回の実行では項目と値は意図どおりでしたが、「説明文は付けない」と指示してもJSONがMarkdownのコードブロックで囲まれました。

この出力をjson.loads()へ直接渡すと解析に失敗します。コードフェンスを取り除けば解析できますが、別の入力や実行では説明文、項目不足、候補外の値が返る可能性もあります。プロンプトは形式を「依頼」しているだけであり、JSONの構文やスキーマへの準拠を保証していません。
そのため、構造化データを後続処理へ渡す用途では、この方式を新規採用しないことを推奨します。まずJSON Schema出力形式に対応するモデルを選定し、利用するAPIやエンドポイントも含めてStructured Outputsを利用できる構成へ変更できないかを検討します。
Assistant Prefillは、Structured Outputs登場前の既存実装を理解するために紹介する方式であり、新規実装には推奨しません。モデルの回答欄を途中まで書いた状態にして、その続きを生成させる方法です。次の例では、assistantメッセージとして、コードフェンスと{をあらかじめ指定しています。さらにコードフェンスの終了記号を停止文字列として設定し、JSONの後に文章が続くことを抑えます。
import json
import os
import boto3
from dotenv import load_dotenv
PROMPT = """
次の問い合わせを、category、priority、summaryを持つJSONに変換してください。
categoryは障害、請求、契約、その他のいずれか、
priorityはhigh、medium、lowのいずれかにしてください。
問い合わせ:
今朝から管理画面へログインできません。
業務に影響するため、至急確認をお願いします。
"""
def main() -> None:
load_dotenv()
client = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"])
body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 500,
"temperature": 0,
"messages": [
{"role": "user", "content": PROMPT},
{"role": "assistant", "content": "```json\n{"},
],
"stop_sequences": ["```"],
}
response = client.invoke_model(
modelId=os.environ["BEDROCK_MODEL_ID"],
body=json.dumps(body),
)
result = json.loads(response["body"].read())
json_text = "{" + result["content"][0]["text"]
print("stop_reason:", result["stop_reason"], "\n")
print(json.dumps(json.loads(json_text), ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
実行結果ではstop_reasonがstop_sequenceとなり、コードフェンスより前で生成が停止しました。事前に与えた{を連結すると、JSONとして解析できます。

この結果だけを見ると、Assistant Prefillでも十分に安定しているように見えるかもしれません。しかし、今回の例は、項目数が少なく入力も短い単純なケースを、指示追従性能の高い現在のモデルで実行した一例にすぎません。高性能なモデルでは、プロンプトだけ、またはAssistant Prefillでも、同様の入力に対して意図どおりのJSONが得られる場合があります。
重要なのは、「今回JSONとして解析できたこと」と「正常終了した出力のスキーマ準拠が仕組みとして保証されること」は別だという点です。入力の長さや曖昧さ、項目数、ネストの深さ、候補値、モデルの更新などによって結果は変化し得ます。単発の成功例だけを根拠に本番採用を判断せず、実データを想定した正常系、境界値、異常系で反復評価する必要があります。
Assistant PrefillもJSON Schemaによる保証ではありません。評価時に高い成功率が得られた場合でも、JSONの構文が崩れる、必須項目が欠ける、型が異なる、候補外の値が返るといった可能性に備え、受信後のパース、バリデーション、再試行が必要です。Structured Outputsとの違いは、この実行結果が成功したかどうかではなく、生成時にスキーマ準拠を制約できるかどうかにあります。
ただし、Assistant Prefillは、すべての現行Claudeモデルで利用できるわけではありません。AnthropicのMessages APIドキュメントによると、Assistant PrefillはClaude 4.6以降でサポートされず、指定するとエラーになります。
新規実装では、まずStructured Outputsに対応するモデルへ変更できるかを検討してください。モデルの変更が難しい既存システムでも、Assistant Prefillを継続利用する前に、APIやエンドポイントの変更によってStructured Outputsへ移行できないかを確認します。いずれも変更できない場合に限り、JSONの破損やスキーマ違反が起こり得ることを受け入れ、受信後のパース、バリデーション、再試行、監視を組み込んだ上で暫定的に利用します。
botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the InvokeModel operation: This model does not support assistant message prefill. The conversation must end with a user message.
ここからがAmazon BedrockのStructured Outputsです。JSON Schema出力形式では、モデルの最終回答が指定したスキーマに準拠するよう、生成可能なトークンが制約されます。
今回は、同じ問い合わせを使ってPydanticモデルからJSON Schemaを生成し、実行結果を確認します。
import json
import os
from typing import Literal
import boto3
from dotenv import load_dotenv
from pydantic import BaseModel, ConfigDict, Field
class SupportTicket(BaseModel):
model_config = ConfigDict(extra="forbid")
category: Literal["障害", "請求", "契約", "その他"] = Field(
description="問い合わせの分類"
)
priority: Literal["high", "medium", "low"] = Field(description="対応優先度")
summary: str = Field(description="問い合わせ内容の要約")
def main() -> None:
load_dotenv()
client = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"])
prompt = (
"次の問い合わせを分類・要約してください。\n\n"
"今朝から管理画面へログインできません。"
"業務に影響するため、至急確認をお願いします。"
)
response = client.converse(
modelId=os.environ["BEDROCK_MODEL_ID"],
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 500},
outputConfig={
"textFormat": {
"type": "json_schema",
"structure": {
"jsonSchema": {
"schema": json.dumps(
SupportTicket.model_json_schema(), ensure_ascii=False
),
"name": "support_ticket",
}
},
}
},
)
if response["stopReason"] != "end_turn":
raise RuntimeError(f"生成が正常終了しませんでした: {response['stopReason']}")
output_text = response["output"]["message"]["content"][0]["text"]
ticket = SupportTicket.model_validate_json(output_text)
print(ticket.model_dump_json(indent=2))
if __name__ == "__main__":
main()
実行結果は、コードフェンスを含まないJSONです。categoryとpriorityはLiteralで指定した候補に限定され、3項目がそろっています。

実装の要点は、次の3点です。
model_json_schema()はクラスからスキーマを生成するメソッドです。一方、最後のticket.model_dump_json()は、検証済みのインスタンスをJSON文字列に変換します。用途が異なるため、リクエストではmodel_json_schema()、出力時にはmodel_dump_json()を使用しています。
Structured Outputsが保証するのは、生成が正常終了した場合に、出力が対応するJSON Schemaに構造的に準拠することです。分類や要約の内容が業務上正しいことまでは保証しないため、内容の評価や業務ルールの検証は別途必要です。また、拒否、最大トークン数への到達、出力ループなどではJSONが未完了になり、スキーマに準拠しない場合があります。そのため、AWSはすべてのレスポンスでstopReasonを確認するよう案内しています(AWS Machine Learning Blog)。
Structured Outputsのもう一つの仕組みがstrict tool useです。JSON Schema出力形式がモデルの最終回答を保証するのに対し、strict tool useはモデルが生成するツール名と入力をツール定義に準拠させます。
次の例では、問い合わせを登録するcreate_support_ticketツールを定義します。実際の登録処理は実行せず、モデルが生成したtoolUseブロックの入力だけを確認します。
import os
from typing import Literal
import boto3
from dotenv import load_dotenv
from pydantic import BaseModel, ConfigDict, Field
class SupportTicket(BaseModel):
model_config = ConfigDict(extra="forbid")
category: Literal["障害", "請求", "契約", "その他"] = Field(
description="問い合わせの分類"
)
priority: Literal["high", "medium", "low"] = Field(description="対応優先度")
summary: str = Field(description="問い合わせ内容の要約")
def main() -> None:
load_dotenv()
client = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"])
prompt = (
"次の問い合わせをcreate_support_ticketツールで登録してください。\n\n"
"今朝から管理画面へログインできません。"
"業務に影響するため、至急確認をお願いします。"
)
response = client.converse(
modelId=os.environ["BEDROCK_MODEL_ID"],
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 500},
toolConfig={
"tools": [
{
"toolSpec": {
"name": "create_support_ticket",
"description": "問い合わせをサポート窓口へ登録する",
"inputSchema": {"json": SupportTicket.model_json_schema()},
"strict": True,
}
}
],
"toolChoice": {"any": {}},
},
)
tool_use = next(
block["toolUse"]
for block in response["output"]["message"]["content"]
if "toolUse" in block
)
print("tool:", tool_use["name"], "\n")
ticket = SupportTicket.model_validate(tool_use["input"])
print(ticket.model_dump_json(indent=2))
if __name__ == "__main__":
main()
実行結果ではcreate_support_ticketが選択され、入力として3項目が返りました。

inputSchema.jsonにPydanticのJSON Schemaを直接指定し、strictをTrueにする部分が中心です。toolChoiceのanyは、定義したツールのいずれかを必ず呼び出す指定です。
ここで保証されるのは、ツール呼び出しに含まれる名前と入力です。外部APIやデータベースへの登録が自動的に実行されるわけではありません。アプリケーション側でtoolUseを受け取り、権限確認や業務ルールの検証を行った上で、実際の関数を呼び出します。
最終回答をJSONとして受け取りたい場合は、JSON Schema出力形式が第一候補です。情報抽出、分類、APIレスポンスなど幅広い用途にそのまま利用できるため、一般的な「JSONを安定して返してほしい」という要件に最も合います。
ただし、JSON Schema出力形式はすべてのモデルで利用できるわけではありません。AWSの公式ドキュメントは、対応状況をモデルごとに確認するよう案内しています。たとえばClaude向けの公式ドキュメントでは、執筆時点のStructured Outputs対応モデルがClaude Sonnet 4.5、Claude Haiku 4.5、Claude Opus 4.5、Claude Opus 4.6に限定されています。
また、対応モデルであっても、bedrock-mantleエンドポイントのAnthropic Messages APIではStructured Outputsを利用できません。Claudeで利用する場合は、bedrock-runtimeエンドポイントのConverse APIまたはInvokeModel APIを使用します。
現在採用しているモデルがJSON Schema出力形式に対応していない場合は、代替手段としてTool useを検討する前に、Structured Outputs対応モデルへの変更を検討します。モデルを変更できない場合も、利用するAPIやエンドポイントを変更してJSON Schema出力形式を利用できないかを確認します。
モデル、API、エンドポイントのいずれも変更できず、そのモデルがTool useには対応している場合に限り、構造化結果を受け取るためのツールを定義し、toolChoiceで呼び出しを必須にする方法が代替案になります。このケースでは実際に外部機能を呼び出す必要がないにもかかわらず、ツール入力を構造化結果として取り出す実装になるため、処理の意図が分かりにくくなります。
一方、モデルに外部機能を選択させ、その引数を生成させる場合はstrict tool useが本来の用途に適しています。なお、JSON Schema出力形式に非対応のモデルが、strict tool useにも対応しているとは限りません。通常のTool useしか利用できない場合はスキーマ準拠が保証されないため、受信後のバリデーション、再試行、監視が必要です。こうした代替方式はリスクを把握し、許容できると判断した場合に限って暫定的に採用し、Structured Outputs対応構成への移行計画も併せて用意します。
| 状況 | 方針 |
|---|---|
| 最終回答を決められたJSONで受け取りたい | JSON Schema出力形式を第一候補にする |
| 現在のモデルや実行経路がJSON Schema出力形式に対応していない | 対応モデル、API、エンドポイントへの変更を検討する |
| ツールや関数へ渡す引数を保証したい | strict tool useを選ぶ |
| 最終回答とツール入力の両方を保証したい | 2つを併用する |
| モデルを変更できず、通常のTool useのみ利用できる | 原則非推奨。リスクを許容できる場合のみ受信後の検証を行う |
| JSON Schema出力形式とTool useの両方に対応していない | 原則として採用しない。継続が不可避な場合のみ暫定利用する |
Structured Outputsで使用できるのはJSON Schema Draft 2020-12のサブセットです。たとえば文字列のminLength、maxLength、数値のminimum、maximum、再帰スキーマ、外部$refはサポートされません。オブジェクトのadditionalPropertiesはfalseにする必要があります。新しいスキーマは初回に文法がコンパイルされ、完了まで数分かかる場合があります。正常にコンパイルされた文法は、最初のアクセスから24時間キャッシュされます。対応状況は更新されるため、実装時はAmazon BedrockのStructured Outputs公式ドキュメントをご確認ください。
Amazon BedrockでJSON出力を安定させる方法は、プロンプトによる依頼やAssistant Prefillによる誘導から、JSON Schemaに準拠するStructured Outputsへ進化しました。
まず利用するモデルがJSON Schema出力形式に対応しているかを確認し、非対応であればモデル、API、エンドポイントの変更を検討します。プロンプト、Assistant Prefill、通常のTool useによる代替は標準的な選択肢とせず、モデル、API、エンドポイントを変更できず、かつリスクを許容できる場合に限って、検証や監視を伴う暫定策として利用します。
Tech Funでは、お客様のフェーズに合わせ、生成AI活用に向けた様々な支援をご提供しています。
生成AIに限らず、Web・業務システム開発やインフラ設計など、技術領域を問わずご相談を承っています。「何から始めれば良いか分からない」という段階でも構いませんので、ぜひお気軽にお問い合わせください。