AI EngineeringZero to ProductionHome·About·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.
▶ 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

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.

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

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.

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

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.

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: STARTINGIN_PROGRESSCOMPLETE

💡 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