Testing Bedrock's New Feature: Structured Outputs to Enforce Strict JSON Output
This page has been translated by machine translation. View original
On February 4, 2026, Structured Outputs became generally available (GA) on Amazon Bedrock.
Until now, even when requesting JSON output, handling symbol intrusions and format issues was essential. With this update, you can now strictly control model output by specifying a JSON schema in advance.
In this article, I'll share my findings on Claude 4.5 Sonnet's behavior and implementation differences across APIs using the Python SDK (boto3).
Test Environment
The latest SDK was required to use Structured Outputs.
I built a Docker container environment to use the latest version as of the time of writing (February 5, 2026).
- Base Image: python:3.12-slim
- Python: 3.12.9
- boto3: 1.42.42
Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install latest version without using cache
RUN pip install --no-cache-dir boto3 --upgrade
COPY . .
# Check version and run
CMD pip show boto3 | grep Version && python structured_outputs_demo.py
Execution Commands
# Build
docker build -t bedrock-structured-demo .
# Run (passing credentials)
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 Confirmation Log
From the logs output at container startup, I confirmed that the target version was applied.
Version: 1.42.42
The inference profiles for Claude 4.5 that were available in the Tokyo region as of February 5, 2026 were as follows:
| Model | Inference Profile 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 |
In this article, I used Sonnet 4.5 (Global) for testing.
1. Testing with InvokeModel API
First, I tried invoke_model.
Instead of traditional prompt instructions, I specified the JSON structure to output using the output_config parameter.
Implementation Code
I provided CSV text containing "date, product, quantity, price, region" as input and received aggregation results in the specified JSON schema.
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"
# Input data
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 definition
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
}
# Execute request
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}"}],
# Specify Structured Outputs here
"output_config": {"format": {"type": "json_schema", "schema": schema}}
}),
)
result = json.loads(response["body"].read())
output_text = result["content"][0]["text"]
# Check if it can be parsed as 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}")
Execution Result
After running in the Docker environment, I obtained JSON-formatted results by extracting the text field from the response.
{
"total_sales": 743600,
"total_items": 36,
"by_region": [
{
"region": "東京",
"sales": 551400
},
{
"region": "大阪",
"sales": 59600
},
{
"region": "福岡",
"sales": 135000
}
],
"top_product": "ノートPC"
}
Verification Points
-
I confirmed that the structure with objects nested in the
by_regionarray and the types of each field (numeric/string) were output according to the defined schema. -
The response was returned as a pure JSON string, so no cleaning with regular expressions was needed when executing
json.loads()in subsequent processing. -
In this verification, I confirmed that
total_salesandtotal_itemswere correctly calculated based on the input CSV data and mapped to the schema fields.
2. Testing with Converse API
Next, I checked how to use the higher-abstraction converse API.
In this case, the parameter position is different, and textFormat within outputConfig is used.
Also, note that a name field is required in the jsonSchema.
Implementation Code (Excerpt)
# Schema definition (using 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), # Must be passed as a string
"name": "sentiment_analysis", # Required
"description": "感情分析結果"
}
}
}
},
)
output = json.loads(response["output"]["message"]["content"][0]["text"])
print(json.dumps(output, indent=2, ensure_ascii=False))
Execution Result
{
"sentiment": "positive"
}
I confirmed that the value specified in enum (positive) was strictly output.
3. strict Mode in Tool Use
The benefits of Structured Outputs can also be applied to Tool Use (Function Calling).
By adding strict: true to the tool definition, I found that the model can be forced to follow the defined schema when generating tool arguments.
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": {}},
}
I confirmed that schema violations (type mismatches or inclusion of undefined fields) do not occur in tool argument generation based on the input prompt.
Summary
With Amazon Bedrock's Structured Outputs, it has become easier to receive LLM output results in JSON and implement API integrations.
Also, by utilizing enum and required, we can expect to create outputs with less variation.
At present, it's important to be aware of differences in implementation and parameter structures across APIs like InvokeModel and Converse, but this is a powerful tool when handling JSON output with LLMs.