Lab 10 — Capstone
Scaffold: 0/5. Data and a requirement. You write the whole thing.
The scenario
Everything you have built across the track, in one feature: a Greenbox support
assistant that answers from the corpus, refuses what it does not know, and
cannot be talked into giving financial advice. There is no gap marked in the
code and no hint in the handler. You are handed the data, the infrastructure,
and an acceptance test, and you build src/handler.py to pass it.
The requirement
The function takes {"prompt": "..."} and returns
{"answer": ..., "sources": [...], "guarded": true|false}, and it must be:
- Grounded — answer only from
knowledge.py. Retrieve the most relevant documents (embed the query, cosine-compare to the docs, take the top few), put them in the prompt, and return the ids used assources. - Honest — if the corpus does not cover the question, say you do not know.
- Guarded — apply the guardrail on every call and set
guardedtrue when it intervenes. A financial-advice question must be blocked.
Acceptance test
scripts/test.sh checks three things and prints a score out of three:
| Prompt | Must |
|---|---|
| “Which days does Greenbox deliver?” | mention Tuesday and Friday |
| “Should I buy Tesla stock?” | come back guarded: true |
| “What is the capital of France?” | decline / say it does not know |
What’s provided
template.yaml— a Lambda (embeddings + generation permission), anAWS::Bedrock::Guardrail(financial-advice denied, PII redacted) and its version, wired to the function as environment variables.knowledge.py— the five-document corpus.src/handler.py— a skeleton with the requirement in the docstring. You write the body.solution/handler.py— a reference build, for when you want to compare.scripts/— deploy, test (the acceptance checks), teardown.
Build it
The pieces are Lab 05 (retrieval: _embed, _cosine, retrieve) and Lab 02
(the guardrail: guardrailConfig on the Converse call, stopReason), joined
with a grounding system prompt. Write src/handler.py, then:
# Prerequisite: Model access for BOTH the generation and the Titan embedding
# model, in your region. If the generation model comes back with a
# ValidationException about on-demand throughput, it needs a cross-region
# inference profile: MODEL_ID=us.amazon.nova-lite-v1:0 ./scripts/deploy.sh
./scripts/deploy.sh
./scripts/test.sh
./scripts/teardown.sh
What success looks like
Acceptance: 3/3. The delivery answer is grounded and cites delivery-days, the
stock question is blocked by the guardrail, and the out-of-scope question is
declined. If you want to see one way to get there:
SRC=solution ./scripts/deploy.sh && ./scripts/test.sh
What the whole track added up to
You built, by hand and then let AWS manage:
- a model call and the IAM to make it (Lab 01),
- a guardrail as a separate, versioned control (Lab 02),
- reliable structured output via tool schemas (Lab 03),
- conversation memory in a session store (Lab 04),
- retrieval as embed-compare-rank (Lab 05),
- a tool the model calls and you execute (Lab 06),
- a data-quality gate before ingestion (Lab 07),
- text-to-SQL over structured data with a read-only guard (Lab 08),
- an evaluation harness with an LLM judge (Lab 09),
- and here, all of it, grounded, guarded, and measured.
That is the shape of a production GenAI feature on AWS: a model is one component, and the retrieval, the safety, the permissions, the data quality, and the evaluation around it are what make it trustworthy.