
Build a Serverless RAG Pipeline on AWS — From Zero to Production in One Day
A step-by-step guide to building a fully serverless RAG pipeline on AWS using Lambda, Amazon Bedrock (Claude 3.5 Sonnet + Titan Embed v2), OpenSearch Serverless, and API Gateway — zero idle cost, deploys in one command, scales to 10,000 queries automatically.
The Problem With Most RAG Tutorials
Most RAG tutorials show you how to build something that works on your laptop. They skip the part where you actually need it to:
- Handle documents uploaded at any time, automatically
- Scale from 0 to 10,000 queries without you touching anything
- Cost $0 when nobody is using it
- Deploy in one command
This tutorial builds the real thing — a fully serverless RAG pipeline on AWS that you can deploy today and use in production tomorrow.
What We're Building
1
2
3
4
5
6
7
8
9
10
11
📄 User uploads PDF/TXT to S3
↓
🔧 Lambda (Ingest) — triggered automatically
↓ chunks text, calls Titan Embed via Bedrock
🗄️ OpenSearch Serverless — stores vectors
↓
🌐 API Gateway — POST /query
↓
🔧 Lambda (Query) — kNN search → Claude 3.5 Sonnet
↓
💬 Answer + sources returned to user| Service | Role | Cost model |
|---|---|---|
| S3 | Document storage + trigger | Per GB stored |
| Lambda (×2) | Ingest + Query logic | Per invocation |
| Bedrock Titan Embed v2 | Text → vectors | Per 1K tokens |
| Bedrock Claude 3.5 Sonnet | Answer generation | Per token |
| OpenSearch Serverless | Vector store (kNN) | Per OCU-hour |
| API Gateway | HTTP endpoint | Per request |
Architecture Deep-Dive
Why Serverless for RAG?
Traditional RAG stacks run a persistent server — an EC2 instance, an ECS container — that sits idle 90% of the time eating money. With this architecture:
- Ingest Lambda wakes up only when a file lands in S3 — done in seconds, costs fractions of a cent per document
- Query Lambda wakes up only when someone asks a question — cold start on Python 3.12 with arm64 is under 300ms
- OpenSearch Serverless scales OCUs up and down automatically
- Total idle cost: ~$0/month
The Chunking Strategy
1
2
3
4
5
6
7
8
9
10
def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
words = text.split()
chunks = []
start = 0
while start < len(words):
end = start + chunk_size
chunk = " ".join(words[start:end])
chunks.append(chunk)
start += chunk_size - overlap
return [c for c in chunks if len(c.strip()) > 50]Tuning tips:
- Dense technical docs → smaller chunks (300 words), more overlap (100)
- Long-form prose → larger chunks (700 words), less overlap (30)
- Q&A documents → chunk by question, not by word count
Embedding with Amazon Titan Embed v2
1
2
3
4
5
6
7
8
9
10
11
12
13
def get_embedding(text: str) -> list[float]:
body = json.dumps({
"inputText": text,
"dimensions": 1024,
"normalize": True # unit-normalize for cosine similarity
})
response = bedrock.invoke_model(
modelId="amazon.titan-embed-text-v2:0",
body=body,
contentType="application/json",
accept="application/json",
)
return json.loads(response["body"].read())["embedding"]Cost: $0.00002 per 1,000 tokens. A 100-page PDF with ~200 chunks costs $0.0026 — less than a cent.
kNN Search in OpenSearch Serverless
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mapping = {
"settings": {"index": {"knn": True}},
"mappings": {
"properties": {
"embedding": {
"type": "knn_vector",
"dimension": 1024,
"method": {
"name": "hnsw",
"space_type": "cosinesimil",
"engine": "nmslib",
}
},
"text": {"type": "text"},
"source_key": {"type": "keyword"},
"chunk_index": {"type": "integer"},
}
}
}Answer Generation with Claude 3.5 Sonnet
1
2
3
4
SYSTEM_PROMPT = """You are a helpful assistant that answers questions using ONLY
the provided context. If the answer is not in the context, say
"I don't have enough information to answer that." Be concise, accurate,
and cite which document section your answer comes from."""Deploy in 4 Steps
Prerequisites:
- AWS CLI configured (
aws configure) - SAM CLI installed (
brew install aws-sam-cli) - Bedrock model access enabled for Claude 3.5 Sonnet + Titan Embed v2
- An OpenSearch Serverless collection created (type: VectorSearch)
Step 1 — Create OpenSearch Serverless Collection
AWS Console → OpenSearch Serverless → Create collection → Type: Vector search → Name:
rag-collectionStep 2 — Clone and Deploy
1
2
3
git clone https://github.com/your-repo/serverless-rag
cd serverless-rag
./scripts/deploy.sh https://YOUR-OPENSEARCH-ENDPOINT devStep 3 — Upload a Document
1
aws s3 cp company_handbook.pdf s3://rag-documents-dev-123456789/Step 4 — Query It
1
2
3
curl -X POST https://YOUR-API-ID.execute-api.us-east-1.amazonaws.com/dev/query \
-H "Content-Type: application/json" \
-d '{"question": "What is the parental leave policy?"}'Response:
1
2
3
4
5
6
{
"answer": "The company offers 16 weeks of paid parental leave for all employees. [Source: company_handbook.pdf, section 3]",
"sources": [
{"file": "company_handbook.pdf", "chunk": 42, "score": 0.9341}
]
}Testing Without AWS
1
2
pip install opensearch-py requests-aws4auth pypdf pytest boto3
pytest tests/test_local.py -vAll 9 tests mock AWS entirely — no credentials needed:
1
2
3
4
5
6
7
8
9
10
✅ TestChunkText::test_basic_chunking PASSED
✅ TestChunkText::test_short_text_no_crash PASSED
✅ TestChunkText::test_empty_text PASSED
✅ TestIngestHandler::test_successful_ingest PASSED
✅ TestIngestHandler::test_empty_file_skipped PASSED
✅ TestQueryHandler::test_cors_preflight PASSED
✅ TestQueryHandler::test_empty_question_400 PASSED
✅ TestQueryHandler::test_successful_query PASSED
✅ TestQueryHandler::test_no_results PASSED
9 passed in 0.49sCost Estimate
For a small internal tool (1,000 queries/month, 50 documents):
| Service | Monthly cost |
|---|---|
| Lambda (ingest + query) | ~$0.002 |
| Bedrock Titan Embed | ~$0.05 |
| Bedrock Claude 3.5 Sonnet | ~$1.50 |
| OpenSearch Serverless | ~$24.00 |
| S3 + API Gateway | ~$0.01 |
| Total | ~$25.56/month |
Cost tip: For under 100 queries/month, replace OpenSearch Serverless with pgvector on Aurora Serverless v2 — true scale-to-zero, ~$0 at idle.
What to Build Next
- Authentication — Add a Cognito User Pool authorizer to API Gateway
- Metadata filtering — Tag documents by department/category and filter search results
- Re-ranking — Use a cross-encoder after kNN retrieval (improves quality ~20%)
- Streaming responses —
bedrock.invoke_model_with_response_stream()+ WebSocket API - Multi-modal — Titan Multimodal Embeddings for images and PDF charts
Conclusion
You just built a production-ready serverless RAG pipeline that:
- Costs nothing at idle — Lambda and Bedrock bill per invocation only
- Scales automatically — no capacity planning needed
- Deploys in one command — SAM handles all the AWS wiring
- Works with any text file — PDF, TXT, Markdown
Read the full breakdown on Medium → iamsanko.medium.com/serverless-rag-pipeline-on-aws-55b20cf43a04
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article