AWS Builder Center

Getting Started with Claude Fable 5 on Amazon Bedrock — A Practitioner's Guide

Handson guide to get started with the newly launched Claude Fable 5 model on Amazon Bedrock

TL;DR

Claude Fable 5 dropped on June 9, 2026, and it's available on Amazon Bedrock the same day. It's essentially the Mythos Preview (Project Glasswing) with built-in safeguards — same 1M context window, same 128K max output, but at 40% of the Mythos Preview price. This post covers the full setup ritual and gives you production-ready code patterns you can lift straight into your projects.
Note: This is a personal blog post, please refer to official Anthropic and Amazon Bedrock documentation

The Model Landscape After Fable 5

Here's how the Claude family looks now:
Fable 5Opus 4.8Sonnet 4Haiku 4.5
ClassMythosOpusSonnetHaiku
Context1M200K200K200K
Max Output128K32K64K8K
Input $/M$10$5$3$0.80
Output $/M$50$25$15$4
ReasoningAlways-onOptionalOptionalNo
TemperatureFixed 1.0AdjustableAdjustableAdjustable
The sweet spot: use Fable 5 when you need sustained multi-step reasoning over large contexts — architectural reviews, complex refactoring, deep research synthesis. For everything else, Sonnet 4 or Haiku remain more cost-effective.

Enabling Claude Fable 5 on Bedrock — Data Retention Opt-in

To invoke anthropic.claude-fable-5, you must first opt in to data sharing via the Data Retention API. There's no console UI — API only.
Prerequisites:
  • AWS temporary credentials exported as env vars (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN)
  • Admin role on the target Bedrock account
  • curl 7.75+ (for --aws-sigv4)
Export credentials from your configured CLI profile:
If your AWS CLI is already configured (SSO, named profile, or default), just export the resolved credentials as env vars:
1
2
3
4
5
# Export temp credentials from your configured CLI into env vars
eval $(aws configure export-credentials --format env)

# Verify identity
aws sts get-caller-identity
💡 Using a named profile? Add --profile your-profile. Using SSO? Run aws sso login first if your session has expired.
Enable data sharing:
1
2
3
4
5
6
7
curl -s -o /dev/null -w "%{http_code}" \
-X PUT https://bedrock.us-east-1.amazonaws.com/data-retention \
--aws-sigv4 "aws:amz:us-east-1:bedrock" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "mode": "provider_data_share" }'
200 = ✅ success. You can now invoke Fable 5.
⚠️ What this does: Enables 30-day input/output retention. Data leaves the AWS boundary and is shared with Anthropic for abuse detection. Configurable at org, account, or project level. More info: AWS Blog 
Verify the setting:
1
2
3
4
5
curl -s \
-X GET https://bedrock.us-east-1.amazonaws.com/data-retention \
--aws-sigv4 "aws:amz:us-east-1:bedrock" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" | jq .
Expected output:
1
2
3
4
{
"mode": "provider_data_share",
"updatedAt": "2026-06-09T23:46:41.595Z"
}

Model Agreement (One-Time)

Separate from data retention, you need to accept the model's EULA:
1
2
3
4
5
6
7
8
9
10
11
# Get the offer token
OFFER_TOKEN=$(aws bedrock list-foundation-model-agreement-offers \
--model-id anthropic.claude-fable-5 \
--region us-east-1 \
--query 'offers[0].offerToken' --output text)

# Accept it
aws bedrock create-foundation-model-agreement \
--model-id anthropic.claude-fable-5 \
--offer-token "$OFFER_TOKEN" \
--region us-east-1

Your First Invocation

Two model IDs to know:
  • anthropic.claude-fable-5 — for agreement/config operations
  • global.anthropic.claude-fable-5 — for inference (cross-region profile)
Terminal output listing the anthropic.claude-fable-5 foundation model and its us. and global. inference profiles, all ACTIVE
Confirming the model and its inference profiles actually exist before writing any code.

Quick CLI Smoke Test

1
2
3
4
5
6
aws bedrock-runtime converse \
--model-id "global.anthropic.claude-fable-5" \
--messages '[{"role":"user","content":[{"text":"What model are you? Reply in one sentence."}]}]' \
--inference-config '{"maxTokens":128}' \
--region us-east-1 \
--query 'output.message.content[?text].text' --output text
Terminal output of the converse CLI smoke test returning the sentence: I'm Claude, an AI assistant made by Anthropic.
The [?text] JMESPath filter skips the reasoning block and returns just the answer text.

Production Code Patterns

Pattern 1: Converse API with Full Response Parsing

The Converse API is the recommended path — it gives you unified access to Bedrock features like Guardrails and Knowledge Bases.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import boto3
from typing import Optional

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")

MODEL_ID = "global.anthropic.claude-fable-5"

def invoke_fable5(
prompt: str,
system: Optional[str] = None,
max_tokens: int = 4096,
) -> dict:
"""
Invoke Claude Fable 5 via Converse API with structured result parsing.

Returns dict with keys: text, reasoning, usage, latency_ms, stop_reason
"""

kwargs = {
"modelId": MODEL_ID,
"messages": [{"role": "user", "content": [{"text": prompt}]}],
"inferenceConfig": {"maxTokens": max_tokens},
}
if system:
kwargs["system"] = [{"text": system}]

response = bedrock.converse(**kwargs)

# Parse response blocks
text_parts = []
reasoning_parts = []
for block in response["output"]["message"]["content"]:
if "text" in block:
text_parts.append(block["text"])
elif "reasoningContent" in block:
rc = block["reasoningContent"]
if "reasoningText" in rc and rc["reasoningText"].get("text"):
reasoning_parts.append(rc["reasoningText"]["text"])

return {
"text": "\n".join(text_parts),
"reasoning": "\n".join(reasoning_parts) or None,
"usage": response["usage"],
"latency_ms": response["metrics"]["latencyMs"],
"stop_reason": response["stopReason"],
}

# Usage
result = invoke_fable5(
prompt="Compare event-driven vs choreography patterns for microservices. "
"Give concrete AWS service mappings for each.",
system="You are a principal-level AWS Solutions Architect. Be specific and opinionated."
)

print(result["text"])
print(f"\n⏱️ {result['latency_ms']}ms | "
f"📥 {result['usage']['inputTokens']} in | "
f"📤 {result['usage']['outputTokens']} out")
💡 On Bedrock, result["reasoning"] comes back None even though the model reasoned (and you were billed for those tokens). Reasoning is returned as an encrypted signature, not readable text — the block-scanning loop above just degrades to None. Don't write logic that depends on reading the chain-of-thought; that text is only available on Anthropic's first-party API.
Terminal output from Pattern 1 showing a parsed Converse response followed by a metrics line: 46264 ms, 70 tokens in, 2770 tokens out
Pattern 1 output: parsed text plus latency and token usage. Those 2,770 output tokens include the (invisible) reasoning tokens.

Pattern 2: Streaming with Backpressure

For long-running Fable 5 responses (it can output 128K tokens), streaming is essential:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import boto3
import sys
import time

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")

def stream_fable5(prompt: str, max_tokens: int = 16384) -> dict:
"""
Stream Fable 5 response with real-time output and metrics collection.
"""

response = bedrock.converse_stream(
modelId="global.anthropic.claude-fable-5",
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": max_tokens},
)

full_text = ""
reasoning_text = ""
token_count = 0
first_token_time = None
start_time = time.time()

for event in response["stream"]:
if "contentBlockStart" in event:
block_type = event["contentBlockStart"].get("start", {})
if "toolUse" in block_type:
pass # tool use block

elif "contentBlockDelta" in event:
delta = event["contentBlockDelta"]["delta"]
if "text" in delta:
chunk = delta["text"]
if first_token_time is None:
first_token_time = time.time()
full_text += chunk
sys.stdout.write(chunk)
sys.stdout.flush()
token_count += 1

elif "reasoningContent" in delta:
rc = delta["reasoningContent"]
if "text" in rc:
reasoning_text += rc["text"]

elif "metadata" in event:
usage = event["metadata"].get("usage", {})

elapsed = time.time() - start_time
ttft = (first_token_time - start_time) if first_token_time else None

print(f"\n\n--- Stream Stats ---")
print(f"Time to first token: {ttft:.2f}s" if ttft else "No text output")
print(f"Total time: {elapsed:.2f}s")
print(f"Input tokens: {usage.get('inputTokens', '?')}")
print(f"Output tokens: {usage.get('outputTokens', '?')}")

return {"text": full_text, "reasoning": reasoning_text, "usage": usage}

# Example: Complex analysis that benefits from streaming
stream_fable5(
"Write a detailed implementation plan for migrating a 500-table Oracle database "
"to Aurora PostgreSQL. Cover schema conversion, data migration, cutover strategy, "
"and rollback procedures. Include specific pgloader configurations."
)
Terminal output from Pattern 2 showing streamed text followed by stream stats: time to first token 11.00s, total time 123.85s, 84 input tokens, 8397 output tokens
Pattern 2: tokens stream to stdout in real time; the run closes with time-to-first-token and throughput stats.

Pattern 3: Vision — PDF & Diagram Analysis

Fable 5 has top-tier vision capabilities. Here's a pattern for analyzing uploaded documents:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import boto3
from pathlib import Path

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")

def analyze_document(
file_path: str,
question: str,
media_type: str = "image/png"
) -> str:
"""
Send an image or PDF page to Fable 5 for analysis.
Supports: image/png, image/jpeg, image/gif, image/webp,
application/pdf (first 20 pages)
"""

file_bytes = Path(file_path).read_bytes()

# For PDFs, use the document block type
if media_type == "application/pdf":
content_block = {
"document": {
"format": "pdf",
"name": Path(file_path).stem,
"source": {"bytes": file_bytes}
}
}
else:
content_block = {
"image": {
"format": media_type.split("/")[1],
"source": {"bytes": file_bytes}
}
}

response = bedrock.converse(
modelId="global.anthropic.claude-fable-5",
messages=[{
"role": "user",
"content": [
content_block,
{"text": question}
]
}],
inferenceConfig={"maxTokens": 8192}
)

# Fable 5 always returns reasoningContent first, then text — find the text block
for block in response["output"]["message"]["content"]:
if "text" in block:
return block["text"]
return "(No text in response — only reasoning content returned)"

# Example: Review an architecture diagram
review = analyze_document(
"ML-18699-1.png",
"You are reviewing this architecture for a Well-Architected Review. "
"Identify: (1) reliability risks, (2) cost optimization opportunities, "
"(3) security gaps. Format as a prioritized action list."
)
print(review)
Here's the capability in action. The input is a real, multi-section architecture diagram — no alt text, no structured metadata, just a PNG:
The input: an agentic equipment-repair assistant on Bedrock AgentCore. Fable 5 reads the boxes, arrows, and section labels straight from the image.
Fable 5 traces the data flow end-to-end and returns a prioritized Well-Architected review — catching the missing WAF, the absent agent guardrails, and the single-region / single-model dependency:
Terminal output of Fable 5's Well-Architected review: an architecture summary line, then Priority 1 Critical findings (no WAF or API throttling, no prompt-injection guardrails, single-region single-model dependency) and Priority 2 High findings (no DLQ/retry, OpenSearch Serverless cost floor)
The output: the model didn't just label the icons — it reasoned about reliability, security, and cost across the whole topology. (Truncated.)

Pattern 4: Tool Use — Building an Agentic Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import boto3
import json

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")

# Define tools the model can call
TOOLS = [
{
"toolSpec": {
"name": "get_cloudwatch_metrics",
"description": "Retrieve CloudWatch metrics for an AWS resource over a time period",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"namespace": {"type": "string", "description": "CloudWatch namespace (e.g., AWS/EC2, AWS/RDS)"},
"metric_name": {"type": "string", "description": "Metric name (e.g., CPUUtilization)"},
"resource_id": {"type": "string", "description": "Resource identifier"},
"hours_back": {"type": "integer", "description": "Hours of history to fetch", "default": 24}
},
"required": ["namespace", "metric_name", "resource_id"]
}
}
}
},
{
"toolSpec": {
"name": "create_jira_ticket",
"description": "Create a Jira ticket for tracking an action item",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"title": {"type": "string"},
"description": {"type": "string"},
"priority": {"type": "string", "enum": ["Critical", "High", "Medium", "Low"]},
"assignee": {"type": "string"}
},
"required": ["title", "description", "priority"]
}
}
}
}
]

def execute_tool(name: str, input_data: dict) -> str:
"""Stub: Replace with real implementations."""
if name == "get_cloudwatch_metrics":
return json.dumps({
"average": 78.5, "maximum": 95.2, "minimum": 12.1,
"datapoints": 288,
"period": f"Last {input_data.get('hours_back', 24)}h"
})
elif name == "create_jira_ticket":
return json.dumps({"ticket_id": "OPS-4521", "status": "created", "url": "https://jira.example.com/OPS-4521"})
return json.dumps({"error": f"Unknown tool: {name}"})

def agentic_loop(user_prompt: str, max_turns: int = 5) -> str:
"""
Run an agentic loop: model reasons → calls tools → incorporates results → repeats.
"""

messages = [{"role": "user", "content": [{"text": user_prompt}]}]

for turn in range(max_turns):
response = bedrock.converse(
modelId="global.anthropic.claude-fable-5",
messages=messages,
toolConfig={"tools": TOOLS},
inferenceConfig={"maxTokens": 4096}
)

assistant_content = response["output"]["message"]["content"]
messages.append({"role": "assistant", "content": assistant_content})

# Check if the model wants to use tools
tool_uses = [b for b in assistant_content if "toolUse" in b]
if not tool_uses:
# No tools called — model is done
text_blocks = [b["text"] for b in assistant_content if "text" in b]
return "\n".join(text_blocks)

# Execute each tool and feed results back
tool_results = []
for block in tool_uses:
tool = block["toolUse"]
print(f" 🔧 Calling {tool['name']}({json.dumps(tool['input'], indent=2)[:80]}...)")
result = execute_tool(tool["name"], tool["input"])
tool_results.append({
"toolResult": {
"toolUseId": tool["toolUseId"],
"content": [{"text": result}]
}
})

messages.append({"role": "user", "content": tool_results})

return "Max turns reached without completion"

# Run it
answer = agentic_loop(
"Our prod RDS instance (db-prod-main) has been slow. Check its CPU metrics "
"for the last 48 hours. If CPU > 80% average, create a High priority Jira ticket "
"for the platform team to investigate."
)
print(answer)
Terminal output from Pattern 4 showing the model calling get_cloudwatch_metrics and then deciding not to file a ticket because average CPU 78.5% is below the 80% threshold
Pattern 4: the model calls the metrics tool, reads the result, and reasons about the 80% threshold before deciding not to file a ticket.

Pattern 5: Handling Content Filtering Gracefully

A clarification that trips people up first: Fable 5's safeguards only fire on operationally harmful requests — weaponizable cybersecurity, bio, chem, or health. Everyday and defensive security topics (how a TLS 1.3 handshake works, where it commonly gets misconfigured) are answered normally, so you won't see filtering in day-to-day use.
When a request is blocked on Bedrock you don't get an exception — you get stopReason == "content_filtered", and unlike Anthropic's first-party API there's no automatic Opus fallback. So you handle it yourself:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import boto3
from botocore.exceptions import ClientError

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")

class FableResponse:
"""Structured response from Fable 5 with safeguard awareness."""
def __init__(self, text: str, filtered: bool, usage: dict, latency_ms: int):
self.text = text
self.filtered = filtered
self.usage = usage
self.latency_ms = latency_ms
self.estimated_cost = (
(usage.get("inputTokens", 0) * 10 / 1_000_000) +
(usage.get("outputTokens", 0) * 50 / 1_000_000)
)

def safe_invoke(prompt: str, fallback_model: str = "global.anthropic.claude-sonnet-4-20250514-v1:0") -> FableResponse:
"""
Invoke Fable 5 with automatic fallback on content filtering.

On Bedrock:
- content_filtered → 0 tokens charged, empty response
- Unlike Anthropic API, there's NO automatic Opus fallback

This function implements client-side fallback to a model of your choice.
"""

try:
response = bedrock.converse(
modelId="global.anthropic.claude-fable-5",
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 4096}
)

if response["stopReason"] == "content_filtered":
print("⚠️ Fable 5 safeguard triggered. Falling back...")
fallback = bedrock.converse(
modelId=fallback_model,
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 4096}
)
# Scan for the text block — your fallback model may also emit
# a reasoningContent block first, so don't assume index 0.
fb_text = "\n".join(
b["text"] for b in fallback["output"]["message"]["content"] if "text" in b
)
return FableResponse(
text=fb_text,
filtered=True,
usage=fallback["usage"],
latency_ms=fallback["metrics"]["latencyMs"]
)

text_blocks = [b["text"] for b in response["output"]["message"]["content"] if "text" in b]
return FableResponse(
text="\n".join(text_blocks),
filtered=False,
usage=response["usage"],
latency_ms=response["metrics"]["latencyMs"]
)

except ClientError as e:
error_code = e.response["Error"]["Code"]
if error_code == "ThrottlingException":
raise # Let caller handle rate limiting
raise

# Usage
result = safe_invoke("Explain TLS 1.3 handshake and where common misconfigurations happen")
if result.filtered:
print("(Response came from fallback model)")
print(result.text)
print(f"Cost: ${result.estimated_cost:.4f}")
⚠️ Your fallback model needs an inference profile ID too. Sonnet 4 rejects on-demand invocation — both anthropic.claude-sonnet-4-20250514 and the bare ...-v1:0 form throw ValidationException. Use a cross-region profile (global. or us. prefix), e.g. global.anthropic.claude-sonnet-4-20250514-v1:0. The "two model IDs" rule isn't a Fable 5 quirk — it applies to whatever you fall back to. Easy to miss because the fallback path only fires when a safeguard trips, so it won't show up in your happy-path testing.
Terminal output probing four Sonnet 4 model IDs: the bare ID and the on-demand v1:0 ID both FAIL with ValidationException, while the us. and global. inference-profile IDs return OK
Probing the candidates: the bare and on-demand IDs raise ValidationException; only the us./global. inference profiles work as a fallback.

Pattern 6: Multi-Turn Conversation Manager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import boto3
from dataclasses import dataclass, field
from typing import List

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")

@dataclass
class Conversation:
"""Manages multi-turn state with token budget tracking."""
model_id: str = "global.anthropic.claude-fable-5"
system_prompt: str = ""
messages: List[dict] = field(default_factory=list)
total_input_tokens: int = 0
total_output_tokens: int = 0
max_context_tokens: int = 900_000 # Leave headroom from 1M limit

@property
def estimated_cost(self) -> float:
return (self.total_input_tokens * 10 + self.total_output_tokens * 50) / 1_000_000

def send(self, user_text: str, max_tokens: int = 4096) -> str:
"""Send a message and return the assistant's response."""
self.messages.append({"role": "user", "content": [{"text": user_text}]})

kwargs = {
"modelId": self.model_id,
"messages": self.messages,
"inferenceConfig": {"maxTokens": max_tokens},
}
if self.system_prompt:
kwargs["system"] = [{"text": self.system_prompt}]

response = bedrock.converse(**kwargs)
usage = response["usage"]
self.total_input_tokens += usage["inputTokens"]
self.total_output_tokens += usage["outputTokens"]

# Warn if approaching context limit
if usage["inputTokens"] > self.max_context_tokens * 0.8:
print(f"⚠️ Context at {usage['inputTokens']/self.max_context_tokens*100:.0f}% capacity")

assistant_content = response["output"]["message"]["content"]
self.messages.append({"role": "assistant", "content": assistant_content})

text_parts = [b["text"] for b in assistant_content if "text" in b]
return "\n".join(text_parts)

def reset(self):
"""Clear conversation history (keeps system prompt)."""
self.messages = []

def summary(self) -> str:
return (
f"Turns: {len(self.messages)//2} | "
f"Tokens: {self.total_input_tokens:,} in / {self.total_output_tokens:,} out | "
f"Cost: ${self.estimated_cost:.4f}"
)

# Example: iterative design session
conv = Conversation(
system_prompt="You are a senior distributed systems engineer. "
"Be concise. Use diagrams (ASCII) when helpful."
)

print(conv.send("Design a rate limiter for a multi-region API gateway. Requirements: "
"global rate limits, per-tenant limits, burst allowance."))
print(f"\n📊 {conv.summary()}")

print(conv.send("Now add a token bucket implementation in Python. "
"Include Redis commands for the distributed version."))
print(f"\n📊 {conv.summary()}")

Key Differences from Other Endpoints

BehaviorBedrock (converse)Anthropic APIClaude Platform
Safeguard triggercontent_filtered + 0 tokensFalls back to Opus 4.8Falls back to Opus 4.8
Reasoning visibilityEncrypted signatureFull text availableFull text available
Temperature control❌ Fixed at 1.0❌ Fixed at 1.0❌ Fixed at 1.0
Prompt cachingVia Anthropic SDK only✅ Native✅ Native
Guardrails integration✅ AWS Guardrails❌❌
Cross-region inference✅ global.* profile❌❌

Gotchas I Hit

  1. Two model IDs — anthropic.claude-fable-5 for config/agreement, global.anthropic.claude-fable-5 for inference. Mix them up and you get cryptic errors. This extends to any fallback or secondary model too: they need their own inference-profile IDs (global./us. prefix), not bare model IDs — see Pattern 5.
  2. Reasoning tokens are billed as output — A "Hello, I'm Claude" response costs ~57 output tokens because reasoning is included. Budget accordingly.
  3. No temperature parameter — If you pass it, you get a validation error. Remove it from any existing code that sets temperature=0.
  4. Streaming reasoning blocks — In converse_stream, reasoning arrives as reasoningContent deltas before text deltas. Don't assume the first delta is text.
  5. Service tier — Only Standard tier at launch. No Flex, Priority, or Reserved capacity yet.
  6. Region availability — In-region: us-east-1, eu-north-1. Cross-region profile works globally.

When NOT to Use Fable 5

  • Simple Q&A or classification — Sonnet 4 at $3/$15 per M tokens is 3-10x cheaper
  • High-volume batch jobs — Haiku 4.5 at $0.80/$4 is 12x cheaper on input
  • Deterministic outputs needed — Temperature is locked at 1.0; use Opus 4.8 with temperature=0
  • Low-latency requirements — Reasoning adds ~3-5s TTFT even for simple prompts
  • Cybersecurity deep research — Safeguards will trigger; you need Mythos 5 (restricted access)

Conclusion

Fable 5 is genuinely impressive for complex, multi-step work — the kind where you'd previously chain multiple Opus calls together. The setup friction (data retention opt-in, agreement) is a one-time cost. The ongoing consideration is billing: those invisible reasoning tokens add up fast.
My recommendation: start with the streaming pattern above, wrap it in the safeguard handler, and point it at your hardest problems. That's where the ROI is clearest.

References

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article