Bedrockの新機能 Structured Outputs (構造化出力)でJSON出力を厳格化してみた

Bedrockの新機能 Structured Outputs (構造化出力)でJSON出力を厳格化してみた

Amazon BedrockのStructured Outputs(構造化出力)により、JSONスキーマを事前指定した厳格な出力制御が可能になりました。本記事では Claude 4.5 Sonnet を用い、InvokeModel、Converse、Tool Use の各APIにおける実装を紹介します。
2026.02.05

2026年2月4日、Amazon BedrockにてStructured Outputs(構造化出力)が一般提供(GA)開始されました。

https://aws.amazon.com/jp/about-aws/whats-new/2026/02/structured-outputs-available-amazon-bedrock/

これまではJSON出力を指示しても、記号の混入や形式不備への対応が不可欠でした。今回のアップデートにより、JSONスキーマを事前指定することで、モデルの出力を厳格に制御できるようになりました。

今回、Python SDK (boto3) を用いて、Claude 4.5 Sonnetでの挙動と、APIごとの実装差異について確認する機会がありましたので、紹介します。

検証環境

Structured Outputsを利用するには最新のSDKが必要でした。
執筆時点(2026年2月5日)の最新版を利用するため、Dockerコンテナで検証環境を構築しました。

  • ベースイメージ: python:3.12-slim
  • Python: 3.12.9
  • boto3: 1.42.42

Dockerfile

FROM python:3.12-slim

WORKDIR /app

# キャッシュを使わず最新版をインストール
RUN pip install --no-cache-dir boto3 --upgrade

COPY . .

# バージョン確認と実行
CMD pip show boto3 | grep Version && python structured_outputs_demo.py

実行コマンド

# ビルド
docker build -t bedrock-structured-demo .

# 実行(クレデンシャルを渡す)
docker run --rm \
  -e AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY \
  -e AWS_SESSION_TOKEN \
  -e AWS_DEFAULT_REGION=ap-northeast-1 \
  bedrock-structured-demo

バージョン確認ログ

コンテナ起動時に出力されたログより、対象バージョンが適用されていることを確認しました。

Version: 1.42.42

東京リージョンで動作確認できたClaude 4.5の推論プロファイル、2026年2月5日の記事執筆時点では以下の通りでした。

モデル 推論プロファイルID
Haiku 4.5 (Global) global.anthropic.claude-haiku-4-5-20251001-v1:0
Haiku 4.5 (JP) jp.anthropic.claude-haiku-4-5-20251001-v1:0
Sonnet 4.5 (Global) global.anthropic.claude-sonnet-4-5-20250929-v1:0
Sonnet 4.5 (JP) jp.anthropic.claude-sonnet-4-5-20250929-v1:0
Opus 4.5 (Global) global.anthropic.claude-opus-4-5-20251101-v1:0

本記事では、この中から Sonnet 4.5 (Global) を使用して検証を行いました。

1. InvokeModel APIでの検証

まず invoke_model を試しました。

従来のプロンプト指示ではなく、output_config パラメータで出力するJSON構造を指定しました。

実装コード

入力として「日付、商品、数量、単価、地域」を含むCSVテキストを与え、集計結果を指定したJSONスキーマで受け取ります。

import boto3
import json
import os

bedrock = boto3.client("bedrock-runtime", region_name="ap-northeast-1")
MODEL_ID = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"

# 入力データ
csv_input = """date,product,quantity,price,region
2026-01-15,ノートPC,5,89800,東京
2026-01-15,マウス,20,2980,大阪
2026-01-16,キーボード,8,12800,東京
2026-01-16,モニター,3,45000,福岡"""

# JSON Schema定義
schema = {
    "type": "object",
    "properties": {
        "total_sales": {"type": "integer", "description": "総売上金額"},
        "total_items": {"type": "integer", "description": "総販売数"},
        "by_region": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "region": {"type": "string"},
                    "sales": {"type": "integer"}
                },
                "required": ["region", "sales"],
                "additionalProperties": False
            }
        },
        "top_product": {"type": "string", "description": "最も売上が高い商品"}
    },
    "required": ["total_sales", "total_items", "by_region", "top_product"],
    "additionalProperties": False
}

# リクエスト実行
try:
    response = bedrock.invoke_model(
        modelId=MODEL_ID,
        body=json.dumps({
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": 1024,
            "messages": [{"role": "user", "content": f"以下のCSVデータを集計してください:\n\n{csv_input}"}],
            # ここでStructured Outputsを指定
            "output_config": {"format": {"type": "json_schema", "schema": schema}}
        }),
    )

    result = json.loads(response["body"].read())
    output_text = result["content"][0]["text"]

    # JSONとしてパースできるか確認
    output_json = json.loads(output_text)
    print(json.dumps(output_json, indent=2, ensure_ascii=False))

except Exception as e:
    print(f"Error: {e}")

実行結果

上記のDocker環境で実行後、レスポンスから text フィールドを抽出する事で、JSON形式の結果が得られました。

{
  "total_sales": 743600,
  "total_items": 36,
  "by_region": [
    {
      "region": "東京",
      "sales": 551400
    },
    {
      "region": "大阪",
      "sales": 59600
    },
    {
      "region": "福岡",
      "sales": 135000
    }
  ],
  "top_product": "ノートPC"
}

検証での確認ポイント

  • by_region 配列の中にオブジェクトがネストされる構造や、各フィールドの型(数値/文字列)が、定義したスキーマ通りに出力されていることを確認しました。

  • レスポンスは純粋なJSON文字列として返却されたため、後続の処理で json.loads() を実行する際も正規表現などによるクリーニングが不要でした。

  • 今回の確認では、入力CSVデータに基づき、total_sales(総売上)や total_items(総数)が正しく計算され、スキーマのフィールドにマッピングされていることを確認できました。

2. Converse APIでの検証

次に、より抽象度の高い converse APIを使用する場合の記述方法を確認しました。
こちらではパラメータ位置が異なり、outputConfig 内の textFormat を使用しました。

また、jsonSchema 内に name フィールドが必須となる点に注意が必要でした。

実装コード(抜粋)

# スキーマ定義(enumを使用)
schema_sentiment = {
    "type": "object",
    "properties": {
        "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]}
    },
    "required": ["sentiment"],
    "additionalProperties": False
}

response = bedrock.converse(
    modelId=MODEL_ID,
    messages=[{"role": "user", "content": [{"text": "このサービスは本当に素晴らしい!使いやすくて感動しました。"}]}],
    outputConfig={
        "textFormat": {
            "type": "json_schema",
            "structure": {
                "jsonSchema": {
                    "schema": json.dumps(schema_sentiment), # 文字列として渡す必要あり
                    "name": "sentiment_analysis",          # 必須
                    "description": "感情分析結果"
                }
            }
        }
    },
)

output = json.loads(response["output"]["message"]["content"][0]["text"])
print(json.dumps(output, indent=2, ensure_ascii=False))

実行結果

{
  "sentiment": "positive"
}

enum で指定した値(positive)が厳格に出力されていることを確認しました。

3. Tool Useにおける strict モード

Structured Outputsの恩恵は、Tool Use(Function Calling)にも適用可能です。
ツール定義に strict: true を追加することで、モデルがツール引数を生成する際、定義したスキーマに強制的に従わせることができました。

tool_config = {
    "tools": [{
        "toolSpec": {
            "name": "search_products",
            "description": "商品を検索する",
            "inputSchema": {
                "json": {
                    "type": "object",
                    "properties": {
                        "category": {"type": "string", "enum": ["electronics", "books"]},
                        "price_limit": {"type": "integer"}
                    },
                    "required": ["category", "price_limit"],
                    "additionalProperties": False
                }
            }
        }
    }],
    "toolChoice": {"auto": {}}, 
}

入力されたプロンプトに基づき、Tool Useの引数生成においてもスキーマ違反(型不一致や未定義フィールドの混入)が発生しないことを確認できました。

まとめ

Amazon BedrockのStructured Outputsにより、LLM出力結果をJSONで受け取り、API連携などの実装が容易になりました。

また、enumrequired を活用することで、揺れの少ない出力の作成も期待できるようになりました。

現時点で、InvokeModel、Converse とAPI毎の実装、パラメータ構造の違いなどがあるため留意が必要ですが、LLMでJSON出力を扱う場合に、有力な手段として活用ください。

この記事をシェアする

FacebookHatena blogX

関連記事