AI EngineeringZero to ProductionHome·About·What’s new·Contact
AWS AI Automation · Chapter W4

Knowledge Bases (managed RAG)

A Bedrock Knowledge Base is Ch 3's RAG pipeline, managed: embeddings, vector store, chunking and retrieval behind two calls. You bring S3 docs; AWS keeps the index in sync.

⏱️ ~2 hours🧪 3 labs🎯 Intermediate→Advanced
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • AWS credentials (aws configure) + Bedrock model access enabled in your region + pip install boto3
  • AWS credentials (aws configure) + pip install boto3
Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.

Learning objectives

  • Explain what a Bedrock Knowledge Base gives you over hand-rolled RAG (Ch 3).
  • Create a KB backed by OpenSearch Serverless and ingest documents from S3.
  • Query with Retrieve (chunks only) and RetrieveAndGenerate (answer).
  • Decide when managed RAG beats owning the pipeline yourself.
📋 Before you startThis page builds on: Bedrock & Claude · RAG foundations. Jumping in? Skim those first — or open the recap.
30-second recap
  • Bedrock & Claude: Amazon Bedrock hosts Claude and other models behind AWS IAM and billing; you call them with the AWS SDK instead of a vendor API key.
  • RAG foundations: Retrieval-augmented generation: find the relevant passages first, then give them to the model as context, so answers come from your documents instead of memory.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/aws4-bedrock-kb-rag/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

Managed RAG vs. the hand-built pipeline essential

Here's the moment this chapter exists for. You shipped the Ch 3 RAG pipeline and it worked beautifully — for a week. Then someone updated a support doc and nobody re-ran the embedding script, so the bot kept quoting the old refund policy. Then the vector store filled its disk. Then a new hire asked "where do I change the chunk size?" and the answer was buried in three files. None of that was a retrieval problem; it was an operations problem. The retrieval logic you wrote was fine — keeping it alive in production was the tax you didn't budget for. A managed Knowledge Base is AWS offering to pay that tax for you.

The clean way to think about it: RAG has two halves. There's the algorithm — chunk the text, embed each chunk, store the vectors, find the nearest ones to a query, stuff them into a prompt — and there's the plumbing that keeps that algorithm running: a vector database that's provisioned, patched, backed up, and scaled; a job that re-embeds documents when they change; an index that stays consistent. Ch 3 taught you the algorithm by making you build both halves by hand. A Bedrock Knowledge Base keeps the exact same algorithm but hands the plumbing to AWS. You point it at documents in S3, name an embeddings model and a vector store, and from then on you interact through essentially two calls — one to retrieve, one to retrieve-and-answer.

Think of it like the difference between running your own Postgres server and using a managed database. The queries you write are identical; what changes is that you stop being paged at 3 a.m. about disk space and replication. That analogy is exactly why this is worth its own chapter and not a footnote: the decision to go managed is a decision about who owns the operational burden, not about whether RAG works differently. The retrieval quality is still governed by the same knobs you learned in Ch 3 — you'll just set them as configuration instead of writing them as code.

The common mistake here is to read "managed" as "magic" and assume the Knowledge Base will also fix bad retrieval. It won't. If your documents are poorly structured, your chunks too big, or your query vague, a managed KB gives you the same mediocre answers your hand-built pipeline would — just with less code to blame. Managed RAG removes the plumbing toil; it does not remove the need to understand what makes retrieval good. Keep Ch 3's mental model in your head as you read: everything below is that model, wearing an AWS uniform.

In Ch 3 you built RAG from scratch: chunk, embed, store, retrieve, stuff the prompt. A Bedrock Knowledge Base manages all of that — embeddings, a vector store, chunking, and retrieval — behind two API calls. You bring documents in S3; AWS keeps the index in sync.

same algorithm both ways — what changes is who owns the plumbing Hand-built (Ch 3) Managed Knowledge Base ALGORITHM — you write it chunk → embed → store → retrieve stuff the prompt PLUMBING — you also own it provision · patch · back up · scale re-embed on change · keep index in sync ALGORITHM — unchanged chunk → embed → store → retrieve set as config, not code PLUMBING — AWS owns it provisioned, patched, scaled for you the red box is the tax you stop paying Managed RAG moves the plumbing, not the algorithm. Both columns run the identical chunk→embed→store→retrieve loop; the only thing a Knowledge Base changes is that the red "plumbing" row — provisioning, patching, re-embedding, scaling — becomes AWS's job instead of yours.
🗺️ How to read this diagram

Compare the two columns side by side: hand-built RAG on the left, a managed Knowledge Base on the right. Each column is split into a top row (the algorithm) and a bottom row (the plumbing).

  • The top row is the same in both columns — chunk, embed, store, retrieve. That's the point: managed RAG does not change what RAG does, only how you express it (code on the left, config on the right).
  • The bottom row is where the columns diverge. On the left it's a red box you own: provisioning, patching, backups, scaling, and re-embedding when documents change. On the right that same row turns green — AWS owns it.
  • The arrow between the columns is the move you make by going managed: you hand the red plumbing box to AWS. The green box is the "aha" — the operational tax you stop paying.

In short: the retrieval algorithm is identical either way; choosing a managed KB is a decision about who owns the plumbing, not about whether RAG works differently.

You still need Ch 3's mental modelManaged RAG hides the machinery, not the tradeoffs. Chunk size, retrieval count, and reranking still decide answer quality — you just tune them as config instead of code.

Create the KB and ingest essential

The first time people stand up a Knowledge Base, they hit the same wall — and it's instructive. They call create_knowledge_base, get back an ID, feel triumphant, immediately fire a query… and get nothing back. The KB is empty. What went wrong is the single most important idea in this section: creating a Knowledge Base and filling it with your documents are two separate steps. Creation builds the container; a later ingestion job is what actually reads your files, embeds them, and loads the index. Miss that distinction and you'll spend an afternoon debugging an index that was never populated.

It helps to picture a KB as a library that you're commissioning. Three things have to be decided before the building can hold a single book: where the books come from (an S3 data source — your bucket of documents), who translates them into a searchable form (an embeddings model, which turns each chunk of text into a vector of numbers capturing its meaning), and the shelving system that stores and finds those vectors (a vector store — OpenSearch Serverless is Bedrock's default). The create_knowledge_base call is you signing the commission with all three specified. But signing the paperwork doesn't move any books in — that's the ingestion job, which comes next.

One more thing that trips newcomers and explains a design choice later in the track: these pieces have an ordering dependency. The vector store and the IAM role the KB uses to reach your S3 bucket must already exist before you create the KB, because the KB immediately tries to wire itself to them. Get the order wrong and the call fails. That fragile hand-assembly — create the collection, then the role, then the KB, then trigger ingestion, each depending on the last — is precisely why production teams stop clicking through boto3 and reach for infrastructure-as-code (W7), where the tool works out the dependency order for you. Reading this section by hand teaches you what those Terraform resources are actually doing under the hood.

The other detail worth internalizing now: ingestion is asynchronous and repeatable. The job returns immediately with a status like STARTING and churns through your documents in the background — you don't sit and wait for it. And because it's a job you trigger rather than a magic live sync, you must re-run it every time the underlying S3 documents change. A Knowledge Base is only as fresh as its last ingestion; nothing about "managed" means "automatically watching your bucket."

A KB needs: an S3 data source, an embeddings model, and a vector store (OpenSearch Serverless is the default). We create it here with boto3; W7 shows the Terraform/CDK version you would actually use in production.

create builds the empty container — a separate ingestion job fills it, asynchronously S3 data source embeddings model vector store + IAM role must exist first create_knowledge_base bedrock-agent (control plane) → KB id (empty) start_ingestion_job — embeds + indexes S3 runs in the background; re-run whenever docs change then trigger STARTING IN_PROGRESS COMPLETE Create and fill are two separate steps. The three inputs plus a pre-existing vector store and IAM role feed create_knowledge_base, which returns an empty KB id; only the later start_ingestion_job reads S3, embeds, and indexes — and it runs asynchronously through STARTING → IN_PROGRESS → COMPLETE.
🗺️ How to read this diagram

Follow the solid blue arrows left to right along the top, then drop down the dashed arrow to the ingestion job and its status track along the bottom.

  • The three boxes on the far left are the KB's ingredients: an S3 data source, an embeddings model, and the vector store + IAM role. The amber box is flagged "must exist first" — the store and role are the ordering dependency the section warns about.
  • They all feed create_knowledge_base on the bedrock-agent control plane, which returns a green KB id — but it's empty. Signing the paperwork moved no documents in.
  • The dashed arrow down is the separate start_ingestion_job call. It runs in the background, which is why the bottom track walks STARTING → IN_PROGRESS → COMPLETE with dashed "still working" arrows rather than finishing instantly.

In short: creating a KB gives you an empty container; a separate, asynchronous ingestion job is what actually indexes your docs — and you re-run it every time they change.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab W4.1
create_kb.pyimport boto3

agent = boto3.client("bedrock-agent", region_name="us-east-1")  # KB control plane

kb = agent.create_knowledge_base(
    name="support-docs",
    roleArn="arn:aws:iam::123456789012:role/bedrock-kb-role",   # needs S3 + OpenSearch access
    knowledgeBaseConfiguration={
        "type": "VECTOR",
        "vectorKnowledgeBaseConfiguration": {
            "embeddingModelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"
        },
    },
    storageConfiguration={
        "type": "OPENSEARCH_SERVERLESS",
        "opensearchServerlessConfiguration": {
            "collectionArn": "arn:aws:aoss:us-east-1:123456789012:collection/abc123",
            "vectorIndexName": "support-index",
            "fieldMapping": {"vectorField": "vec", "textField": "text", "metadataField": "meta"},
        },
    },
)
print("KB id:", kb["knowledgeBase"]["knowledgeBaseId"])
▶ How this works

This creates a Knowledge Base (KB) — Amazon's managed version of the RAG pipeline you hand-built in Ch 3. Instead of writing code to chunk, embed and store documents, you describe what you want and AWS runs the machinery. This one block declares all three pieces a KB needs: where documents live, which model turns text into vectors, and where those vectors are stored.

  1. boto3.client("bedrock-agent", ...) opens a connection to the AWS service that manages Knowledge Bases (the "control plane" — the part that creates and configures things). region_name="us-east-1" picks the AWS data-centre region.
  2. agent.create_knowledge_base(...) is the single call that builds the KB. Everything inside the parentheses is configuration passed as named arguments.
  3. name is a human label; roleArn is an AWS IAM role — an identity with permission to read your S3 documents and write to the vector store. The KB acts as this role when it works.
  4. knowledgeBaseConfiguration says the KB is a VECTOR store and names the embedding model (amazon.titan-embed-text-v2) — the model that converts each chunk of text into a list of numbers (a vector) capturing its meaning.
  5. storageConfiguration points at OpenSearch Serverless, the default vector database. fieldMapping tells it which fields hold the vector, the original text, and metadata. collectionArn is the unique address of that store.
  6. print("KB id:", ...) pulls the new KB's ID out of the reply. You'll reuse this ID in every later call, so note it down.

What the output means: On success AWS prints something like KB id: KB123. That ID is the handle you pass to the ingestion and query calls below. The ARNs and IDs shown are placeholders — swap in your own.

Try this: Read the three sub-configs top to bottom and match them to the KB's three needs: S3 docs (added next), embedding model (embeddingModelArn), and vector store (opensearchServerlessConfiguration). Seeing all three named here is the whole point.

Provisioning order mattersThe OpenSearch Serverless collection and the IAM role must exist before the KB. This dependency chain is exactly why W7 does it in Terraform — the tool resolves order for you.

After attaching an S3 data source you trigger an ingestion job to embed and index. Re-run it whenever the documents change.

Lab W4.2
ingest.pyimport boto3
agent = boto3.client("bedrock-agent", region_name="us-east-1")

job = agent.start_ingestion_job(
    knowledgeBaseId="KB123",
    dataSourceId="DS456",
)
print("ingestion job:", job["ingestionJob"]["status"])   # STARTING -> IN_PROGRESS -> COMPLETE
▶ How this works

Creating the KB in W4.1 built the empty container. Your documents are not indexed yet. An ingestion job is the step that actually reads the files from S3, chunks them, calls the embedding model on each chunk, and writes the resulting vectors into OpenSearch. You run this once after setup, and again every time the documents change.

  1. We reconnect to bedrock-agent — the same control-plane client as W4.1, because starting an ingestion job is a management action.
  2. agent.start_ingestion_job(...) kicks off the indexing work. knowledgeBaseId is the KB from W4.1; dataSourceId identifies the S3 data source you attached to it (KB123 / DS456 are placeholders).
  3. The job runs asynchronously — the call returns immediately with a starting status rather than waiting for all documents to finish. print(...job["ingestionJob"]["status"]) shows that first status.

What the output means: You'll see a status like STARTING. Behind the scenes it moves STARTING → IN_PROGRESS → COMPLETE (the comment spells this out). You would poll the job again later, or check the console, to confirm it reached COMPLETE before querying.

Try this: Think about why re-running matters: if you add a new support doc to S3 tomorrow, the KB won't know about it until you start another ingestion job. Managed RAG syncs on demand, not by magic.

Query: Retrieve vs RetrieveAndGenerate intermediate

Now the index is full, and you face a fork that quietly decides how much of the RAG pipeline you still own. Picture two engineers on the same team. One is building a quick internal help-desk bot and just wants a grounded answer with sources — done by lunch. The other is building a regulated compliance assistant that needs a hand-crafted system prompt, a custom reranking step, and a specific model with tuned parameters. Both are querying the same Knowledge Base, but they should not use the same call. Bedrock gives you two doors, and picking the wrong one means either fighting a black box or rebuilding machinery you didn't need to.

The mental model is "how much of the last mile do you want to drive yourself." retrieve is the short door: it does the vector search and hands you back the matching chunks — raw text and their source locations — and then stops. Generation is now entirely your job. You assemble those chunks into whatever prompt you like, choose whatever model you like, and call it yourself. This is the choice when you need control: your own system prompt, your own reranking, your own template. retrieve_and_generate is the long door: one call retrieves the chunks, feeds them to the model you name, and returns a finished natural-language answer plus citations that point back to the source documents. It's the whole RAG loop collapsed into a single request.

Here's the analogy that makes the trade stick: retrieve is buying the ingredients, retrieve_and_generate is ordering the finished dish. If you just want to eat, order the dish — it's faster and it arrives plated with a receipt (the citations) showing exactly which documents it was made from. If you're a chef with a specific recipe in mind, you want the raw ingredients so you control the seasoning. Neither is "better"; they sit at opposite ends of the convenience-versus-control spectrum, and a good engineer reaches for whichever the task actually demands.

The subtle point most people miss: the citations that retrieve_and_generate returns are not decoration — they're the feature that makes managed RAG trustworthy. An answer you can't trace back to a source is, for anything auditable, barely better than a guess. Because the citations name the exact chunks used, you can enforce a hard rule in your own code — "if there are no citations, refuse to answer" — which is far stronger than politely asking the model to cite. Retrieval grounds the answer; the citations prove the grounding. That's the quiet reason teams pick a managed KB for compliance work even when they could have hand-built the retrieval themselves.

retrieve returns the matching chunks — use it when you want to control the prompt (feed chunks to Claude yourself). retrieve_and_generate does retrieval + answering in one call, with citations.

same vector search — the fork decides how much of the last mile you drive vector search over KB bedrock-agent-runtime retrieve — short door returns raw chunks, then STOPS retrieve_and_generate one call: retrieve + answer + cite YOU build the prompt, pick the model, rerank — full control finished answer + citations pointing back to source docs Two doors off the same search. retrieve (amber) hands you the raw chunks and stops — you own the prompt, model, and reranking; retrieve_and_generate (green) carries on to a finished answer with citations in a single call. Convenience versus control, nothing more.
🗺️ How to read this diagram

Start at the top box and follow the two arrows down — both branches begin from the same vector search over the Knowledge Base, then split into the two call styles.

  • The left (amber) branch is retrieve, the "short door": it returns the raw matching chunks and stops. The box below shows what's now on you — building the prompt, choosing the model, doing your own reranking.
  • The right (green) branch is retrieve_and_generate, the "long door": one call retrieves, feeds the chunks to the model you name, and returns a finished answer. Its box below is the payoff — an answer plus citations that point back to the source documents.
  • The split itself is the "aha": both start from identical retrieval, so the choice is purely convenience versus control — reach for whichever the task demands.

In short: retrieve is buying ingredients (you cook); retrieve_and_generate is ordering the plated dish with a receipt (the citations).

Lab W4.3
query_kb.pyimport boto3
rt = boto3.client("bedrock-agent-runtime", region_name="us-east-1")   # KB data plane

# One-call RAG: retrieves, then answers with Claude, with citations.
resp = rt.retrieve_and_generate(
    input={"text": "How do I reset a customer's password?"},
    retrieveAndGenerateConfiguration={
        "type": "KNOWLEDGE_BASE",
        "knowledgeBaseConfiguration": {
            "knowledgeBaseId": "KB123",
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
        },
    },
)
print(resp["output"]["text"])
for c in resp["citations"]:
    for ref in c["retrievedReferences"]:
        print("  source:", ref["location"])
▶ How this works

Now we ask a question against the indexed KB. Bedrock offers two query styles: retrieve returns just the matching chunks (you then write your own prompt to Claude), and retrieve_and_generate — used here — does the whole RAG loop in one call: find relevant chunks, feed them to Claude, and return a written answer plus citations pointing back to the sources.

  1. boto3.client("bedrock-agent-runtime", ...) connects to the data plane — the part that uses the KB to answer queries (as opposed to the control plane that built it). Different job, different client.
  2. rt.retrieve_and_generate(...) is the one-call RAG. input={"text": "How do I reset a customer's password?"} is the user's question in plain English.
  3. retrieveAndGenerateConfiguration tells it how to answer: type: KNOWLEDGE_BASE means "use a KB", knowledgeBaseId picks which KB, and modelArn chooses the model that writes the answer — here Claude 3.5 Sonnet.
  4. print(resp["output"]["text"]) prints Claude's final written answer, grounded in your documents.
  5. The for c in resp["citations"] loop walks the citations. Each citation lists retrievedReferences — the actual document chunks used — and we print each ref["location"], i.e. where that fact came from. This is how you prove the answer isn't made up.

What the output means: You get a natural-language answer to the password question, followed by one or more source: lines showing which S3 documents backed it. Citations are the key advantage of managed RAG — grounding and traceability come free.

Try this: Decide which call you'd want for two tasks: (1) a chatbot that shows a polished answer — retrieve_and_generate; (2) a custom prompt where you control every instruction to Claude — plain retrieve, then build the prompt yourself.

Exercise W4.1 — Compare to Ch 3

Context: The honest way to judge a managed Knowledge Base is to race it against the hand-built RAG pipeline you already wrote, over the same documents. The interesting answer is rarely 'which is more accurate' — it's 'which cost less to maintain'.

Your task: Point a Bedrock KB at the same documents from your Ch 3 RAG lab, ask five questions through both retrieve_and_generate and your hand-built pipeline, and compare where they differ and which was less work to maintain.

Requirements:

  • Ingest the identical Ch 3 corpus into a Knowledge Base
  • Run the same five questions through both the managed KB and the hand-built pipeline
  • Compare answers and citations side by side, noting where they diverge
  • Judge each on operational cost — which required less code and upkeep
  • Draw a concrete conclusion about when the managed KB's lost control is worth the saved ops

💡 Hint: Hold the corpus and the questions fixed so the only variables are the retrieval stack and the maintenance effort — that's what makes the comparison fair.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · One-call RAG with RetrieveAndGenerateBeginner

Context: Bedrock Knowledge Bases collapse the entire RAG pipeline — retrieve, generate, and cite — into a single managed call. It's the fastest path from 'documents in S3' to a grounded answer without building any retrieval yourself.

Your task: Ask a question against a Knowledge Base with retrieve_and_generate and print the grounded answer.

Requirements:

  • Use the bedrock-agent-runtime client (the KB data plane)
  • Pass the question via input={"text": ...}
  • Configure type: "KNOWLEDGE_BASE" with a knowledgeBaseId and a generation model ARN
  • Print the grounded answer from output.text

💡 Hint: Note the third client family: management is bedrock-agent, plain inference is bedrock-runtime, but KB queries go through bedrock-agent-runtime.

Show solution

bedrock-agent-runtime is the data plane. retrieve_and_generate retrieves, answers, and cites in one call.

import boto3

rt = boto3.client("bedrock-agent-runtime", region_name="us-east-1")
resp = rt.retrieve_and_generate(
    input={"text": "What is our refund window?"},
    retrieveAndGenerateConfiguration={
        "type": "KNOWLEDGE_BASE",
        "knowledgeBaseConfiguration": {
            "knowledgeBaseId": "KB123456",
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/"
                        "anthropic.claude-3-5-sonnet-20241022-v2:0",
        },
    },
)
print(resp["output"]["text"])
Exercise 2 · Print the citationsIntermediate

Context: A grounded answer you can't trace isn't much better than a guess. Knowledge Bases return citations that map each answer back to the exact source chunks in S3 — surfacing them is what makes the answer auditable.

Your task: Extend the retrieve_and_generate call to also print the source document location of each citation.

Requirements:

  • Keep printing the generated answer from output.text
  • Iterate the citations list on the response
  • For each citation, walk its retrievedReferences
  • Print each reference's location (the source S3 doc)

💡 Hint: Citations are nested two levels deep — a list of citations, each holding a list of retrievedReferences, each with its own location.

Show solution

Citations trace the answer back to source S3 docs via retrievedReferences[].location.

import boto3

rt = boto3.client("bedrock-agent-runtime", region_name="us-east-1")
resp = rt.retrieve_and_generate(
    input={"text": "What is our refund window?"},
    retrieveAndGenerateConfiguration={
        "type": "KNOWLEDGE_BASE",
        "knowledgeBaseConfiguration": {
            "knowledgeBaseId": "KB123456",
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/"
                        "anthropic.claude-3-5-sonnet-20241022-v2:0",
        }})
print(resp["output"]["text"])
for c in resp.get("citations", []):
    for ref in c.get("retrievedReferences", []):
        print("source:", ref["location"])
Exercise 3 · Retrieve-only for custom promptingAdvanced

Context: Sometimes the managed generation is too rigid — you want your own system prompt, reranking, or model. Retrieve hands you just the raw chunks so you own everything downstream, trading convenience for control.

Your task: Use retrieve (not retrieve_and_generate) to fetch raw chunks, build your own prompt from them, and explain when you'd choose this split.

Requirements:

  • Call retrieve with a knowledgeBaseId and a vectorSearchConfiguration (e.g. numberOfResults)
  • Pull the chunk text out of retrievalResults[].content.text
  • Assemble the chunks into your own context/prompt to send to converse with your chosen model
  • Justify the choice: pick retrieve-only when you need control over prompt, reranking, or model that the managed call hides

💡 Hint: retrieve stops at the chunks — generation is now your job, which is exactly the point when the one-call version is too much of a black box.

Show solution

retrieve returns chunks only, so you control the prompt, reranking, and model. Choose it when the managed generation is too rigid.

import boto3

rt = boto3.client("bedrock-agent-runtime", region_name="us-east-1")
r = rt.retrieve(
    knowledgeBaseId="KB123456",
    retrievalConfiguration={"vectorSearchConfiguration": {"numberOfResults": 5}},
    retrievalQuery={"text": "refund window"},
)
chunks = [x["content"]["text"] for x in r["retrievalResults"]]
prompt = "Context:\n" + "\n---\n".join(chunks) + "\n\nQuestion: ..."
# then send `prompt` to converse() with your own system prompt/model
print(len(chunks), "chunks retrieved")
Exercise 4 · Create a KB and start ingestionExpert

Context: Standing up a Knowledge Base wires three things in order — an S3 data source, an embedding model, and a vector store — and then ingestion runs asynchronously. Knowing it's async is what stops you from querying an empty index.

Your task: Sketch create_knowledge_base with a Titan embedding model plus an OpenSearch Serverless store, kick off an ingestion job, and note its async status lifecycle.

Requirements:

  • Use the bedrock-agent control-plane client and supply a roleArn
  • Configure a VECTOR KB with an embedding model ARN (e.g. amazon.titan-embed-text-v2:0)
  • Point storageConfiguration at an OpenSearch Serverless collection with a vector index and field mapping
  • Start ingestion with start_ingestion_job and read its status
  • Recognize the async lifecycle: STARTING → IN_PROGRESS → COMPLETE

💡 Hint: Creation returns immediately with a KB id, but the documents aren't queryable until the separate ingestion job finishes — poll its status, don't assume.

Show solution

A KB needs three things wired in order: an S3 data source, an embedding model ARN, and a vector store. Ingestion is async.

import boto3

agent = boto3.client("bedrock-agent", region_name="us-east-1")
kb = agent.create_knowledge_base(
    name="support-kb",
    roleArn="arn:aws:iam::123:role/kb-role",
    knowledgeBaseConfiguration={"type": "VECTOR",
        "vectorKnowledgeBaseConfiguration": {"embeddingModelArn":
            "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"}},
    storageConfiguration={"type": "OPENSEARCH_SERVERLESS",
        "opensearchServerlessConfiguration": {
            "collectionArn": "arn:aws:aoss:...",
            "vectorIndexName": "kb-index",
            "fieldMapping": {"vectorField":"v","textField":"t","metadataField":"m"}}},
)
kb_id = kb["knowledgeBase"]["knowledgeBaseId"]
job = agent.start_ingestion_job(knowledgeBaseId=kb_id, dataSourceId="DS123")
print(job["ingestionJob"]["status"])   # STARTING -> IN_PROGRESS -> COMPLETE
Exercise 5 · Managed KB vs hand-built RAG trade-offProfessional

Context: Managed KB versus hand-built RAG is a control-vs-ops trade, not a quality one. A KB owns chunk size, retrieval count, and reranking for you; you give up those knobs in exchange for far less operational work. Encoding the rule makes the decision defensible.

Your task: Encode rag_choice(need_chunk_control, need_custom_rerank, want_low_ops) returning 'managed-kb' or 'hand-built', with a justification.

Requirements:

  • Needing chunk control or custom reranking forces 'hand-built' — those are knobs the KB hides
  • Wanting low ops (and no special control) favours 'managed-kb'
  • The default leans 'managed-kb' when nothing demands the extra control
  • Run offline; verify the branches with example inputs
  • Capture the trade-off in a comment: managed KB owns chunk/embed/store, you own less

💡 Hint: Check the control-requiring conditions first — if any knob the KB hides is required, hand-built wins before low-ops preference even enters the picture.

Show solution

A managed KB owns chunk size, retrieval count, and reranking; you trade that control for far less operational work.

def rag_choice(need_chunk_control, need_custom_rerank, want_low_ops):
    if need_chunk_control or need_custom_rerank:
        return "hand-built"     # you need the knobs the KB hides
    if want_low_ops:
        return "managed-kb"     # AWS owns chunk/embed/store
    return "managed-kb"

print(rag_choice(False, False, True))   # managed-kb
print(rag_choice(True,  False, True))   # hand-built
Exercise 6 · A citations-required RAG endpoint for complianceIndustry scenario

Context: For a compliance use case, an answer without a source is worse than no answer. Grounding is only enforceable if you actually inspect the citations and refuse when none came back — the model's confidence is not evidence.

Your task: Wrap retrieve_and_generate so any response with no citations is rejected with a safe refusal message instead of being returned.

Requirements:

  • Pull all retrievedReferences out of the response's citations
  • If there are zero references, return a safe refusal and mark it refused: True — do not surface the model's text
  • If references exist, return the answer plus the list of source locations and refused: False
  • The guard logic is pure and testable offline against fake grounded and ungrounded responses
  • Enforce the rule in code — asking the prompt to 'please cite' is not a guarantee

💡 Hint: The presence of a citation, not the wording of the answer, is your signal — flatten citations to their references and treat an empty list as an automatic refusal.

Show solution

Grounding is only enforceable if you check the citations; no source means no answer.

def enforce_citations(resp):
    cites = resp.get("citations", [])
    refs = [r for c in cites for r in c.get("retrievedReferences", [])]
    if not refs:
        return {"answer": "I can't answer without a cited source.",
                "sources": [], "refused": True}
    return {"answer": resp["output"]["text"],
            "sources": [r["location"] for r in refs], "refused": False}

# offline test with a fake grounded response and an ungrounded one:
grounded = {"output": {"text": "30 days."},
            "citations": [{"retrievedReferences": [{"location": "s3://kb/faq.md"}]}]}
print(enforce_citations(grounded)["refused"])                 # False
print(enforce_citations({"output":{"text":"guess"}})["refused"])  # True

✓ Checkpoint — you can move on when you can…

  • Explain what a Knowledge Base manages that Ch 3 made you build.
  • List the three things a vector KB needs (data source, embeddings model, vector store).
  • Choose between retrieve and retrieve_and_generate for a given task.
  • Explain why the provisioning order pushed us toward IaC (W7).

Knowledge check check yourself

✓ Knowledge check

A Bedrock Knowledge Base needs three things to exist as a vector store. What are they, and which client (control plane vs data plane) creates versus queries it?

Show answer
It needs an S3 data source, an embeddings model, and a vector store (OpenSearch Serverless by default). The bedrock-agent control-plane client creates the KB and starts ingestion; the bedrock-agent-runtime data-plane client queries it.
✓ Knowledge check

When would you use Retrieve versus RetrieveAndGenerate against a KB?

Show answer
Use retrieve when you want only the matching chunks so you can control the prompt to Claude yourself. Use retrieve_and_generate for one-call RAG that retrieves, answers with the model, and returns citations pointing back to the source documents.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in