DEV Community

Cover image for MongoDB as a Vector Database for AI Agents
Nestor Daza for MongoDB

Posted on AI-assisted

MongoDB as a Vector Database for AI Agents

This article was written by Néstor Daza

Introduction

MongoDB is a general-purpose document database with several modules embedded in its core that turn it into a complete data platform for modern application development. One of them is native Vector Search, allowing MongoDB to manage your data while also powering semantic retrieval and agent memory for your application. You don't need a separate vector store.

In this tutorial, you will build a Recipe Assistant Agent called Chez Robot using MongoDB, VoyageAI, Claude, and LangChain. The agent will answer natural language questions like "what can I make with rice and saffron?" by performing a semantic search over a collection of recipes fetched from TheMealDB, a free, public recipe API. It will also remember your personal preferences across sessions, storing and retrieving that memory from the database using the same vector index.

By the end, you will understand how MongoDB handles embeddings and Vector Search, how LangChain wires together retrieval and memory, and how Voyage AI models fit into the pipeline.

What you will need

Before you begin, make sure you have the following:

  • Python 3.10 or higher. You can verify with python --version. If you need to install it, follow the official Python installation guide.
  • A MongoDB Atlas account where you will set up a free M0 cluster later. You can create the account at mongodb.com/cloud/atlas (no credit card required for M0). It is also possible to use a local Instance of MongoDB Community Edition.
  • mongosh (MongoDB Shell). You will use it to create the vector search indexes and query the database. Follow the official installation guide to set it up.
  • A Voyage AI API key. Sign up at voyageai.com and generate a key from your dashboard. Voyage AI offers a free tier generous enough to complete this tutorial.
  • An Anthropic API key for Claude, which will drive the agent's reasoning. You can create one at console.anthropic.com.
  • Basic familiarity with what embeddings and Vector Search are, what a language model agent does, and how MongoDB stores documents. If any of those feel shaky, the MongoDB Vector Search docs and Anthropic's intro to Claude are good starting points.

Understanding the Architecture

Before writing any code, let's cover some basic concepts about Vector Search, the role MongoDB plays, and why it makes sense to use MongoDB as the data backbone of your AI Agentic applications.

A MongoDB document stores your application data: fields, arrays, nested objects, etc. A vector-enabled document stores all of that, plus an additional field containing a high-dimensional float array: the embedding. When you run a Vector Search query, MongoDB compares your query embedding against the stored embeddings of all your documents in a collection; the result is a ranked list of documents ordered by semantic similarity, effectively finding elements that have a meaningful relationship with the original query.

Vector Search is the backbone of two capabilities that make AI agents genuinely useful in production: knowledge retrieval and persistent memory.

  • RAG (Retrieval-Augmented Generation): The agent retrieves relevant information from external sources before generating an answer, grounding its output in real data rather than the model's training knowledge alone.
  • Persistent memory: Between sessions, the agent stores what it learns about your preferences and previous interactions, and retrieves the most contextually relevant memories at the start of each new conversation.

Putting everything together, we can build out the Chez Robot app with a full stack that looks like this:

TheMealDB -> VoyageAI embeddings -> MongoDB Atlas (vector index)
                                                  |
                                        LangChain retriever
                                                  |
                                       Claude (via LangChain)
                                                  |
                                               Your app
Enter fullscreen mode Exit fullscreen mode

All data concerns in an AI agent, knowledge retrieval, persistent memory, and conversation history, can be stored in MongoDB. One connection string, one query language, one place to manage your data. That is what makes MongoDB a natural fit for agentic applications: it handles both structured and semantic workloads without splitting your infrastructure or adding complexity to your software.


Step 1 — Setting Up Your Environment

Create a project directory and a virtual environment to keep dependencies isolated:

mkdir chez-robot-agent
cd chez-robot-agent
python -m venv .venv
source .venv/bin/activate   # On Windows: .venv\Scripts\activate
Enter fullscreen mode Exit fullscreen mode

Install the required packages:

pip install langchain langchain-anthropic langchain-mongodb \
            langchain-voyageai pymongo requests python-dotenv
Enter fullscreen mode Exit fullscreen mode

Here is what each package does:

  • langchain provides the agent framework and the retrieval abstractions.
  • langchain-anthropic is the LangChain integration for Claude.
  • langchain-mongodb provides MongoDBAtlasVectorSearch, which stores and queries embeddings in MongoDB directly from LangChain.
  • langchain-voyageai wraps the Voyage AI embedding API in a LangChain-compatible interface.
  • pymongo is the underlying MongoDB driver.
  • requests is used to fetch recipes from TheMealDB.
  • python-dotenv loads your secrets from a .env file.

Add a .gitignore so you never accidentally commit secrets:

.env
.venv/
__pycache__/
Enter fullscreen mode Exit fullscreen mode

Step 2 — Setting Up MongoDB Atlas

Head to MongoDB Atlas and log in to your account.

To create your cluster, go to Clusters, click Create, and select the M0 free tier. Choose your preferred cloud provider and region, in Cluster Details, name your cluster chez-robot, and click Create Cluster. It'll take a couple of minutes to get everything up and running.

Now you need a database user. In the Security section of the left menu, go to Database & Network Access and click Add New Database User. Choose Password as the authentication method, set a username and a strong password, and assign the Read and Write to any database built-in role. Click Add User.

Finally, whitelist your IP address to allow secure access to your cluster. In Network Access, select IP Access List and click Add IP Address. Click Add Current IP Address to whitelist your machine, then click Confirm. You might already have 0.0.0.0/0 configured, which allows access from anywhere; this is useful if you are working from a dynamic IP or want to keep things simple for a local tutorial, but not recommended for production deployments.

Create a .env file in the project root:

MONGODB_URI=<your-atlas-connection-string>
VOYAGE_API_KEY=<your-voyage-api-key>
ANTHROPIC_API_KEY=<your-anthropic-api-key>
Enter fullscreen mode Exit fullscreen mode

You can find your database connection string in the Atlas dashboard under Cluster > Connect > Drivers. Make sure to replace <db_username> and <db_password> in the URI with the credentials you just created.


Step 3 — Creating the Vector Search Indexes

With the cluster ready, you can create both vector search indexes before ingesting any data. You will create the collections using db.createCollection(), then attach the vector search indexes to them. This way, everything is set up before any data arrives.

Create a file called scripts/create_indexes.js in your project root (this is a JavaScript file executed directly by mongosh, no additional dependencies needed):

// create_indexes.js

const DB_NAME = "chez_robot";

db = db.getSiblingDB(DB_NAME);

// Create collections explicitly before creating vector search indexes
db.createCollection("recipes");
db.createCollection("memories");

// Vector search index for recipes
db.recipes.createSearchIndex({
  name: "recipe_vector_index",
  type: "vectorSearch",
  definition: {
    fields: [
      {
        type: "vector",
        path: "embedding",
        numDimensions: 1024,
        similarity: "cosine"
      },
      {
        type: "filter",
        path: "tags"
      }
    ]
  }
});

// Vector search index for agent memory
db.memories.createSearchIndex({
  name: "memory_vector_index",
  type: "vectorSearch",
  definition: {
    fields: [
      {
        type: "vector",
        path: "embedding",
        numDimensions: 1024,
        similarity: "cosine"
      }
    ]
  }
});

print("Index creation finished for recipes and memories collections.");
Enter fullscreen mode Exit fullscreen mode

A few things worth noting here. The numDimensions value of 1024 matches the default output size of voyage-4, the VoyageAI model you will use to generate embeddings. If you ever switch to a different VoyageAI model, verify its output dimensions in the VoyageAI model documentation and update this value accordingly.

The cosine similarity metric measures the angle between vectors rather than their absolute distance, which works well for semantic similarity tasks where the direction of the embedding matters more than its magnitude.

The filter field on tags in the recipes index allows you to pre-filter results by dietary category before the vector search runs, which you will use later. For a complete reference on vector search index definitions, check the official documentation.

Make sure you have mongosh installed; then export your connection string as an environment variable (the same URI you added to your .env file in Step 2) and run the index creation script.

export MONGODB_URI=<your-atlas-connection-string>
mongosh "$MONGODB_URI" --file scripts/create_indexes.js
Enter fullscreen mode Exit fullscreen mode

Step 4 — Ingesting Recipes from TheMealDB

We're ready to fetch recipes and store them as vector-enabled documents in your cluster. TheMealDB's API allows you to retrieve recipes by their first letter; you will loop through the alphabet, load each recipe, generate embeddings for it, and store them as vector-enabled documents in MongoDB.

Create a file called ingest.py:

import os
import requests
from pymongo import MongoClient
from langchain_voyageai import VoyageAIEmbeddings
from dotenv import load_dotenv

load_dotenv()

# --- Clients ---
mongoDBClient = MongoClient(os.environ["MONGODB_URI"], appname="devrel-tutorial-vectorsearch-agents")
db = mongoDBClient["chez_robot"]
collection = db["recipes"]

voyageAIClient = VoyageAIEmbeddings(
    voyage_api_key=os.environ["VOYAGE_API_KEY"],
    model="voyage-4",
)

# --- Fetch recipes from TheMealDB ---
def fetch_recipes_by_letter(letter: str) -> list[dict]:
    url = f"https://www.themealdb.com/api/json/v1/1/search.php?f={letter}"
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    meals = response.json().get("meals") or []
    return meals

def build_recipe_text(meal: dict) -> str:
    """Build a single string representation of a recipe for embedding."""
    ingredients = []
    for i in range(1, 21):
        ingredient = (meal.get(f"strIngredient{i}") or "").strip()
        measure = (meal.get(f"strMeasure{i}") or "").strip()
        if ingredient:
            ingredients.append(f"{measure} {ingredient}".strip())

    return (
        f"Recipe: {meal['strMeal']}\n"
        f"Category: {meal.get('strCategory', '')}\n"
        f"Cuisine: {meal.get('strArea', '')}\n"
        f"Ingredients: {', '.join(ingredients)}\n"
        f"Instructions: {meal.get('strInstructions', '')[:500]}"
    )

def build_tags(meal: dict) -> list[str]:
    tags = []
    if meal.get("strCategory"):
        tags.append(meal["strCategory"].lower())
    if meal.get("strArea"):
        tags.append(meal["strArea"].lower())
    raw_tags = meal.get("strTags") or ""
    tags += [t.strip().lower() for t in raw_tags.split(",") if t.strip()]
    return list(set(tags))

# --- Ingest ---
def ingest(letters: str = "abcdefghijklmnopqrstuvwxyz"):
    print("Fetching recipes from TheMealDB...")
    all_meals = []
    for letter in letters:
        meals = fetch_recipes_by_letter(letter)
        all_meals.extend(meals)
        print(f"  {letter}: {len(meals)} recipes")

    print(f"\nTotal recipes fetched: {len(all_meals)}")
    print("Generating embeddings with Voyage AI...")

    texts = [build_recipe_text(meal) for meal in all_meals]

    # Voyage AI supports batching - send up to 128 texts at a time
    batch_size = 64
    all_embeddings = []
    for i in range(0, len(texts), batch_size):
        batch = texts[i : i + batch_size]
        batch_embeddings = voyageAIClient.embed_documents(batch)
        all_embeddings.extend(batch_embeddings)
        print(f"  Embedded {min(i + batch_size, len(texts))}/{len(texts)}")

    print("Writing documents to MongoDB ...")
    documents = []
    for meal, text, embedding in zip(all_meals, texts, all_embeddings):
        documents.append({
            "meal_id": meal["idMeal"],
            "name": meal["strMeal"],
            "category": meal.get("strCategory"),
            "cuisine": meal.get("strArea"),
            "tags": build_tags(meal),
            "source_text": text,
            "thumbnail": meal.get("strMealThumb"),
            "embedding": embedding,
        })

    # Use replace to avoid duplicates if you re-run ingest
    for doc in documents:
        collection.replace_one(
            {"meal_id": doc["meal_id"]},
            doc,
            upsert=True,
        )

    print(f"Done. {len(documents)} recipes stored in MongoDB.")

if __name__ == "__main__":
    ingest()
Enter fullscreen mode Exit fullscreen mode

Run the ingestion script:

python ingest.py
Enter fullscreen mode Exit fullscreen mode

You will see output similar to:

Fetching recipes from TheMealDB...
  a: 9 recipes
  b: 14 recipes
  ...
Total recipes fetched: 303
Generating embeddings with Voyage AI...
  Embedded 64/303
  Embedded 128/303
  ...
Done. 303 recipes stored in MongoDB.
Enter fullscreen mode Exit fullscreen mode

Each document in your recipes collection now has an embedding field alongside the recipe's structured data. The embedding is an array of 1024 (the dimensions) floating-point numbers, generated by passing the full recipe text (name, category, cuisine, ingredients, and a truncated version of the instructions) through voyage-4. It captures the meaning of the recipe, not just its keywords, which is what allows a query like "light Japanese noodle dish" to match a recipe for Miso Ramen even if those exact words don't appear together in the document.


Step 5 — Building the Agent

We're ready to build our agent. Create a file called agent.py:

import os
import warnings
from datetime import datetime, UTC
from pymongo import MongoClient
from langchain_anthropic import ChatAnthropic
from langchain_mongodb import MongoDBAtlasVectorSearch
from langchain_voyageai import VoyageAIEmbeddings
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage
from langgraph.prebuilt import create_react_agent
from dotenv import load_dotenv

warnings.filterwarnings("ignore", category=DeprecationWarning)

load_dotenv()

# --- Clients ---
mongoDBClient = MongoClient(os.environ["MONGODB_URI"], appname="devrel-tutorial-vectorsearch-agents")
db = mongoDBClient["chez_robot"]

voyageAIClient = VoyageAIEmbeddings(
    voyage_api_key=os.environ["VOYAGE_API_KEY"],
    model="voyage-4",
)

llm = ChatAnthropic(
    model="claude-haiku-4-5",
    anthropic_api_key=os.environ["ANTHROPIC_API_KEY"],
    temperature=0,
)

# --- Vector stores ---
recipe_store = MongoDBAtlasVectorSearch(
    collection=db["recipes"],
    embedding=voyageAIClient,
    index_name="recipe_vector_index",
    text_key="source_text",
    embedding_key="embedding",
)

memory_store = MongoDBAtlasVectorSearch(
    collection=db["memories"],
    embedding=voyageAIClient,
    index_name="memory_vector_index",
    text_key="content",
    embedding_key="embedding",
)


# --- Tools ---

@tool
def search_recipes(query: str, dietary_filter: str = "") -> str:
    """
    Search for recipes by ingredient, cuisine, dish type, or cooking style.
    Optionally filter by dietary category (e.g. 'vegetarian', 'seafood', 'chicken').
    Returns the top 5 most relevant recipes.
    """
    search_kwargs = {"k": 5}

    if dietary_filter:
        search_kwargs["pre_filter"] = {
            "tags": {"$in": [dietary_filter.lower()]}
        }

    results = recipe_store.similarity_search(query, **search_kwargs)

    if not results:
        return "No recipes found matching that query."

    output = []
    for i, doc in enumerate(results, 1):
        output.append(f"**Recipe {i}:** {doc.metadata.get('name', 'Unknown')}\n{doc.page_content[:400]}\n")

    return "\n".join(output)


@tool
def save_preference(preference: str) -> str:
    """
    Save a dietary preference or food restriction that the user has mentioned.
    Examples: 'user is vegetarian', 'user dislikes cilantro', 'user is allergic to shellfish'.
    """
    doc = {
        "content": preference,
        "created_at": datetime.now(UTC).isoformat(),
        "type": "preference",
    }
    memory_store.add_texts(
        texts=[preference],
        metadatas=[doc],
    )
    return f"Got it, I've saved that preference: '{preference}'"


@tool
def recall_preferences(context: str) -> str:
    """
    Recall stored user preferences relevant to the current cooking context.
    Use this at the start of a session or when the user asks for personalized recommendations.
    """
    results = memory_store.similarity_search(context, k=5)
    if not results:
        return "No preferences stored yet."

    preferences = [doc.page_content for doc in results]
    return "Remembered preferences:\n" + "\n".join(f"- {p}" for p in preferences)


# --- Load session memory from MongoDB ---
def load_session_memory(session_id: str, k: int = 10) -> list:
    """Retrieve the most recent conversation turns for this session."""
    turns = list(
        db["sessions"].find(
            {"session_id": session_id},
            sort=[("timestamp", -1)],
            limit=k,
        )
    )
    turns.reverse()

    history = []
    for turn in turns:
        if turn["role"] == "human":
            history.append(HumanMessage(content=turn["content"]))
        else:
            history.append(AIMessage(content=turn["content"]))
    return history


def save_turn(session_id: str, role: str, content: str):
    """Persist a single conversation turn to MongoDB."""
    db["sessions"].insert_one({
        "session_id": session_id,
        "role": role,
        "content": content,
        "timestamp": datetime.now(UTC),
    })


# --- Agent setup ---
tools = [search_recipes, save_preference, recall_preferences]

system_prompt = """You are a knowledgeable and enthusiastic recipe assistant. You help users 
find recipes based on ingredients they have, cuisines they love, or dietary needs they follow.

At the start of each conversation, use the recall_preferences tool to check if the user 
has any stored dietary preferences, allergies, or food restrictions, and factor those 
into every recommendation you make.

When a user mentions a preference, restriction, or allergy, always save it using the 
save_preference tool so you can remember it next time.

When recommending recipes, be specific: mention the cuisine, key ingredients, and why 
it matches what the user is looking for. Keep your tone warm and conversational."""

agent = create_react_agent(
    model=llm,
    tools=tools,
    prompt=system_prompt,
)


# --- Main loop ---
def run(session_id: str = "default"):
    print(f"\nChez Robot ready (session: {session_id})")
    print("Ask me anything about recipes. Type 'quit' or 'exit' to leave.\n")

    chat_history = load_session_memory(session_id)

    if chat_history:
        print(f"  (Loaded {len(chat_history)} messages from your last session)\n")

    while True:
        user_input = input("You: ").strip()
        if user_input.lower() in ("quit", "exit", "q"):
            print("Goodbye! Your preferences have been saved for next time.")
            break
        if not user_input:
            continue

        result = agent.invoke({
            "messages": chat_history + [HumanMessage(content=user_input)]
        })

        response = result["messages"][-1].content
        print(f"\nChez Robot: {response}\n")

        chat_history.append(HumanMessage(content=user_input))
        chat_history.append(AIMessage(content=response))

        save_turn(session_id, "human", user_input)
        save_turn(session_id, "ai", response)


if __name__ == "__main__":
    import sys
    session = sys.argv[1] if len(sys.argv) > 1 else "default"
    run(session_id=session)
Enter fullscreen mode Exit fullscreen mode

There are a few things here worth unpacking.

The agent has separate tools for searching recipes, saving preferences, and recalling preferences. This gives the LLM clear, distinct responsibilities to reason about rather than a single overloaded function. The agent uses each tool's function signature and docstring to decide when to invoke it; writing descriptive docstrings is a good practice and a direct input to the agent's reasoning.

Recipe retrieval and preference management are deterministic tasks; you want the agent to reliably call the right tools in the right order, not to get creative. Temperature 0 keeps the LLM's behavior consistent and predictable for tool-use scenarios. If you were building a more open-ended creative assistant, you could consider raising this value.

The pre_filter parameter in similarity_search applies a MongoDB query filter before the vector search runs, which is more efficient than post-filtering results. When a user specifies a dietary category, filtering by the tags field narrows the candidate set significantly, helping the vector search find the most semantically similar recipe within that narrowed set.

Session and preference memory serve different purposes and live in different collections. Session memory in sessions is simple document storage: raw text, ordered by timestamp, no embeddings needed. Preference memory in memories uses vector embeddings, so when the agent calls recall_preferences("Japanese noodle dishes"), it retrieves preferences such as "user avoids shellfish" or "user loves umami-forward flavors" based on semantic relevance to the current context, not just keyword matching. This is what makes the memory feel "intelligent" rather than mechanical.


Step 6 — Running the Agent

Start the agent with a session name. Using named sessions lets you maintain separate conversation histories for different users or contexts:

python agent.py my-session
Enter fullscreen mode Exit fullscreen mode

You will see the agent initialize and load any prior history:

Chez Robot ready (session: my-session)
Ask me anything about recipes. Type 'quit' or 'exit' to leave.
Enter fullscreen mode Exit fullscreen mode

Try a sequence of interactions to see the full pipeline in action; keep in mind that the agent's responses might vary compared to the examples here:

You: I'm vegetarian and I really don't like anything too spicy.

Chez Robot: Got it! I've saved that for you. I'll keep your vegetarian diet and preference for mild flavors in mind for every recommendation. What are you in the mood to cook today?

You: I have some chickpeas, tomatoes, and spinach. What can I make?

Chez Robot: With chickpeas, tomatoes, and spinach, you'd love a Chana Masala. It's a classic Indian chickpea curry that's hearty and flavourful without being overly spicy (you can control the chili level). Another great option is a Shakshuka variation with chickpeas added for extra protein...

You: quit

Goodbye! Your preferences have been saved for next time.
Enter fullscreen mode Exit fullscreen mode

Now restart with the same session name and notice how the agent remembers:

python agent.py my-session
Enter fullscreen mode Exit fullscreen mode
  (Loaded 4 messages from your last session)

You: Suggest something for dinner tonight.

Chez Robot: Based on what I know about you, vegetarian and mild flavors, let me pull up some ideas...
Enter fullscreen mode Exit fullscreen mode

The my-session conversation history is loaded from the sessions collection, retrieving your dietary preferences from the memories collection using vector similarity against the current conversational context. This is the full memory loop in action: MongoDB stores it, VoyageAI encodes it, and the agent retrieves only what's relevant using Vector Search.


Step 7 — Inspecting What MongoDB is Storing

Let's take a look at the actual documents stored in MongoDB to understand their structure. With mongosh already set up, you can query the database directly:

mongosh "$MONGODB_URI"
Enter fullscreen mode Exit fullscreen mode

Once connected, switch to the chez_robot database and inspect a recipe document:

use chez_robot

db.recipes.findOne(
  {},
  { _id: 0, name: 1, tags: 1, embedding: { $slice: 5 } }
)
Enter fullscreen mode Exit fullscreen mode

Output:

{
  name: 'Chana Masala',
  tags: [ 'vegetarian', 'indian', 'curry' ],
  embedding: [ 0.0234, -0.1205, 0.0871, 0.0543, -0.0312 ] // only showing the first 5 dimensions out of 1024
}
Enter fullscreen mode Exit fullscreen mode

The embedding field is an array of 1,024 floating-point numbers. The specific values are not human-interpretable in isolation; their meaning only emerges when you compute the similarity between two of them. A query embedding for "chickpea curry" will have a high cosine similarity to this document's embedding because they encode similar semantic content, even if the exact words differ.

You could also inspect a stored preference memory:

db.memories.findOne(
  { type: "preference" },
  { _id: 0, content: 1, embedding: { $slice: 5 } }
)
Enter fullscreen mode Exit fullscreen mode
{
  content: 'user likes vegetarian dishes',
  embedding: [ 0.0234, -0.1205, 0.0871, 0.0543, -0.0312 ] // only showing the first 5 dimensions out of 1024
}
Enter fullscreen mode Exit fullscreen mode

Or a session turn:

db.sessions.findOne(
  { session_id: "my-session" },
  { _id: 0 }
)
Enter fullscreen mode Exit fullscreen mode
{
  session_id: 'my-session',
  role: 'human',
  content: 'I am vegetarian and I really do not like anything too spicy',
  timestamp: ISODate('2026-04-24T13:11:05.898Z')
}
Enter fullscreen mode Exit fullscreen mode

Notice that documents in the memories collection have an embedding field, just like the recipes. This is what makes preference retrieval semantic rather than literal: when the agent calls recall_preferences, it doesn't look up your preferences by keyword; it converts the current conversational context into an embedding and finds the stored preferences that are most semantically similar to it. A preference saved as "user loves bold Mediterranean flavors" will surface when the user asks about Greek or Spanish food, even if those words never appeared in the original preference.

Session turns in the sessions collection, on the other hand, have no embeddings. They are retrieved by session ID and timestamp because conversation history needs to be ordered and complete, not semantically ranked.

Keep in mind that this is a deliberately simple approach to memory management. Agent memory is actually a rich and complex topic with many strategies for storage, retrieval, forgetting, and summarization. If you want to go deeper, this MongoDB Agent Memory article is a great starting point.


Going Further

You have a working recipe agent with semantic search and persistent memory. These ideas could expand its capabilities and help you explore other features:

Add a pre-filter. Expose the dietary_filter parameter in the conversation so users can say "only show me Italian recipes," and the tag filter is applied automatically. You already have the tags field indexed for this.

Score and weight memories. Right now, all memories are treated equally. You could add a relevance_score field and use MongoDB's $addFields aggregation stage during retrieval to boost more recent or more frequently confirmed preferences.

Use Hybrid Search. MongoDB supports hybrid search, combining vector similarity scores with keyword relevance using Reciprocal Rank Fusion and Relative Score Fusion. This is useful when users search for exact recipe names rather than by semantic descriptions.


Conclusion

In this tutorial, you built a recipe assistant agent that uses MongoDB Vector Search for two purposes: semantic retrieval over a recipes collection, and persistent vector-based memory across sessions. You fetched real recipe data from a public API, embedded it with Voyage AI's voyage-4 model, and stored the results as vector-enabled documents in MongoDB. You then wired a LangChain tool-calling agent to an LLM to give the app reasoning and natural language understanding.

The key insight is that a document database and a vector database are not separate infrastructure concerns when you use MongoDB. The same cluster, the same connection string, and the same query patterns handle both structured filtering and semantic search. That consolidation reduces operational complexity and keeps your data model coherent; your recipe documents don't have to live in two places. To learn more, go to Build AI Agents with MongoDB and start coding!

For more on the tools used in this tutorial:

Top comments (0)