Serverless RAG Architecture: Combining MongoDB Atlas Vector Search, Amazon Bedrock, and AWS Lambda
A Practical Guide to Building a Serverless RAG System with AWS Services and MongoDB Atlas
Building production-ready RAG (Retrieval-Augmented Generation) systems doesn't have to be complex. In this guide, we'll explore how to combine MongoDB Atlas Vector Search, Amazon Bedrock, and AWS Lambda to create an efficient and scalable RAG pipeline. By leveraging MongoDB's vector search capabilities, Bedrock's foundation models, and Lambda's serverless architecture, we'll demonstrate a practical approach to implementing RAG that can handle real-world workloads while keeping operational overhead minimal. Let's build a system that turns your documents into actionable AI-powered insights.
You may refer to the GitHub Link below for the code and Devpost Link for the Demo.
GitHub Link
RAG Architecture
Before I start explaining the implementation, let me talk about the RAG architecture.
This architecture presents a modern RAG (Retrieval-Augmented Generation) system implemented on AWS Cloud, combining several key components. In the upper section, it integrates AI21's LLM model (AI21-Jumbo-1.5-Mini) with Amazon Bedrock Knowledge Base, utilizing LangChain's RAG Chain Agentic Framework for orchestration. The Knowledge Base serves as an intelligent data layer that enhances the LLM's responses by providing relevant context from your stored documents – it indexes and organizes information from MongoDB Atlas, making it quickly accessible for the LLM to reference when generating responses. The system leverages MongoDB Atlas for document storage and vector search capabilities, enabling efficient similarity searches across your data.
For the backend, it implements a serverless architecture where user requests flow through API Gateway to AWS Lambda, which uses Docker containers and Mangum Handler to run a FastAPI application. This setup ensures scalable, efficient handling of RAG operations while maintaining a serverless infrastructure for cost-effectiveness and automatic scaling.
Tech Stack Used
Amazon Bedrock: Core LLM & knowledge base
Amazon S3 Storage : Storage for Data
MongoDB Atlas: Vector search & data storage
LangChain: RAG framework & chain management
FastAPI + Lambda: Serverless API
Docker: Containerization
AWS CDK: Infrastructure
Data Source Used
For this blog, Im using recipe data from TheMealDB website as data source. I created a script to ingest the data into the S3 storage. You can refer to the GitHub Repo for more details.
Setup
Setup the Python Environment for Local Development
git clone https://github.com/intelli-foods/intellifoods_backend.git
cd image
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Configure AWS
You need to have an AWS account, and AWS CLI set up on your machine. You'll also need to have Bedrock enabled on AWS (and granted model access to Claude or whatever you want to use). For my case, I used AI21-Jamba-1.5-Mini
Update .env File with AWS Credentials
Create a file named .env
in image/. Do NOT commit the file to .git. The file should have content like this:
AWS_ACCESS_KEY_ID=XXXXX
AWS_SECRET_ACCESS_KEY=XXXXX
AWS_REGION=us-east-1
This will be used by Docker for when we want to test the image locally. The AWS keys are just your normal AWS credentials and region you want to run this in (even when running locally you will still need access to Bedrock LLM).
Setup the Bedrock Knowledge Bases and MongoDB Atlas Vector Search
Prerequisties
set your AWS region into
us-east-1
region as Atlas Vector Search is currently available as a knowledge base only in AWS regions located in the United States.Create an Atlas M10+ cluster running MongoDB version 6.0.11, 7.0.2, or later.
Access to the following foundation models used in this project in your Bedrock:
Amazon Titan Embeddings G1 - Text
Jamba 1.5 Mini
Data Ingestion into S3 Bucket
Create the S3 Bucket that store the recipe data.
Run the Data Preparation Python Script to ingest the data and metadata.json into the S3 Bucket
cd image
python code/data_collection.py
metadata.json
is needed for metadata filtering
.
Metadata filtering is like adding smart labels to your documents to help find information more efficiently. Just as you would tag photos with dates, locations, or people's names, metadata filtering in Amazon Bedrock Knowledge Bases lets you attach properties like year, category, or version to your documents. When searching, you can use these tags to narrow down results before looking at the actual content - for example, filtering technical documentation to only show guides from 2023 for Windows 10, rather than searching through all versions and years. This pre-filtering helps you get more accurate and relevant results faster, while reducing processing costs.
For more details can refer this Amazon Bedrock Metadata Filtering Documentation
can check your S3 Bucket to ensure that the data is ingested correctly.
Create the Atlas Vector Search Index and Atlas Vector as a vector database in MongoDB Atlas
create a new database called recipe_db
create a new collection called recipe_vector
Note: the name can change accordingly.
go to Atlas Search page and define Atlas Vector Search index
put the index name as vector_index_recipe
and put the definition below for the index
{
"fields": [
{
"numDimensions": 1536,
"path": "embedding",
"similarity": "cosine",
"type": "vector"
},
{
"path": "metadata",
"type": "filter"
},
{
"path": "text_chunk",
"type": "filter"
}
]
}
Review the index definition and then Click Create Search Index then we are good to go.
Create a Bedrock Knowledge Base
go to the Amazon Bedrock Console and click Knowledge bases.
Click Create knowledge base and put its name as recipe-mongodb-atlas-knowledge-base
. You can change it accordingly.
Add a data source
Enter the URI for the S3 bucket that you created just now as the data source. For the chunking strategy, choose No Chunking as we have preprocessed the data in the data ingestion step already.
After that, choose Titan Embeddings G1-Text
as embedding models to convert the data source into vector embeddings.
Connect Atlas to the Knowledge Base
choose mongoDB Atlas
as the vector database.
Configuration for MongoDB Atlas as Vector DB in knowledge bases
- For the
Hostname
, enter the URL for your Atlas cluster located in its connection string. The hostname uses the following format:
<clusterName>.mongodb.net
For Database name, enter
recipe_db
For Collection name, enter
recipe_vector
For the Credentials secret ARN, enter the ARN for the secret that contains your Atlas cluster credentials.
AWS Secret Manager to store the Atlas Credentials
go to your AWS Secret Manager and store the Atlas Credentials in the format below.
Store in Key/Value
format
Secret Key | Secret Value |
username | "mongoDB atlas-username" |
password | "mongoDB atlas-password" |
then retrieve the Secret ARN
for this credentials and use it in the knowledge bases creation.
Metadata Field Mapping
In the Metadata field mapping section, configure the following options to determine the search index and field names that Atlas uses to embed and store your data source:
For the Vector search index name, enter
vector_index
.For the Vector embedding field path, enter
embedding
.For the Text field path, enter
text_chunk
.For the Metadata field path, enter
metadata
.
Review and create the knowledge base
After reviewing the details for your knowledge base, click Create knowledge base to finalize your creation.
Sync the data source
After Amazon Bedrock creates the knowledge base, it prompts you to sync your data. In the Data source
section, select your data source and click Sync
to sync the data from the S3 bucket and load it into Atlas.
When the sync completes, you can view your vector embeddings in the Atlas UI by navigating to the recipe_db.recipe_vector
collection in your cluster.
now you have created the knowledge bases successfully. Can proceed to the next step which is running the FastAPI server to test the knowledge base.
Change the KNOWLEDGE_BASE_ID accordingly
once you created the knowledge base successfully, go to image/src
and find the variable KNOWLEDGE_BASE_ID
and change the variable to your own KNOWLEDGE_BASE_ID accordingly.
Running the FastAPI Server Locally
cd image
python src/main.py
Then go to http://0.0.0.0:8000/docs
to try it out.
Run in Docker Environment
Build the Docker Image
docker build -t recipe-api .
Run the Docker Image as a Server Locally
docker run --rm -p 8000:8000 \
--entrypoint python \
--env-file .env \
recipe-api main.py
After running the Docker container on localhost, you can access an interactive API page locally to test it: http://0.0.0.0:8000/docs
.
Deploy to AWS Lambda
Install the Node dependencies
cd lambda-rag-cdk-infra
npm install
Deploy the FastAPI into Lambda Serverless Function
cdk deploy
You will get the deployed Lambda URL and you can test out the deployed fastAPI endpoint using that URL. You can also find it out from Lambda Functions page and get the Function URL.
Note for Lambda Deployment
Make sure that you have setup the AWS CLI in your machine and AWS CDK already bootstrapped. If haven't bootstrap, run the code below.
cdk bootstrap
Delete the lambda deployment
If you want to stop the lambda deployment, run the code below.
cdk destroy
API Response
We have 2 endpoints which are /recommend and /substitute. I will only show the response for endpoint /substitute as both endpoints actually have similar output. The only difference is /recommend will just recommend the most suitable recipe based on the input ingredients while /substitute will recommend the most suitable recipe based on the input ingredients while introducing the substitution ingredients if the required ingredients are not found in the input ingredients.
Input Endpoint for /substitute
Input JSON Payload
{
"main_ingredients": ["chicken"],
"pantry_ingredients": [
"egg"
]
}
Output Response
{
"status": "success",
"data": {
"recipe": {
"recipe_name": "Chicken Basquaise",
"category": "Chicken",
"cuisine": "French",
"match_scores": {
"main_score": 100.0,
"pantry_score": 0.0,
"total_score": 70.0,
"main_matches": 1,
"pantry_matches": 0
},
"ingredients": [
{
"name": "chicken",
"measure": "1.5kg",
"is_main": true,
"available": true
},
{
"name": "butter",
"measure": "25g",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "olive oil",
"measure": "20g"
},
{
"ingredient": "coconut oil",
"measure": "20g"
},
{
"ingredient": "applesauce",
"measure": "3 tablespoons"
}
]
},
{
"name": "olive oil",
"measure": "6 tblsp",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "unsweetened applesauce",
"measure": "6 tablespoons"
},
{
"ingredient": "avocado",
"measure": "3/4 cup mashed"
}
]
},
{
"name": "red onions",
"measure": "2 sliced",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "sweet onion",
"measure": "2"
},
{
"ingredient": "shallots",
"measure": "6"
}
]
},
{
"name": "red pepper",
"measure": "3 Large",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "1 large yellow pepper",
"measure": "1"
},
{
"ingredient": "1 large orange pepper",
"measure": "1"
},
{
"ingredient": "1 large green pepper",
"measure": "1"
}
]
},
{
"name": "chorizo",
"measure": "130g",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "smoked paprika",
"measure": "1 tbsp"
},
{
"ingredient": "cooked chicken sausage",
"measure": "1 link"
},
{
"ingredient": "plant-based sausage",
"measure": "1 link"
}
]
},
{
"name": "sun-dried tomatoes",
"measure": "8",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "dried tomatoes",
"measure": "1/2 cup"
},
{
"ingredient": "tomato paste",
"measure": "1/4 cup"
}
]
},
{
"name": "garlic",
"measure": "6 cloves sliced",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "garlic powder",
"measure": "1 teaspoon"
},
{
"ingredient": "granulated garlic",
"measure": "1 teaspoon"
}
]
},
{
"name": "basmati rice",
"measure": "300g",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "couscous",
"measure": "300g"
},
{
"ingredient": "quinoa",
"measure": "300g"
}
]
},
{
"name": "tomato puree",
"measure": "drizzle",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "tomato paste",
"measure": "1:1 ratio"
},
{
"ingredient": "canned crushed tomatoes",
"measure": "1:1 ratio"
}
]
},
{
"name": "paprika",
"measure": "tsp",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "cayenne pepper",
"measure": "1/4 tsp"
},
{
"ingredient": "smoked paprika",
"measure": "1 tsp"
}
]
},
{
"name": "bay leaves",
"measure": "4",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "dried thyme",
"measure": "1 teaspoon"
},
{
"ingredient": "dried rosemary",
"measure": "1 teaspoon"
}
]
},
{
"name": "thyme",
"measure": "Handful",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "rosemary",
"measure": "2 teaspoons"
},
{
"ingredient": "oregano",
"measure": "2 teaspoons"
},
{
"ingredient": "savory",
"measure": "2 teaspoons"
}
]
},
{
"name": "chicken stock",
"measure": "350ml",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "water",
"measure": "350ml"
},
{
"ingredient": "vegetable broth",
"measure": "350ml"
}
]
},
{
"name": "dry white wine",
"measure": "180g",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "chicken broth",
"measure": "240ml"
},
{
"ingredient": "white grape juice",
"measure": "180ml"
}
]
},
{
"name": "lemons",
"measure": "2",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "lime juice",
"measure": "2 limes"
},
{
"ingredient": "vinegar",
"measure": "2 tablespoons vinegar"
}
]
},
{
"name": "black olives",
"measure": "100g",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "capers",
"measure": "50g"
},
{
"ingredient": "canned artichoke hearts",
"measure": "50g"
}
]
},
{
"name": "salt",
"measure": "to serve",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "salt-free seasoning blend",
"measure": "1 teaspoon"
},
{
"ingredient": "kosher salt",
"measure": "3/4 teaspoon"
}
]
},
{
"name": "pepper",
"measure": "to serve",
"is_main": false,
"available": false,
"substitutes": [
{
"ingredient": "paprika",
"measure": "1:1"
},
{
"ingredient": "cayenne pepper",
"measure": "1/2 tsp per tsp pepper"
}
]
}
],
"steps": [
"Preheat the oven to 180C/Gas mark 4. Have the chicken joints ready to cook. Heat the butter and 3 tbsp olive oil in a flameproof casserole or large frying pan. Brown the chicken pieces in batches on both sides, seasoning them with salt and pepper as you go. Don't crowd the pan - fry the chicken in small batches, removing the pieces to kitchen paper as they are done.",
"Add a little more olive oil to the casserole and fry the onions over a medium heat for 10 minutes, stirring frequently, until softened but not browned. Add the rest of the oil, then the peppers and cook for another 5 minutes.",
"Add the chorizo, sun-dried tomatoes and garlic and cook for 2-3 minutes. Add the rice, stirring to ensure it is well coated in the oil. Stir in the tomato paste, paprika, bay leaves and chopped thyme. Pour in the stock and wine. When the liquid starts to bubble, turn the heat down to a gentle simmer. Press the rice down into the liquid if it isn't already submerged and place the chicken on top. Add the lemon wedges and olives around the chicken.",
"Cover and cook in the oven for 50 minutes. The rice should be cooked but still have some bite, and the chicken should have juices that run clear when pierced in the thickest part with a knife. If not, cook for another 5 minutes and check again."
],
"image_url": "https://www.themealdb.com/images/media/meals/wruvqv1511880994.jpg",
"total_score": 70.0,
"missing_ingredients": [
{
"name": "butter",
"measure": "25g",
"substitutes": [
{
"ingredient": "olive oil",
"measure": "20g"
},
{
"ingredient": "coconut oil",
"measure": "20g"
},
{
"ingredient": "applesauce",
"measure": "3 tablespoons"
}
]
},
{
"name": "olive oil",
"measure": "6 tblsp",
"substitutes": [
{
"ingredient": "unsweetened applesauce",
"measure": "6 tablespoons"
},
{
"ingredient": "avocado",
"measure": "3/4 cup mashed"
}
]
},
{
"name": "red onions",
"measure": "2 sliced",
"substitutes": [
{
"ingredient": "sweet onion",
"measure": "2"
},
{
"ingredient": "shallots",
"measure": "6"
}
]
},
{
"name": "red pepper",
"measure": "3 Large",
"substitutes": [
{
"ingredient": "1 large yellow pepper",
"measure": "1"
},
{
"ingredient": "1 large orange pepper",
"measure": "1"
},
{
"ingredient": "1 large green pepper",
"measure": "1"
}
]
},
{
"name": "chorizo",
"measure": "130g",
"substitutes": [
{
"ingredient": "smoked paprika",
"measure": "1 tbsp"
},
{
"ingredient": "cooked chicken sausage",
"measure": "1 link"
},
{
"ingredient": "plant-based sausage",
"measure": "1 link"
}
]
},
{
"name": "sun-dried tomatoes",
"measure": "8",
"substitutes": [
{
"ingredient": "dried tomatoes",
"measure": "1/2 cup"
},
{
"ingredient": "tomato paste",
"measure": "1/4 cup"
}
]
},
{
"name": "garlic",
"measure": "6 cloves sliced",
"substitutes": [
{
"ingredient": "garlic powder",
"measure": "1 teaspoon"
},
{
"ingredient": "granulated garlic",
"measure": "1 teaspoon"
}
]
},
{
"name": "basmati rice",
"measure": "300g",
"substitutes": [
{
"ingredient": "couscous",
"measure": "300g"
},
{
"ingredient": "quinoa",
"measure": "300g"
}
]
},
{
"name": "tomato puree",
"measure": "drizzle",
"substitutes": [
{
"ingredient": "tomato paste",
"measure": "1:1 ratio"
},
{
"ingredient": "canned crushed tomatoes",
"measure": "1:1 ratio"
}
]
},
{
"name": "paprika",
"measure": "½ tsp",
"substitutes": [
{
"ingredient": "cayenne pepper",
"measure": "1/4 tsp"
},
{
"ingredient": "smoked paprika",
"measure": "1 tsp"
}
]
},
{
"name": "bay leaves",
"measure": "4",
"substitutes": [
{
"ingredient": "dried thyme",
"measure": "1 teaspoon"
},
{
"ingredient": "dried rosemary",
"measure": "1 teaspoon"
}
]
},
{
"name": "thyme",
"measure": "Handful",
"substitutes": [
{
"ingredient": "rosemary",
"measure": "2 teaspoons"
},
{
"ingredient": "oregano",
"measure": "2 teaspoons"
},
{
"ingredient": "savory",
"measure": "2 teaspoons"
}
]
},
{
"name": "chicken stock",
"measure": "350ml",
"substitutes": [
{
"ingredient": "water",
"measure": "350ml"
},
{
"ingredient": "vegetable broth",
"measure": "350ml"
}
]
},
{
"name": "dry white wine",
"measure": "180g",
"substitutes": [
{
"ingredient": "chicken broth",
"measure": "240ml"
},
{
"ingredient": "white grape juice",
"measure": "180ml"
}
]
},
{
"name": "lemons",
"measure": "2",
"substitutes": [
{
"ingredient": "lime juice",
"measure": "2 limes"
},
{
"ingredient": "vinegar",
"measure": "2 tablespoons vinegar"
}
]
},
{
"name": "black olives",
"measure": "100g",
"substitutes": [
{
"ingredient": "capers",
"measure": "50g"
},
{
"ingredient": "canned artichoke hearts",
"measure": "50g"
}
]
},
{
"name": "salt",
"measure": "to serve",
"substitutes": [
{
"ingredient": "salt-free seasoning blend",
"measure": "1 teaspoon"
},
{
"ingredient": "kosher salt",
"measure": "3/4 teaspoon"
}
]
},
{
"name": "pepper",
"measure": "to serve",
"substitutes": [
{
"ingredient": "paprika",
"measure": "1:1"
},
{
"ingredient": "cayenne pepper",
"measure": "1/2 tsp per tsp pepper"
}
]
}
]
},
"metadata": {
"main_ingredients": [
"chicken"
],
"pantry_ingredients": [
"egg"
]
}
},
"error": null,
"details": null
}
Conclusion
Congratulations! You've successfully built a production-ready RAG system that combines the power of MongoDB Atlas Vector Search, Amazon Bedrock's foundation models, and AWS Lambda's serverless capabilities.
This architecture not only ensures scalable and efficient data retrieval but also provides cost-effective, always-available access to your AI-powered insights from anywhere in the world. Whether you're expanding this system for different use cases or using it as a foundation for more complex AI applications, you now have a robust, enterprise-grade solution that can grow with your needs. The journey doesn't end here – feel free to explore, modify, and enhance this system to better suit your specific requirements. Happy building!
Feel free to put your comment if you have any questions/suggestions!
Thank you for reading.
Resources
MongoDB Documentation for Bedrock Integration
Amazon Bedrock Metadata Filtering Documentation
MongoDB and Amazon Bedrock Seminar Youtube
MongoDB and Amazon Bedrock Agent Integration
Simplify RAG application with MongoDB Atlas and Amazon Bedrock Dev Blog