This is the first of the hands-on labs that run alongside these posts. The idea is simple: you get a working base and build the part that matters. Here the scaffolding is at its highest, everything is built except one function body, and each lab after this hands you less.
The full lab, CloudFormation and scripts, is in lab-01-invoke-a-model.zip. Download it, unpack, and follow the README; this post is the walk-through and the why.
The scenario
A team wants the simplest thing a GenAI feature can be: a function that takes a prompt, asks a Bedrock model, and returns the answer as JSON. No retrieval, no memory, no safety layer yet. Just prove that code you own can call a model you don’t have to host.
That last part is the point of the lab. There is no endpoint to stand up, no instance to size, no container to build. On-demand inference on Bedrock is an ordinary AWS SDK call from anything with the right IAM permission, and a Lambda function is the smallest thing that can make one.
What you’re given
The CloudFormation template builds two resources:
- a Lambda function (Python 3.12) that receives the model id in a
MODEL_IDenvironment variable; - an execution role allowing
bedrock:InvokeModel(and the streaming variant) on foundation models, plus inference profiles in the account.
src/handler.py already parses the prompt out of an HTTP-shaped JSON body and has an _ok() helper that wraps the reply. The one thing missing is the middle: the model call.
This shape is the whole track’s shape. Every lab is one CloudFormation stack holding a Lambda function and an IAM role scoped to that lab, driven by the same three scripts; later labs add resources inside the stack (a Guardrail, a DynamoDB table, a Knowledge Base) without changing the outline. Bedrock itself never appears in the stack, because on-demand inference is serverless and billed per token, and that is also why deleting the stack removes everything with a meter on it.
Your task
Replace the NotImplementedError in handler() with a Converse call:
import boto3
_bedrock = boto3.client("bedrock-runtime")
response = _bedrock.converse(
modelId=MODEL_ID,
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 512, "temperature": 0.2},
)
answer = response["output"]["message"]["content"][0]["text"]
return _ok({"answer": answer})
That is the whole change: a client, one call, and pulling the assistant’s text out of the reply.
Deploy and prove it
The zip ships a preflight.sh; run it once before your first lab. It checks the CLI, your credentials, the region, model access for the three models the track uses, quota visibility, and whether an organisation-level policy is going to block you, so none of it surfaces halfway through a deploy. Its model probes send three one-token requests, which together cost a fraction of a US cent.
The check most worth internalising is model access, because it is the single most common reason a lab fails: in the Bedrock console, open Model access and enable the model you plan to use, in the region you are working in. Then:
cd lab-01-invoke-a-model
./scripts/deploy.sh
./scripts/test.sh
./scripts/test.sh "Explain retrieval-augmented generation in two sentences."
The defaults are stack genai-lab-01, region us-east-1, and amazon.nova-lite-v1:0; override any of them with environment variables. Before you fill the gap, the test prints a NotImplementedError from the logs. After, it prints a JSON body with an answer field containing a real sentence from the model.
Two failures are worth recognising on sight. An AccessDeniedException naming the model is nearly always the console gate: model access is not enabled for that model in that region. IAM refusals arrive as the same error, so read the ARN the message names and check it against the role before you decide which gate is shut. A ValidationException complaining about on-demand throughput means the model is only served through a cross-region Inference profileA Bedrock resource wrapping a model so calls to it can be tagged, routed across regions, or repointed without changing app code.
; redeploy with the profile id for the geography you are calling from (MODEL_ID=us.amazon.nova-lite-v1:0 ./scripts/deploy.sh in a US region).
When you are done:
./scripts/teardown.sh
What the call is actually doing
The lab is five lines of Python, but the shape of those lines carries most of what matters about Bedrock:
- Invocation is an SDK call, not infrastructure. For on-demand inference there is nothing to provision and nothing to keep warm; AWS runs the model and you pay per token. The service boundary is IAM, the same as S3 or DynamoDB.
- Two separate gates must both be open. Model access is enabled per model, per region, in the console;
bedrock:InvokeModelis granted per model ARN in IAM. Passing one and not the other produces errors that look similar and have different fixes. - The Converse API is one request shape across providers. The handler never inspects which model it is talking to. Swap
MODEL_IDfor another model and the code is unchanged, which is what makes model choice a configuration decision rather than a rewrite. - The permission scope is worth reading once. The role allows
bedrock:InvokeModelon foundation models in any region, plus the inference profiles in your account, because some models are only reachable through a profile and a cross-region profile is authorised against the foundation model in every region it can route your call to. That distinction shows up again the moment you leaveus-east-1.
What’s worth remembering
- On-demand Bedrock inference is an SDK call with IAM permission; there is no endpoint to stand up.
- Model access (console, per region) and
bedrock:InvokeModel(IAM, per model ARN) are two separate gates, and you need both. - The Converse API gives one message shape across model providers, so changing models is a config change.
- The reply text lives at
output.message.content[0].text; everything else in the response is metadata you will care about later (token counts, stop reason). inferenceConfigis where the sampling controls live:maxTokenscaps the answer,temperaturetrades determinism for variety.- Some models are only served through a cross-region inference profile; the
ValidationExceptionabout on-demand throughput is the tell. - Tear the stack down when you finish a session; the function is free at rest but the habit is what keeps labs from becoming bills.