A walkthrough of the Claude Academy course Claude with Amazon Bedrock (Build with the API track). Each lesson is summarised with my own tables and examples; take the course for the full lessons. All the courses are listed in the Claude Academy course index.
Note: This page focuses on what differs on Bedrock. Shared concepts such as prompt evals, prompt engineering, RAG theory, MCP and agent patterns are covered in my Building with the Claude API series. A few details were added from docs.claude.com and AWS docs, including IAM actions, the AnthropicBedrock client and some Converse field names; verify those against the lessons.
Prerequisites:
- Python and JSON.
- An AWS account with Bedrock access, and local AWS credentials (
aws configure or a profile). Check them with aws sts get-caller-identity.
- The
boto3 package.
- Docker, for the computer-use demo.
- An Anthropic API key for the MCP project, which calls the Claude API directly.
Course Outline (and how it maps to the main course)
| Module |
Bedrock-specific content |
Shared with the main course |
| Intro |
Model family overview (Opus / Sonnet / Haiku) |
— |
| Working with the API |
boto3 Converse API, regions, inference profiles, inferenceConfig |
Request lifecycle, multi-turn, system prompts, temperature, prefill and stop sequences |
| Prompt evaluation & engineering |
Same pipeline built on the Converse chat() helper |
Main notes §2–3 |
| Tool use (13 lessons) |
toolSpec / toolUse / toolResult format, batch tool, structured data with tools, flexible to_json extraction, text editor |
Main notes §4 |
| RAG (10 lessons) |
Titan embeddings, reranking, contextual retrieval |
Chunking, BM25 and RRF in main notes §5 |
| Features |
Thinking via additionalModelRequestFields, image and document parts in bytes, cachePoint |
Main notes §6 |
| MCP (12 lessons) |
Adds an “MCP review” lesson |
Main notes §7 |
| Agents |
Claude Code, MCP enhancements, parallel Claude Code with worktrees, automated debugging, computer use, qualities of agents |
Main notes §8–9 |
- The Bedrock course has no web search, Files API or code execution lessons, which reflects what Bedrock supported.
1. Models, Regions and Access
- Opus is the most intelligent. Sonnet balances speed and intelligence and is strong at coding. Haiku is the fastest.
- The course uses Haiku 4.5. It supports extended thinking with a manual budget, but not adaptive thinking.
- Enable model access in the Bedrock console under Model access. Embedding models such as Titan may need access requested separately.
- Regions matter. Not every model is in every region. Using the wrong region gives a “model doesn’t exist” or invalid identifier error.
- Newer models need a cross-region inference profile rather than on-demand throughput. The inference profile ID is the base model ID with a geographic prefix:
| Kind |
Example |
| Base model ID |
anthropic.claude-haiku-4-5-20251001-v1:0 |
| Inference profile (US) |
us.anthropic.claude-haiku-4-5-20251001-v1:0 |
| Other prefixes |
global., eu., jp., apac. (availability varies by model) |
- You find inference profiles in the Bedrock console under Cross-region inference. Calling a base ID directly can fail with “on-demand throughput isn’t supported”.
- IAM: the caller needs
bedrock:InvokeModel, and bedrock:InvokeModelWithResponseStream for streaming. This applies to Converse and ConverseStream too. Automated debugging also needs CloudWatch Logs read access. (Taken from AWS docs, not the lessons.)
- Auth uses standard AWS credentials (environment variables, profile or role). There is no Anthropic API key, except for the MCP project.
2. Client Setup and the Converse API
import boto3
client = boto3.client("bedrock-runtime", region_name="us-west-2")
model_id = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
messages = [{"role": "user", "content": [{"text": "What is quantum computing? One sentence."}]}]
response = client.converse(modelId=model_id, messages=messages)
print(response["output"]["message"]["content"][0]["text"])
Converse vs the Anthropic Messages API
| Concept |
Anthropic SDK (messages.create) |
Bedrock Converse (boto3) |
| Content |
"content": "text" or [{"type":"text","text":…}] |
"content": [{"text": …}] (no type key) |
| Model |
model="claude-haiku-4-5" |
modelId="us.anthropic.claude-…-v1:0" |
| System |
system="…" |
system=[{"text": "…"}] |
| Params |
max_tokens, temperature, stop_sequences |
inferenceConfig={"maxTokens", "temperature", "stopSequences"} (camelCase) |
| Response text |
msg.content[0].text |
resp["output"]["message"]["content"][0]["text"] |
| Stop reason |
stop_reason == "tool_use" |
resp["stopReason"] == "tool_use" |
| Extra fields |
Top-level parameters (thinking=…) |
additionalModelRequestFields={"thinking": …} |
- Roles must alternate between user and assistant. Prefill and stop sequences work the same way as in the main course.
def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None, tool_choice="auto"):
params = {"modelId": model_id, "messages": messages,
"inferenceConfig": {"temperature": temperature, "stopSequences": stop_sequences}}
if system:
params["system"] = [{"text": system}]
if tools:
params["toolConfig"] = {"tools": tools}
if tool_choice != "auto":
params["toolConfig"]["toolChoice"] = {"tool": {"name": tool_choice}}
resp = client.converse(**params)
parts = resp["output"]["message"]["content"]
text = "\n".join(p["text"] for p in parts if "text" in p)
return {"text": text, "parts": parts, "stop_reason": resp["stopReason"]}
Streaming
resp = client.converse_stream(modelId=model_id, messages=messages)
for event in resp["stream"]:
if "contentBlockDelta" in event:
print(event["contentBlockDelta"]["delta"]["text"], end="")
- Events arrive in this order:
messageStart → contentBlockDelta… → contentBlockStop → messageStop → metadata, which carries the usage.
Alternative: the Anthropic SDK on Bedrock (docs, not the course)
# pip install "anthropic[bedrock]"
from anthropic import AnthropicBedrock
client = AnthropicBedrock(aws_region="us-west-2") # uses AWS credential chain
msg = client.messages.create(model="us.anthropic.claude-haiku-4-5-20251001-v1:0",
max_tokens=256, messages=[{"role": "user", "content": "Hi"}])
- With this client, you can reuse the main-course code almost unchanged.
- Schema: wrap the schema as
{"toolSpec": {"name", "description", "inputSchema": {"json": <JSON Schema>}}}, then pass it as toolConfig={"tools": [...]}.
- Response: the assistant message contains a
{"toolUse": {"toolUseId", "name", "input"}} part. Check stopReason == "tool_use".
- Result: send a user message containing
{"toolResult": {"toolUseId": id, "content": [{"text": json.dumps(out)}], "status": "error"}}. Include "status": "error" only when the tool failed.
toolChoice |
Behaviour |
{"auto": {}} |
Model decides whether to use a tool (default) |
{"any": {}} |
Model must use some tool |
{"tool": {"name": "x"}} |
Model must use tool x |
- Batch tool: define a
batch_tool whose input is invocations: [{name, arguments}]. arguments is a JSON string, which run_batch parses with json.loads.
- Add a
batch_tool branch to run_tool that passes tool_input directly, without **.
- This nudges Claude to run independent calls together. In the course example, computing two dates is done in a single batch call.
- Structured data with tools: give Claude an extraction schema such as
article_details (title, author, topics), force it with toolChoice, and read the toolUse input. No follow-up request is needed.
- This is more reliable than prefill, but needs more setup.
- Flexible extraction: use one generic
to_json tool that accepts any properties (additionalProperties). Describe the shape you want in the prompt, for example "title": str, "topics": List[str].
- This gives roughly 90% of the quality for 10% of the setup. Use dedicated schemas for critical or nested data.
- Text editor: use
text_editor_20250728, named str_replace_based_edit_tool, on Claude 4+. Use text_editor_20250124 / str_replace_editor, which also has undo_edit, on earlier models.
- You implement view, str_replace, create and insert yourself.
- Current tool IDs for Bedrock are listed in the AWS “Anthropic-defined tools” docs.
4. RAG Differences
- Embeddings use Amazon Titan, not VoyageAI.
def generate_embedding(text, model_id="amazon.titan-embed-text-v2:0", dimensions=1024, normalize=True):
body = json.dumps({"inputText": text, "dimensions": dimensions, "normalize": normalize})
resp = client.invoke_model(modelId=model_id, body=body,
accept="application/json", contentType="application/json")
return json.loads(resp["body"].read())["embedding"]
- Titan v2 may need access requested first. v1 is fine for learning.
- Embeddings are produced through
invoke_model with a model-specific JSON body, not through Converse.
- Reranking: after the hybrid (vector + BM25, RRF) search, Claude reorders the chunks. Give each chunk an ID and ask for
{"document_ids": [...]} via prefill and stop sequence.
- Contextual retrieval: Claude writes a short snippet that situates each chunk in the document, and the snippet is prepended before indexing. For large documents, use the first few chunks plus the chunks just before the target as context.
5. Claude Features on Bedrock
| Feature |
Bedrock (Converse) format / limits |
| Extended thinking |
additionalModelRequestFields={"thinking": {"type": "enabled", "budget_tokens": 1024}} (minimum 1024) on Haiku/Sonnet/Opus 4.5 and earlier. Newer models use {"type": "adaptive", "display": "summarized"} with effort; budget_tokens is deprecated, or rejected with a 400 on Opus 4.7+. The response has a reasoning part with a signature, or redactedContent. |
| Images |
{"image": {"format": "png", "source": {"bytes": raw_bytes}}}. Raw bytes, not base64. The course states 20 images per request, 3.75 MB each and 8000 px max. Tokens ≈ w×h/750. |
| PDFs / citations |
{"document": {"format": "pdf", "name": "earth", "source": {"bytes": b}, "citations": {"enabled": True}}}. The response mixes text parts with citation content parts. |
| Prompt caching |
{"cachePoint": {"type": "default"}} parts, instead of cache_control. Put them after the content to cache: at the end of the system list, at the end of the tools list or inside messages. TTL is 5 min, the minimum is 1024 tokens, and the prefix must be identical. |
system = [{"text": "You are a senior software..."}, {"cachePoint": {"type": "default"}}]
tools = [{"toolSpec": get_current_datetime_schema},
{"toolSpec": add_duration_to_datetime_schema},
{"cachePoint": {"type": "default"}}]
6. MCP, Claude Code and Agents (Bedrock flavour)
- MCP: the content is the same as main notes §7, plus a review lesson on tools (model), resources (app) and prompts (user).
- Parallelizing Claude Code: run several instances in separate git worktrees to avoid file conflicts.
- Automate the setup with custom commands in
.claude/commands/*.md, for example /project:create_worktree feature_a using $ARGUMENTS.
- Let Claude merge the branches back and resolve conflicts.
- Automated debugging: a daily GitHub Action runs Claude to:
- Pull the last 24 hours of CloudWatch logs with the AWS CLI.
- Deduplicate the errors.
- Fix the code.
- Open a PR.
- The course example is a mistyped Bedrock model ID that only appeared in production on Amplify.
- The workflow needs CloudWatch IAM read access and repository write permission.
- Computer use: run the Anthropic quickstarts reference Docker image. It needs Docker plus a local AWS profile (usually
default), because it calls Claude through Bedrock.
- It is ordinary tool use: a small schema is expanded into mouse, keyboard and screenshot actions, which run in an isolated container.
- Qualities of agents:
- Focused tool sets running in loops.
- Context is everything; agents gather it with tools and observe before acting.
- High-value, low-error-cost tasks.
- Continuous evaluation.
Quick-Review Flashcards
- Q: Which boto3 client and method does the course use to call Claude? → A:
boto3.client("bedrock-runtime", region_name=…) and client.converse(modelId=…, messages=…).
- Q: How does a Converse text content block differ from a Messages API block? → A: It is
{"text": "…"}, with no "type" key, and content is always a list.
- Q: Where do temperature and stop sequences go in Converse? → A: In
inferenceConfig={"temperature": …, "stopSequences": […]}, in camelCase.
- Q: Why might you get “model doesn’t exist” or “on-demand throughput isn’t supported”? → A: You are in the wrong region, or you need a cross-region inference profile ID such as
us.anthropic.claude-haiku-4-5-20251001-v1:0.
- Q: Which streaming events carry text in ConverseStream? → A:
contentBlockDelta → delta.text. The metadata event carries usage at the end.
- Q: What is the Bedrock tool schema wrapper? → A:
{"toolSpec": {"name", "description", "inputSchema": {"json": …}}} inside toolConfig.tools.
- Q: How do you force a specific tool in Converse? → A:
toolConfig["toolChoice"] = {"tool": {"name": "article_details"}}.
- Q: What does the batch tool solve? → A: Claude often calls independent tools one at a time. A
batch_tool with a list of invocations groups them into one turn.
- Q: What is the trade-off with the flexible
to_json extraction tool? → A: About 90% of the quality for 10% of the setup. The structure is described in the prompt instead of a schema.
- Q: Which embedding model does the Bedrock course use, and how is it called? → A: Amazon Titan Text Embeddings v2 (
amazon.titan-embed-text-v2:0), via invoke_model with inputText, dimensions and normalize.
- Q: How do you pass extended-thinking settings in Converse? → A:
additionalModelRequestFields={"thinking": {...}}.
- Q: How are images sent on Bedrock, and what limits does the course state? → A: As
{"image": {"format": "png", "source": {"bytes": …}}} with raw bytes. Up to 20 images, 3.75 MB each and 8000 px max.
- Q: What is the Bedrock equivalent of
cache_control? → A: A {"cachePoint": {"type": "default"}} part placed after the content to cache. The minimum is 1024 tokens and the TTL is 5 minutes.
- Q: How do you avoid file conflicts with parallel Claude Code instances? → A: Give each instance its own git worktree and branch, then merge.
- Q: What does the automated-debugging GitHub Action do? → A: It fetches 24 hours of CloudWatch logs, deduplicates the errors, has Claude fix them and opens a PR.
- Q: Which Anthropic SDK client targets Bedrock? → A:
AnthropicBedrock(aws_region=…) from anthropic[bedrock]. It uses Messages API syntax with Bedrock model IDs.
Practice Ideas
- Port the main course’s
chat() helper to Converse: system list, inferenceConfig, toolConfig and a returned stopReason. Then rebuild the reminder tool loop with toolUse and toolResult.
- Call the same model with a base ID and with a
us. inference profile, and note the errors and the region behaviour. List the available models with aws bedrock list-foundation-models --by-provider anthropic.
- Write a least-privilege IAM policy for an app that only invokes and streams one Claude inference profile.
- Build a Titan-embedding RAG pipeline with BM25, RRF, Claude reranking and contextual retrieval. Compare the results on an ID-heavy query.
- Add
cachePoint to the system prompt and tool list, and compare the cache read and write counts in the usage of repeated calls.
- Implement the batch tool and the flexible
to_json extractor, then compare the extraction quality against a dedicated schema on 10 articles.
- Re-implement one notebook with
AnthropicBedrock instead of boto3 and list the code differences.