# MongoDB as a Vector Database for AI Agents

> Source: <https://dev.to/mongodb/mongodb-as-a-vector-database-for-ai-agents-3lip>
> Published: 2026-10-02 09:00:00+00:00

*This article was written by [Néstor Daza](https://www.linkedin.com/in/nestordaza/).*

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](https://www.themealdb.com/api.php), a free, public recipe API that requires no key. It will also remember your personal preferences across sessions, storing and retrieving that memory from **MongoDB** 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](https://www.voyageai.com/) models fit into the pipeline.

Before you begin, make sure you have the following:

`python --version`. If you need to install it, follow the 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.

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

``` php
TheMealDB -> VoyageAI embeddings -> MongoDB Atlas (vector index)
                                                  |
                                        LangChain retriever
                                                  |
                                       Claude (via LangChain)
                                                  |
                                               Your app
```

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.

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
```

Install the required packages:

```
pip install langchain langchain-anthropic langchain-mongodb \
            langchain-voyageai pymongo requests python-dotenv
```

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__/
```

Head to [MongoDB Atlas](https://www.mongodb.com/cloud/atlas/register/?utm_campaign=devrel&utm_source=third-party-content&utm_medium=cta&utm_content=devrel-tutorial-vectorsearch-agents&utm_term=nestor.daza) 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>
```

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.

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

``` js
// 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.");
```

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](https://docs.voyageai.com/docs/embeddings) 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](https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-overview/?utm_campaign=devrel&utm_source=third-party-content&utm_medium=cta&utm_content=devrel-tutorial-vectorsearch-agents&utm_term=hugh.murray).

Make sure you have [mongosh](https://www.mongodb.com/docs/mongodb-shell/install/?utm_campaign=devrel&utm_source=third-party-content&utm_medium=cta&utm_content=devrel-tutorial-vectorsearch-agents&utm_term=hugh.murray) 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
```

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

``` python
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()
```

Run the ingestion script:

```
python ingest.py
```

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.
```

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.

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

``` python
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)
```

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.

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
```

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.
```

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.
```

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

```
python agent.py my-session
(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...
```

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.

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"
```

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 } }
)
```

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
}
```

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 } }
)
{
  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
}
```

Or a session turn:

```
db.sessions.findOne(
  { session_id: "my-session" },
  { _id: 0 }
)
{
  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')
}
```

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](https://www.mongodb.com/resources/basics/artificial-intelligence/agent-memory) article is a great starting point.

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](https://www.mongodb.com/docs/atlas/atlas-vector-search/tutorials/reciprocal-rank-fusion/), combining vector similarity scores with keyword relevance using [Reciprocal Rank Fusion and Relative Score Fusion](https://medium.com/mongodb/reciprocal-rank-fusion-and-relative-score-fusion-classic-hybrid-search-techniques-3bf91008b81d). This is useful when users search for exact recipe names rather than by semantic descriptions.

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](https://www.mongodb.com/docs/vector-search/about/ai-agents/) and start coding!

For more on the tools used in this tutorial:
