AWS AI foundations
AWS bundles AI into three layers — pre-built services, Bedrock, and SageMaker. This chapter gets you authenticated, in the right region, and oriented, so every later chapter is build, not setup.
AWS (Amazon Web Services) is a giant menu of computing services you rent by the hour or by usage. For AI, three items on that menu matter: Bedrock (call ready-made AI models like Claude), SageMaker (train/host your own models), and a set of pre-built AI APIs (read documents, images, speech). You talk to all of them from Python using a library called boto3. That's the whole picture — everything else is detail.
The words you'll hear (in plain terms):
| Term | What it actually means |
|---|---|
| Bedrock | AWS's way to call foundation models (incl. Claude) over an API — like the Anthropic API, but on AWS. |
| boto3 | the official Python library for talking to AWS. pip install boto3. |
| IAM role / credentials | AWS's way of proving who you are and what you're allowed to do — instead of a password in your code. |
| region | which AWS data-center location your service runs in (e.g. us-east-1). |
| Terraform / CDK | tools that create cloud resources from a file, so setup is repeatable — introduced gently later. |
What you need before starting:
- Comfort with Python basics (the Python track covers this).
- Having called Claude via the Anthropic API helps — Bedrock is the same idea.
- An AWS account to actually run the labs (reading works without one).
New to the topic? Read this box, then take the chapters in order — each section is tagged essential → expert so you always know the depth you're at.
- AWS credentials (
aws configure) + Bedrock model access enabled in your region +pip install boto3 - AWS credentials (
aws configure) +pip install boto3
Learning objectives
- Explain how AWS packages AI: Bedrock (GenAI), SageMaker (ML platform), and the pre-built AI services.
- Authenticate boto3 to AWS with the credential chain, profiles, and IAM roles — no keys in code.
- Choose a region that has the models/services you need, and reason about the cost model.
- Find where Claude fits: the same model you used via the Anthropic SDK, now behind Bedrock.
code/aws1-foundations/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.The AWS AI landscape in one map essential
Start with the moment that sends most people down the wrong path. You've decided to "add AI on AWS," you open the console, and you're staring at what feels like fifty services with overlapping names — Bedrock, SageMaker, Comprehend, Kendra, Q, Rekognition, Textract. Which one calls Claude? Which one is for training? You pick one at random, spend two hours in its docs, and discover it was the wrong door entirely. The confusion isn't you — it's that AWS never gives you the map, only the menu.
So here's the map, and it's simpler than the menu suggests. Think of AWS AI as a vending machine with three shelves, arranged from "just press a button" at the top to "bring your own ingredients" at the bottom. The top shelf is the pre-built AI services: you hand them a document, an image, or an audio clip and they hand back an answer — no model, no training, no ML knowledge. The middle shelf is Amazon Bedrock: someone else's foundation models (including Claude) that you rent by the token, plus managed wrappers for RAG, agents, and guardrails. The bottom shelf is Amazon SageMaker: the full workshop where you train, tune, and host your own models — maximum control, maximum responsibility.
Why does the shelf you pick matter so much? Because it decides three things at once: how much you build, how much you pay, and how much you can customize. Reaching for the bottom shelf (SageMaker) to do a job the top shelf already does (extract text from a PDF → Textract) is the single most expensive beginner mistake on this whole track — you end up training and babysitting a model to solve a problem AWS solved for you at a fraction of a cent per page. The instinct that costs teams the most money is treating "AI on AWS" as one thing instead of three.
The rule that keeps you out of trouble: climb down the shelves only when the shelf above genuinely can't do the job. Does a narrow, pre-built service already cover your task? Use it. If not, can a general foundation model on Bedrock do it with a good prompt? Use that. Only when you truly need a custom-trained or custom-hosted model do you drop to SageMaker. This whole track leads with Bedrock because that middle shelf — renting a capable model like Claude — is where the vast majority of generative-AI automation actually lives.
AWS AI splits into three layers. From highest-level to lowest: pre-built AI services (call an API, no ML knowledge — Textract, Comprehend, Rekognition, Transcribe), Amazon Bedrock (managed access to foundation models incl. Claude, plus managed RAG, agents, and guardrails), and Amazon SageMaker (the full ML platform — train, tune, host your own models). This track covers all three, but leads with Bedrock because that is where the generative-AI automation lives.
Read the three stacked boxes top to bottom — that top-to-bottom order is also the order you should consider them: try the highest shelf first.
- The green top box is the pre-built AI services — you send a document, image, or audio clip and get an answer back, with no model to manage. The blue middle box is Bedrock, where you rent a foundation model (including Claude) by the token. The amber bottom box is SageMaker, where you train and host your own model.
- The dashed downward arrow on the left is the cost of descending: each step down adds more you have to build and more you have to operate. The labels on the right are the payoff that matters most — the top two shelves scale to zero, while a SageMaker endpoint bills even while idle.
- The payoff element is the rule at the top: pick the highest shelf that can do your task. Reaching straight for SageMaker to extract text from a PDF is the classic expensive mistake — the green shelf already does it.
In short: start at the top shelf and climb down only when the shelf above genuinely can't do the job.
Authentication: the boto3 credential chain essential
Here's the failure that teaches this lesson the hard way. A developer, in a hurry, pastes their AWS access key and secret straight into the Python file — aws_access_key_id="AKIA..." — because it works instantly and they'll "clean it up later." They commit it. Weeks pass. Then a bill arrives for thousands of dollars: a bot scraped the key off GitHub within minutes of the push and spun up crypto miners in their account. This is not a rare horror story; it is one of the most common ways cloud accounts get compromised. The boto3 credential chain exists precisely so you never have to type a key into code at all.
The mental model is a doorman who checks several pockets in order until he finds your ID. When your code calls AWS, boto3 doesn't ask you for credentials — it goes looking for them itself, in a fixed sequence, and stops at the first place it finds them. The order is: environment variables first, then a named profile in your ~/.aws/credentials file, then — if the code is running on AWS — the temporary credentials handed out by an attached IAM role. Because the search is automatic, the same line of Python authenticates on your laptop (where it finds a profile) and inside a Lambda function (where it finds a role), with nothing to change between them.
Now the part that trips people up: the difference between a key and a role. A key (that AKIA... string plus its secret) is a long-lived password — it works until you rotate it, which means if it leaks, it keeps working for the attacker too. An IAM role is different: it's an identity your compute assumes, and AWS mints short-lived, auto-rotating credentials for it behind the scenes. Nothing long-lived is ever written down. That is why the rule for anything running in automation — Lambda, EC2, ECS, a CI job — is use a role, never a key. Keys are for the rare case of a human on a laptop who has no better option, and even then you scope them tightly.
One more thing worth naming, because beginners conflate the two: authentication is not authorization. The credential chain proves who you are (authentication). What you're actually allowed to do — call Bedrock, read a bucket, delete an endpoint — is decided separately by the IAM policies attached to that identity (authorization). A perfectly resolved identity can still get AccessDenied on the very next call because its policy doesn't grant the action. Keeping those two ideas apart saves you hours of debugging: "am I logged in?" and "am I permitted?" are different questions with different fixes.
boto3 (the AWS SDK for Python) never wants credentials hard-coded. It searches a chain: environment variables → shared config file (~/.aws/credentials profiles) → IAM role (on EC2/Lambda/ECS). In automation you almost always use an IAM role so there are no long-lived keys at all.
~/.aws profile → IAM role and stops at the first hit — which is why the same line of code authenticates on a laptop (profile) and inside Lambda (role) with nothing to change.
Trace one credential lookup left to right. The blue box on the far left is your code making a boto3 call; everything to its right is the search boto3 runs to find who you are.
- The solid arrow into box 1 is the first place boto3 looks: environment variables. A dashed "miss" arrow means nothing was there, so the search steps right — to a named profile in
~/.aws/credentials, then to an attached IAM role (only present when the code runs on AWS). - The downward arrow into the green box is a hit: the first source that has credentials wins, and boto3 resolves your identity — the account number and ARN you saw
get_caller_identityprint. - If every source misses, the dashed path falls through to the red box —
Unable to locate credentials. That is the error that means "runaws configure," not "wrong password."
In short: the chain resolves the first source it finds, so automation just attaches a role and never writes a key down.
check_auth.pyimport boto3
# The credential chain resolves automatically. On your laptop this uses a
# named profile; in Lambda/EC2 it uses the attached IAM role — same code.
session = boto3.Session(region_name="us-east-1") # region where Bedrock has Claude
# Prove who we are (STS = Security Token Service) without needing any AI service.
sts = session.client("sts")
ident = sts.get_caller_identity()
print("account:", ident["Account"])
print("arn: ", ident["Arn"])
This tiny script answers one question before you touch any AI service: "Who does AWS think I am, and am I logged in?" It uses boto3 (the official AWS library for Python) to open a connection and ask AWS to identify you. If it prints an account number, your credentials work and every later lab will too.
import boto3pulls in the AWS SDK. You installed it withpip install boto3. It knows how to talk to every AWS service.boto3.Session(region_name="us-east-1")creates a session — a bundle of "who I am" plus "which data-center region I'm working in". You did not type any password or key here: boto3 finds your credentials automatically by walking the credential chain (environment variables → your~/.aws/credentialsprofile → an IAM role if running on AWS). The same code works on your laptop and inside Lambda.session.client("sts")builds a client for STS (Security Token Service) — AWS's "identity desk". We pick STS on purpose because it needs no special permissions and touches no AI service, so it's the cleanest possible login test.sts.get_caller_identity()is the actual call over the network. It returns a small dictionary describing you; we read two fields from it and print them.
What the output means: Two lines: your 12-digit AWS account number, and your ARN (Amazon Resource Name — the unique string naming exactly which user or role you are). Seeing them means you're authenticated. An error like Unable to locate credentials instead means the chain found nothing — run aws configure first.
Try this: Change region_name to "us-west-2" and re-run — the account and ARN stay the same, because who you are is global; only where you work changed. Identity is not tied to a region.
aws configure --profile llm-course once, then boto3.Session(profile_name="llm-course", region_name="us-east-1"). Keep production on roles, never on keys.Regions & model access intermediate
You've probably already hit this wall without knowing what it was. Your credentials resolve perfectly — get_caller_identity prints your account, everything looks healthy — and yet the moment you try to list or call a Claude model, you get an empty list or an AccessDenied that mentions nothing about credentials. You double-check your keys. You re-run aws configure. Nothing. The real culprit is one of two invisible settings that have nothing to do with who you are: the region you're pointed at, and whether model access has been switched on there.
Start with region, because the analogy makes it click: AWS is not one building, it's a chain of warehouses in different cities (us-east-1 in Northern Virginia, us-west-2 in Oregon, and many more). Each warehouse stocks its own inventory. A model that's available in one region may simply not be carried in another — and your account's resources, quotas, and even the enablement switches are tracked per warehouse. When you write region_name="us-east-1", you're choosing which warehouse to walk into. Ask for Claude in a region that doesn't stock it and the shelf is bare, no matter how valid your ID is.
The second setting catches almost everyone once. On Bedrock, foundation models are opt-in per account, per region: even in a region that does carry Claude, a brand-new account sees zero models until a human clicks through Model access → request access in the Bedrock console and it's granted. This is deliberate — it's AWS making you consciously agree to each provider's terms before you can spend money on their model — but it means the fix for "my list is empty" is almost never in your code. It's a one-time, per-region checkbox in the console. The common mistake is enabling access in us-east-1, watching it work, then being baffled when the identical script fails in us-west-2: enablement doesn't travel between warehouses.
Why do practitioners bother with more than one region at all, if it's this fiddly? Latency (serving users closer to a warehouse near them), resilience (a fallback if one region has an outage), data-residency rules (some data must stay in a specific geography), and plain availability (a specific model or quota you need may only exist in certain regions). The practical habit this section is teaching: treat "which region?" and "is model access on here?" as the first two things you verify in any new environment — before you debug a single line of inference code.
Not every model is in every region. Claude on Bedrock is broadest in us-east-1 and us-west-2. Before a model works you must enable model access once in the Bedrock console (Model access → request). This is a one-time, per-account, per-region step.
Start at the box on top — your resolved identity — then follow the two arrows down into two separate regional warehouses.
- The top box is the identity the credential chain resolved (account + ARN). It is global: the same one is used no matter which region you point at, which is why both arrows come from it unchanged.
- The left green warehouse is
us-east-1, where you already clicked Model access → request: it stocks Claude and returns real model IDs (Sonnet, Haiku). - The right red warehouse is a brand-new
us-west-2: the very same identity arrives, but because access was never requested here, the shelf is bare — you get an empty list, not anAccessDeniedabout your credentials.
In short: an empty model list means "enable access in this region," not "wrong credentials" — enablement doesn't travel between regions.
list_models.pyimport boto3
# List the foundation models your account can see in this region.
bedrock = boto3.client("bedrock", region_name="us-east-1") # control-plane client
models = bedrock.list_foundation_models(byProvider="Anthropic")
for m in models["modelSummaries"]:
print(m["modelId"], "-", m["modelName"])
anthropic.claude-3-5-sonnet-20241022-v2:0 - Claude 3.5 Sonnet
anthropic.claude-3-5-haiku-20241022-v1:0 - Claude 3.5 Haiku
anthropic.claude-3-opus-20240229-v1:0 - Claude 3 Opus
Before you can call an AI model on Bedrock, you need to know which models your account is actually allowed to see in a region. This script asks Bedrock for that list, filtered to Anthropic's Claude models. Think of it as reading the menu before ordering.
boto3.client("bedrock", region_name="us-east-1")creates a client for the bedrock service. Note this is the control plane — the part that manages and lists models. (A different client,bedrock-runtime, is what you'll use later to actually run a model. Don't mix them up.) This is also a shortcut for the session+client you saw in W1.1 rolled into one line.bedrock.list_foundation_models(byProvider="Anthropic")calls AWS and returns every foundation model from Anthropic that's visible in this region.byProvideris a filter so you don't get every vendor's models back.- The result is a dictionary; the models live under the key
"modelSummaries", which is a list. Theforloop walks that list, and for each modelmwe print itsmodelId(the exact string you pass when calling it) next to its human-friendlymodelName.
What the output means: One line per available Claude model — its machine ID and its name, e.g. anthropic.claude-3-5-sonnet-20241022-v2:0 - Claude 3.5 Sonnet. An empty list is the common gotcha: it usually means you haven't enabled model access yet in the Bedrock console for that region — a one-time step.
Try this: Copy one of the printed modelId strings — that exact value is what W2 passes to the bedrock-runtime client to send a real prompt. Also try removing byProvider="Anthropic" to see how many models other providers add to the menu.
bedrock is the control plane (manage models, guardrails, KBs). bedrock-runtime is the data plane (actually call a model). You will use bedrock-runtime for every inference call — that is W2.The cost model advanced
The war story here is always the same, and it always stings. An engineer spins up a SageMaker endpoint on a Friday to test a model, gets it working, closes the laptop, and moves on. No calls go to it over the weekend — zero traffic. Monday morning the finance channel lights up: the endpoint quietly billed for every hour it existed, idle or not, and a "quick test" turned into a four-figure surprise. Nobody did anything wrong except misunderstand how the resource charges. Getting the cost model right — not the exact prices, which drift — is what separates an architecture that scales affordably from one that hemorrhages money while doing nothing.
The distinction that governs everything is pay-per-use versus pay-per-time. Picture two ways of getting somewhere: a taxi charges only while you're riding and costs nothing parked at the curb; a rental car charges by the day whether it moves or sits in the driveway. Bedrock and the pre-built AI services are taxis — you're billed only for the work you actually request (tokens processed, pages read, minutes transcribed), and when no requests arrive, the meter reads zero. They scale to zero. A SageMaker real-time endpoint is a rental car: you're paying for a provisioned instance by the hour, and that clock runs whether or not a single prediction flows through it.
This is why "scales to zero?" is the one column in the table below worth memorizing. It reframes the cost question from "how much per call?" to the far more important "does this thing keep charging me when I'm not using it?" For a service that scales to zero, forgetting about it costs you nothing — a prototype you abandon simply stops billing. For a service that doesn't, forgetting about it is exactly how the weekend-endpoint story happens. The discipline this teaches is a habit, not a formula: anything billed per instance-hour must be explicitly torn down when idle — deleted, or fronted by autoscaling that can drop to zero — because AWS will never do it for you.
A second, subtler point for the token-billed layers: Bedrock, like the Anthropic API, prices input and output tokens separately, and output is usually the pricier of the two. That means the cheapest lever you have on Bedrock spend often isn't switching models — it's writing prompts that produce tighter answers, since every extra sentence the model generates is billed. Cost on the taxi layers is a simple, predictable, linear function of tokens you can estimate offline before you ever send a request (which is exactly what the practice exercises below build).
Bedrock bills per token, like the Anthropic API, but on the AWS bill. Pre-built AI services bill per unit (per page for Textract, per minute for Transcribe). SageMaker endpoints bill per hour the instance is running — a real trap: an idle endpoint still costs money. The rule for automation: Bedrock and AI services are pay-per-use (scale to zero); SageMaker real-time endpoints are pay-per-hour (remember to delete them).
Both panels plot the same thing: dollars on the vertical axis, idle time (with zero traffic) running left to right along the bottom. Compare the two lines.
- The left blue panel is a pay-per-use layer — Bedrock or a pre-built AI service. Its cost line stays flat on the $0 baseline the whole time: no requests, no charge. This is what "scales to zero" looks like.
- The right amber panel is a SageMaker real-time endpoint. Its line climbs steadily upward even though no requests are arriving, because you're billed per instance-hour just for keeping it provisioned.
- The green ✅ and red ❌ mark the payoff: forgetting a pay-per-use service costs nothing, while forgetting the endpoint is the "quiet weekend bill." Anything billed per instance-hour must be explicitly deleted when idle.
In short: if a service doesn't scale to zero, an idle resource is still spending your money — tear it down.
| Layer | Bills by | Scales to zero? |
|---|---|---|
| Bedrock (models) | input+output tokens | ✅ yes |
| AI services (Textract…) | per page / minute / image | ✅ yes |
| SageMaker endpoint | instance-hour | ❌ no — delete it |
Exercise W1.1 — Prove your setup
Context: Nothing in the rest of this track works until your credentials resolve and Bedrock model access is enabled in the regions you'll use. Proving both up front saves you from debugging a permissions ghost three lessons later.
Your task: Write a script that prints your caller identity and the list of available Anthropic models in both us-east-1 and us-west-2; if a region's list is empty, enable model access in the console and re-run.
Requirements:
- Print caller identity via
sts.get_caller_identity() - For each region, list Anthropic foundation models with a
bedrockcontrol-plane client - Treat an empty model list as the signal to enable model access in that region's console
- You're ready for W2 only when both regions return at least one Claude model
💡 Hint: Model access is per-region, so run the same listing code twice with different region names — a passing us-east-1 tells you nothing about us-west-2.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Before you invoke a single model you need to know which identity AWS thinks you are — the same credential chain governs every Bedrock call, and a misresolved role is the most common reason a script that 'worked yesterday' suddenly gets AccessDenied.
Your task: Use a boto3 Session to resolve your credentials via STS and print the account id and ARN the chain landed on.
Requirements:
- Create a
boto3.Sessionwith an explicit region - Call
get_caller_identity()on anstsclient - Print both the
Accountand theArnfields from the response - Understand the resolution order: environment vars, then
~/.aws/credentials, then an attached IAM role
💡 Hint: STS doesn't grant anything — it just echoes whoever the credential chain already resolved, so this is a pure read-only identity check.
Show solution
The credential chain resolves env vars, then ~/.aws/credentials, then an IAM role. STS just echoes who you are.
import boto3
session = boto3.Session(region_name="us-east-1")
ident = session.client("sts").get_caller_identity()
print(ident["Account"], ident["Arn"])
Context: Bedrock model access is opt-in per region: a fresh account can authenticate perfectly and still see zero Claude models until access is enabled. Listing what Anthropic models are actually available is the first thing to verify in any new region.
Your task: Use the Bedrock control plane to list foundation models from Anthropic and print each modelId.
Requirements:
- Use the
bedrockclient (the control plane), notbedrock-runtime - Call
list_foundation_modelsfiltering to the Anthropic provider - Iterate
modelSummariesand print eachmodelId - Recognize that an empty list means model access hasn't been enabled for that region yet
💡 Hint: There are two Bedrock clients for a reason — the one that lists models is not the one that runs them.
Show solution
bedrock (not bedrock-runtime) is the control plane. Model access must be enabled once per region.
import boto3
bedrock = boto3.client("bedrock", region_name="us-east-1")
resp = bedrock.list_foundation_models(byProvider="Anthropic")
for m in resp["modelSummaries"]:
print(m["modelId"], "-", m["modelName"])
Context: Bedrock splits cleanly into a control plane that manages models and a data plane that runs them, and confusing the two is a rite of passage: the call you want simply isn't a method on the client you reached for.
Your task: Explain, with a short boto3 sketch, which client manages models versus runs them, and why calling inference on the control-plane client fails.
Requirements:
- Use
bedrockfor management operations likelist_foundation_models - Use
bedrock-runtimefor inference such asconverse/invoke_model - Show that calling
converseon thebedrockclient raisesAttributeErrorbecause the method doesn't exist there - State the mental model plainly: control plane administers, data plane performs inference
💡 Hint: If the method you want throws AttributeError rather than a permissions error, you've almost certainly grabbed the wrong plane's client.
Show solution
bedrock administers (list/enable); bedrock-runtime performs inference. Calling converse on the control-plane client raises AttributeError.
import boto3
control = boto3.client("bedrock", region_name="us-east-1") # manage
runtime = boto3.client("bedrock-runtime", region_name="us-east-1") # run
control.list_foundation_models() # OK on control plane
# control.converse(...) # AttributeError -- wrong client
runtime.converse( # inference lives here
modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
messages=[{"role":"user","content":[{"text":"hi"}]}],
)
Context: Bedrock bills per token, so cost is a simple linear function of two counts you already have from every response. A tiny local estimator lets you reason about spend without waiting for a bill or calling the API at all.
Your task: Given input/output token counts and per-1K prices, compute the dollar cost of one call, and demonstrate it for 1,200 input and 400 output tokens at $0.003 / $0.015 per 1K.
Requirements:
- Cost =
in_tok/1000 * in_per_k + out_tok/1000 * out_per_k - Input and output are priced separately — don't collapse them into one rate
- The sample inputs total roughly $0.0096 ($0.0036 in + $0.0060 out)
- Run entirely offline — no AWS credentials or network needed
💡 Hint: Prices are quoted per 1,000 tokens, so divide each count by 1000 before multiplying — the two products just add.
Show solution
Bedrock bills per token; cost is a linear function of the two token counts.
def call_cost(in_tok, out_tok, in_per_k, out_per_k):
return in_tok/1000*in_per_k + out_tok/1000*out_per_k
c = call_cost(1200, 400, 0.003, 0.015)
print(round(c, 6)) # 0.0096 (0.0036 in + 0.0060 out)
Context: AWS gives you three tiers of AI — pre-built services, managed foundation models on Bedrock, and self-hosted SageMaker — and picking wrong is usually a cost decision, not a capability one. Encoding the rule makes the trade-off explicit and reviewable.
Your task: Encode the layer choice as choose_layer(need_custom_model, task_is_narrow_prebuilt) returning 'prebuilt', 'bedrock', or 'sagemaker', and note each option's cost shape.
Requirements:
- A narrow, pre-built task (Textract/Comprehend/Rekognition) short-circuits to
'prebuilt', pay-per-unit - Needing a custom trained/hosted model returns
'sagemaker', billed per instance-hour - Otherwise return
'bedrock'— managed FMs, pay-per-token - Capture the cost implication: pre-built and Bedrock scale to zero, a SageMaker real-time endpoint bills continuously and must be deleted
- Run offline; verify all three branches with example inputs
💡 Hint: Order the checks so the cheapest, narrowest option wins first — only fall through to SageMaker when a custom model is genuinely required.
Show solution
Pre-built services and Bedrock scale to zero; a SageMaker real-time endpoint bills per instance-hour and must be deleted.
def choose_layer(need_custom_model, task_is_narrow_prebuilt):
if task_is_narrow_prebuilt:
return "prebuilt" # Textract/Comprehend/Rekognition, pay-per-unit
if need_custom_model:
return "sagemaker" # train/host your own; endpoint billed per hour
return "bedrock" # managed FMs, pay-per-token, scales to zero
print(choose_layer(False, True)) # prebuilt
print(choose_layer(True, False)) # sagemaker
print(choose_layer(False, False)) # bedrock
Context: Finance doesn't care about tokens — they care about the monthly bill and whether it blows the budget. Turning per-call token cost into a projected 30-day spend with a budget flag is exactly the artifact a capacity-planning review expects.
Your task: Given average tokens per request, requests per day, and the model's prices, project the 30-day Bedrock cost and flag whether it exceeds a supplied budget.
Requirements:
- Compute a per-call cost from separate input/output per-1K prices (reuse the Expert-rung formula)
- Scale it by requests-per-day and a configurable horizon (default 30 days)
- Look prices up per model id (e.g.
anthropic.claude-3-5-sonnet-20241022-v2:0) from a table - Return the per-call cost, the projected monthly total, and an
over_budgetboolean - Run offline — this is planning math, not a live API call
💡 Hint: Keep the horizon a parameter so the same function answers 'per week' or 'per quarter' — the budget comparison is just total > budget.
Show solution
Scale the per-call cost by daily volume and horizon; compare against the budget to drive an alert.
PRICES = {"anthropic.claude-3-5-sonnet-20241022-v2:0": (0.003, 0.015)}
def monthly_projection(model, in_tok, out_tok, reqs_per_day, budget, days=30):
in_k, out_k = PRICES[model]
per_call = in_tok/1000*in_k + out_tok/1000*out_k
total = per_call * reqs_per_day * days
return {"per_call": round(per_call, 6),
"monthly": round(total, 2),
"over_budget": total > budget}
print(monthly_projection("anthropic.claude-3-5-sonnet-20241022-v2:0",
in_tok=1500, out_tok=500, reqs_per_day=20000,
budget=5000))
✓ Checkpoint — you can move on when you can…
- Name the three layers of AWS AI and give one service in each.
- Explain the boto3 credential chain and why automation prefers IAM roles.
- Describe the difference between the
bedrockandbedrock-runtimeclients. - State which AWS AI layer bills per hour and why that matters.
Knowledge check check yourself
What are the three layers of AWS AI, and where does calling Claude fit?
Show answer
bedrock-runtime data-plane client.Why does the boto3 credential chain mean no keys in code, and what is the cost-model trap with SageMaker endpoints?
Show answer
~/.aws profile, then an attached IAM role), so the same code works on a laptop or in Lambda without embedding keys. The trap is that Bedrock and pre-built services scale to zero (pay per use), but a SageMaker real-time endpoint bills per instance-hour even when idle, so you must explicitly delete it or costs keep accruing.