{ "cells": [ { "cell_type": "markdown", "id": "2c08f132", "metadata": {}, "source": "# Mem0 Integration Patterns\n\n> **Give every user a persistent, self-improving memory. No need to build your own extraction pipeline, vector store, or deduplication logic.**\n\nThink of a personal assistant who takes notes during every meeting. After each conversation, they jot down key facts: your preferences, your schedule, your contacts. Before the next meeting, they review those notes so they can help you better. Mem0 is that assistant for your AI agent, but fully automated.\n\nMost agent memory implementations require you to wire three separate systems by hand. First, an LLM-based extraction step to pull facts from conversations. Second, a vector database (a database optimized for finding similar items by meaning) for storage. Third, reconciliation logic to handle contradictions and duplicates. **Mem0** collapses all of this into a single `add()` / `search()` / `get_all()` API.\n\nUnder the hood, Mem0's open-source library:\n1. Sends conversation messages through an LLM to **extract key facts, preferences, and instructions**.\n2. **Deduplicates and resolves contradictions** against existing memories.\n3. Stores the result in a vector database (Qdrant by default) for **semantic retrieval** (finding memories by meaning, not exact keywords).\n\nThe result is a **user-scoped memory layer** that gets smarter over time. Memories are automatically merged, updated, and refined as new information arrives.\n\n**By the end of this notebook you'll:**\n- Install and configure Mem0 with OpenAI as the LLM backend.\n- Add memories from conversation messages and raw text.\n- Search, retrieve, update, and delete memories.\n- Build a memory-augmented agent loop that personalizes responses using Mem0.\n- Understand when Mem0 is the right choice vs. building your own memory system." }, { "cell_type": "markdown", "id": "55ac5c53", "metadata": {}, "source": "## Key Concepts\n\n- **`Memory.add()`:** Accepts conversation messages (or raw text). Runs LLM-based extraction to identify memorable facts. Checks for duplicates and contradictions against existing memories. Stores the results. This single call replaces a custom extraction + embedding + upsert pipeline.\n- **`Memory.search()`:** Semantic search over stored memories using a natural-language query. Returns ranked results with relevance scores. Supports filters by `user_id`, `agent_id`, and metadata.\n- **`Memory.get_all()`:** Retrieves all memories for a given user, agent, or session scope. Useful for displaying a user's full memory profile or debugging.\n- **`Memory.update()` / `Memory.delete()`:** Explicitly modify or remove individual memories by ID. The update path is useful when the agent knows a fact has changed (for example, a user moved cities).\n- **User-scoped isolation:** Every operation is scoped by `user_id`. One user's memories never leak into another's context. Optional `agent_id` and `run_id` add finer-grained scoping.\n- **Automatic conflict resolution:** When you `add()` a memory that contradicts an existing one (for example, \"I moved from NYC to London\"), Mem0's LLM pipeline detects the conflict. It updates the stored memory rather than creating a duplicate.\n- **Self-improving memory:** Over repeated `add()` calls, Mem0 merges and refines related memories. The memory store becomes more accurate and concise over time without manual curation." }, { "cell_type": "markdown", "id": "8ccce5d3", "metadata": {}, "source": "## Architecture\n\n

\n \"diagram\"\n

\n\n
Mermaid source\n\n```mermaid\nflowchart LR\n subgraph App[\"Agent / Application\"]\n A[\"User message\"] --> B[\"Agent generates\\nresponse\"]\n B --> C[\"Response to user\"]\n end\n\n subgraph Mem0Layer[\"Mem0 Memory Layer\"]\n D[\"memory.add()\\n─────────\\nLLM extraction\\nConflict resolution\\nDeduplication\"]\n E[\"memory.search()\\n─────────\\nSemantic search\\nRanked results\"]\n F[(\"Vector Store\\n(Qdrant)\\n─────────\\nEmbeddings\\nMetadata\\nUser scopes\")]\n end\n\n subgraph Ops[\"Management\"]\n G[\"memory.get_all()\\nmemory.update()\\nmemory.delete()\"]\n end\n\n A -->|\"conversation turns\"| D\n D -->|\"extracted facts\"| F\n A -->|\"query\"| E\n E -->|\"relevant memories\"| B\n F <-->|\"store / retrieve\"| E\n G <--> F\n\n style F fill:#4f46e5,color:#fff\n style D fill:#059669,color:#fff\n style E fill:#d97706,color:#fff\n style B fill:#6366f1,color:#fff\n```\n\n
\n\n**Data flow for each conversation turn:**\n1. The user sends a message. The agent calls `memory.search()` to retrieve relevant context.\n2. The agent generates a response using the retrieved memories as context.\n3. The full conversation turn passes to `memory.add()` to extract and store new facts.\n4. Mem0's LLM pipeline extracts facts, resolves conflicts, and updates the vector store." }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "Install the required packages. Mem0 uses OpenAI for both the extraction LLM and embeddings by default." ] }, { "cell_type": "code", "execution_count": null, "id": "4af4b426", "metadata": {}, "outputs": [], "source": [ "# Install required packages (run once)\n", "%pip install -q mem0ai python-dotenv openai" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import Mem0 and load your API key from a `.env` file.\n", "Mem0 picks up `OPENAI_API_KEY` automatically for its LLM and embedding calls." ] }, { "cell_type": "code", "execution_count": null, "id": "08c7631f", "metadata": {}, "outputs": [], "source": [ "import os\n", "import json\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv() # reads API keys from .env\n", "\n", "assert os.getenv(\"OPENAI_API_KEY\"), \"Set OPENAI_API_KEY in your .env file\"\n", "\n", "# Mem0 uses OpenAI for both extraction (LLM) and embeddings by default.\n", "# No additional config is needed if OPENAI_API_KEY is set.\n", "\n", "from mem0 import Memory\n", "\n", "print(\"\\u2713 Environment loaded\")\n", "print(f\"\\u2713 mem0 imported successfully\")" ] }, { "cell_type": "markdown", "id": "5a36f1f0", "metadata": {}, "source": "## Core Implementation\n\nWe'll walk through each Mem0 operation step by step:\n\n1. **Initialize:** Create a `Memory` instance (uses Qdrant locally + OpenAI by default).\n2. **Add:** Store memories from conversation messages.\n3. **Search:** Retrieve relevant memories by semantic query.\n4. **Get All:** List a user's complete memory profile.\n5. **Update:** Modify an existing memory.\n6. **Delete:** Remove a specific memory.\n7. **History:** View the change history of a memory." }, { "cell_type": "code", "execution_count": null, "id": "8cb348e0", "metadata": {}, "outputs": [], "source": [ "# Initialize Mem0 with default configuration\n", "# Default: Qdrant (local, on-disk) + OpenAI gpt-4o-mini + text-embedding-3-small\n", "m = Memory()\n", "\n", "print(\"\\u2713 Memory instance created\")\n", "print(\" Vector store: Qdrant (local)\")\n", "print(\" LLM: OpenAI (default)\")\n", "print(\" Embedder: OpenAI text-embedding-3-small\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding Memories from Conversations\n", "\n", "Pass conversation messages to `m.add()`. Mem0's LLM pipeline extracts the key facts automatically.\n", "Each call is scoped to a `user_id`, so one user's memories stay separate from another's." ] }, { "cell_type": "code", "execution_count": null, "id": "7f16c2a3", "metadata": {}, "outputs": [], "source": [ "# ── Adding memories from conversation messages ──\n", "# Mem0 extracts facts automatically. You just pass the conversation.\n", "\n", "USER_ID = \"alice\"\n", "\n", "# Conversation 1: Food preferences\n", "messages_1 = [\n", " {\"role\": \"user\", \"content\": \"I'm a vegetarian and I love Italian food. My favorite dish is mushroom risotto.\"},\n", " {\"role\": \"assistant\", \"content\": \"Great taste! Mushroom risotto is a classic. I'll remember your preferences.\"},\n", "]\n", "result_1 = m.add(messages_1, user_id=USER_ID)\n", "print(\"Add result (food preferences):\")\n", "print(json.dumps(result_1, indent=2))\n", "\n", "print()\n", "\n", "# Conversation 2: Work context\n", "messages_2 = [\n", " {\"role\": \"user\", \"content\": \"I'm a senior data scientist at Google. I mostly work with Python and PyTorch.\"},\n", " {\"role\": \"assistant\", \"content\": \"Nice! Data science at Google, exciting work. I'll keep that in mind.\"},\n", "]\n", "result_2 = m.add(messages_2, user_id=USER_ID)\n", "print(\"Add result (work context):\")\n", "print(json.dumps(result_2, indent=2))\n", "\n", "print()\n", "\n", "# Conversation 3: Personal details\n", "messages_3 = [\n", " {\"role\": \"user\", \"content\": \"I live in San Francisco. I moved here from New York last year. I have a golden retriever named Max.\"},\n", " {\"role\": \"assistant\", \"content\": \"SF is lovely! And golden retrievers are the best. How's Max adjusting to the new city?\"},\n", "]\n", "result_3 = m.add(messages_3, user_id=USER_ID)\n", "print(\"Add result (personal details):\")\n", "print(json.dumps(result_3, indent=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Searching Memories\n", "\n", "Use `m.search()` to find memories by meaning, not exact keywords.\n", "Mem0 embeds your query and returns the most similar stored facts, ranked by relevance." ] }, { "cell_type": "code", "execution_count": null, "id": "4d6db967", "metadata": {}, "outputs": [], "source": [ "# ── Searching memories by semantic query ──\n", "# Mem0 returns the most relevant memories ranked by similarity.\n", "\n", "print(\"=== Search: 'What food does Alice like?' ===\\n\")\n", "food_results = m.search(\"What food does Alice like?\", user_id=USER_ID)\n", "for mem in food_results.get(\"results\", food_results if isinstance(food_results, list) else []):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "print()\n", "\n", "print(\"=== Search: 'What does Alice do for work?' ===\\n\")\n", "work_results = m.search(\"What does Alice do for work?\", user_id=USER_ID)\n", "for mem in work_results.get(\"results\", work_results if isinstance(work_results, list) else []):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "print()\n", "\n", "print(\"=== Search: 'pets' ===\\n\")\n", "pet_results = m.search(\"pets\", user_id=USER_ID)\n", "for mem in pet_results.get(\"results\", pet_results if isinstance(pet_results, list) else []):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Listing All Memories\n", "\n", "Call `m.get_all()` to see every memory stored for a user.\n", "This is useful for debugging or showing the user their full profile." ] }, { "cell_type": "code", "execution_count": null, "id": "5812914b", "metadata": {}, "outputs": [], "source": [ "# ── Get all memories for a user ──\n", "# Useful for inspecting the full memory profile.\n", "\n", "all_memories = m.get_all(user_id=USER_ID)\n", "\n", "# Handle both list and dict response formats\n", "memory_list = all_memories.get(\"results\", all_memories) if isinstance(all_memories, dict) else all_memories\n", "\n", "print(f\"=== All memories for '{USER_ID}' ({len(memory_list)} total) ===\\n\")\n", "for i, mem in enumerate(memory_list, 1):\n", " mem_id = mem.get(\"id\", \"N/A\")\n", " mem_text = mem.get(\"memory\", mem.get(\"text\", str(mem)))\n", " print(f\" {i}. [{mem_id[:8]}...] {mem_text}\")\n", "\n", "# Save one memory ID for later update/delete demos\n", "if memory_list:\n", " SAMPLE_MEMORY_ID = memory_list[0].get(\"id\")\n", " print(f\"\\n\\u2713 Saved memory ID for later: {SAMPLE_MEMORY_ID[:12]}...\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Automatic Conflict Resolution\n", "\n", "When you add a fact that contradicts an existing memory, Mem0 detects the conflict.\n", "It updates the stored memory instead of creating a duplicate.\n", "Watch how Alice's location changes from San Francisco to London." ] }, { "cell_type": "code", "execution_count": null, "id": "69b378b3", "metadata": {}, "outputs": [], "source": [ "# ── Automatic conflict resolution ──\n", "# When you add a fact that contradicts an existing memory, Mem0 updates it.\n", "\n", "print(\"=== Before: Alice's location ===\")\n", "location_before = m.search(\"Where does Alice live?\", user_id=USER_ID)\n", "for mem in (location_before.get(\"results\", location_before) if isinstance(location_before, dict) else location_before):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "# Alice moves to London\n", "messages_move = [\n", " {\"role\": \"user\", \"content\": \"I just moved to London! Left San Francisco last week.\"},\n", " {\"role\": \"assistant\", \"content\": \"Exciting move! How are you finding London so far?\"},\n", "]\n", "result_move = m.add(messages_move, user_id=USER_ID)\n", "print(f\"\\nUpdate result:\")\n", "print(json.dumps(result_move, indent=2))\n", "\n", "print(\"\\n=== After: Alice's location ===\")\n", "location_after = m.search(\"Where does Alice live?\", user_id=USER_ID)\n", "for mem in (location_after.get(\"results\", location_after) if isinstance(location_after, dict) else location_after):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "print(\"\\n\\u2713 Mem0 resolved the contradiction: location updated, not duplicated.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Explicit Update and Delete\n", "\n", "You can also modify or remove specific memories by their ID.\n", "Use `m.update()` when you know exactly which fact changed.\n", "Use `m.delete()` when a memory should be removed entirely." ] }, { "cell_type": "code", "execution_count": null, "id": "5591351a", "metadata": {}, "outputs": [], "source": [ "# ── Explicit update and delete ──\n", "\n", "# Update: change a specific memory by ID\n", "if SAMPLE_MEMORY_ID:\n", " print(f\"=== Updating memory {SAMPLE_MEMORY_ID[:12]}... ===\")\n", " update_result = m.update(memory_id=SAMPLE_MEMORY_ID, data=\"Alice is a strict vegan who loves Italian cuisine\")\n", " print(f\"Update result: {update_result}\")\n", "\n", " # Verify the update\n", " print(\"\\nAfter update:\")\n", " updated = m.search(\"food preferences\", user_id=USER_ID)\n", " for mem in (updated.get(\"results\", updated) if isinstance(updated, dict) else updated):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "print()\n", "\n", "# Delete: remove a specific memory\n", "# First, let's add a temporary memory to delete\n", "temp = m.add(\"Alice temporarily likes sushi\", user_id=USER_ID)\n", "temp_list = temp.get(\"results\", temp) if isinstance(temp, dict) else temp\n", "if isinstance(temp_list, list) and temp_list:\n", " temp_id = temp_list[0].get(\"id\", temp_list[0].get(\"memory_id\"))\n", "elif isinstance(temp_list, dict):\n", " temp_id = temp_list.get(\"id\", temp_list.get(\"memory_id\"))\n", "else:\n", " temp_id = None\n", "\n", "if temp_id:\n", " print(f\"=== Deleting temporary memory {str(temp_id)[:12]}... ===\")\n", " delete_result = m.delete(memory_id=temp_id)\n", " print(f\"Delete result: {delete_result}\")\n", " print(\"\\u2713 Memory deleted\")" ] }, { "cell_type": "markdown", "id": "53772c25", "metadata": {}, "source": "## Memory-Augmented Agent Loop\n\nNow let's build a practical agent that uses Mem0 to personalize its responses. The pattern has three steps:\n\n1. **Retrieve:** Before responding, search Mem0 for memories relevant to the user's message.\n2. **Generate:** Include retrieved memories in the system prompt so the LLM can personalize its response.\n3. **Store:** After the exchange, pass the conversation to `memory.add()` to capture new facts.\n\nThis creates a **self-reinforcing loop**. Every conversation enriches the memory. Every response benefits from accumulated knowledge." }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "from openai import OpenAI\n", "\n", "\n", "class Mem0Agent:\n", " \"\"\"A conversational agent that uses Mem0 for persistent, personalized memory.\"\"\"\n", "\n", " def __init__(self, memory: Memory, model: str = \"gpt-4o-mini\", user_id: str = \"default\"):\n", " self.memory = memory\n", " self.client = OpenAI()\n", " self.model = model\n", " self.user_id = user_id\n", " self.conversation: list[dict] = []" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `chat` method ties the three-step pattern together: retrieve, generate, store.\n", "Before answering, it searches Mem0 for relevant context.\n", "After answering, it passes the exchange back to Mem0 so new facts get captured." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ " def chat(self, user_message: str) -> str:\n", " \"\"\"Process a user message: retrieve memories, generate response, store new memories.\"\"\"\n", "\n", " # Step 1: Retrieve relevant memories\n", " relevant_memories = self.memory.search(user_message, user_id=self.user_id)\n", " mem_list = relevant_memories.get(\"results\", relevant_memories) if isinstance(relevant_memories, dict) else relevant_memories\n", "\n", " # Format memories for the system prompt\n", " if mem_list:\n", " memory_context = \"\\n\".join(\n", " f\"- {mem.get('memory', mem.get('text', str(mem)))}\"\n", " for mem in mem_list\n", " if isinstance(mem, dict)\n", " )\n", " memory_block = (\n", " f\"\\n\\nYou have these memories about the user (use them to personalize your response):\\n\"\n", " f\"{memory_context}\"\n", " )\n", " else:\n", " memory_block = \"\"\n", "\n", " system_prompt = (\n", " \"You are a helpful, friendly assistant with a great memory. \"\n", " \"You remember details about the user from past conversations and use them \"\n", " \"to provide personalized, relevant responses. Reference specific things you \"\n", " \"remember when appropriate. It shows you care.\"\n", " f\"{memory_block}\"\n", " )\n", "\n", " # Step 2: Build messages and generate response\n", " self.conversation.append({\"role\": \"user\", \"content\": user_message})\n", "\n", " response = self.client.chat.completions.create(\n", " model=self.model,\n", " messages=[{\"role\": \"system\", \"content\": system_prompt}] + self.conversation[-10:], # last 10 turns\n", " )\n", " assistant_message = response.choices[0].message.content\n", " self.conversation.append({\"role\": \"assistant\", \"content\": assistant_message})\n", "\n", " # Step 3: Store new memories from this exchange\n", " self.memory.add(\n", " [\n", " {\"role\": \"user\", \"content\": user_message},\n", " {\"role\": \"assistant\", \"content\": assistant_message},\n", " ],\n", " user_id=self.user_id,\n", " )\n", "\n", " return assistant_message" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `show_memories` helper prints all stored memories for the current user.\n", "We'll use it after each demo to see what Mem0 extracted." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ " def show_memories(self) -> None:\n", " \"\"\"Display all stored memories for this user.\"\"\"\n", " all_mem = self.memory.get_all(user_id=self.user_id)\n", " mem_list = all_mem.get(\"results\", all_mem) if isinstance(all_mem, dict) else all_mem\n", " print(f\"\\n=== Memories for '{self.user_id}' ({len(mem_list)} total) ===\")\n", " for i, mem in enumerate(mem_list, 1):\n", " print(f\" {i}. {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "\n", "print(\"\\u2713 Mem0Agent class defined\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Demo: Multi-Turn Personalized Conversation\n", "\n", "Let's create a new user (Bob) and have a three-turn conversation.\n", "The agent should remember Bob's details and personalize its responses.\n", "After the conversation, we'll check what Mem0 stored." ] }, { "cell_type": "code", "execution_count": null, "id": "d06d3c20", "metadata": {}, "outputs": [], "source": [ "# ── Demo: Multi-turn personalized conversation ──\n", "\n", "# Use a different user_id for this demo to start with a clean slate\n", "agent = Mem0Agent(memory=m, user_id=\"bob\")\n", "\n", "# Turn 1: Bob introduces himself\n", "print(\"=\" * 60)\n", "print(\"Turn 1\")\n", "print(\"=\" * 60)\n", "user_msg = \"Hi! I'm Bob. I'm a software engineer and I love hiking on weekends.\"\n", "print(f\"User: {user_msg}\\n\")\n", "response = agent.chat(user_msg)\n", "print(f\"Agent: {response}\")\n", "\n", "print()\n", "\n", "# Turn 2: More preferences\n", "print(\"=\" * 60)\n", "print(\"Turn 2\")\n", "print(\"=\" * 60)\n", "user_msg = \"I've been learning Rust lately. Also, I'm training for a half marathon in October.\"\n", "print(f\"User: {user_msg}\\n\")\n", "response = agent.chat(user_msg)\n", "print(f\"Agent: {response}\")\n", "\n", "print()\n", "\n", "# Turn 3: Agent should recall earlier context\n", "print(\"=\" * 60)\n", "print(\"Turn 3\")\n", "print(\"=\" * 60)\n", "user_msg = \"Can you suggest some weekend activities for me?\"\n", "print(f\"User: {user_msg}\\n\")\n", "response = agent.chat(user_msg)\n", "print(f\"Agent: {response}\")\n", "\n", "# Show what Mem0 stored\n", "agent.show_memories()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Simulating a New Session\n", "\n", "Now we create a brand-new agent instance but keep the same `user_id`.\n", "This simulates Bob coming back days later.\n", "The memories persist in Mem0, so the agent should remember everything from before." ] }, { "cell_type": "code", "execution_count": null, "id": "45225387", "metadata": {}, "outputs": [], "source": [ "# ── Simulating a new session ──\n", "# Create a brand-new agent instance, same user_id.\n", "# Memories persist from the previous conversation.\n", "\n", "print(\"=\" * 60)\n", "print(\"NEW SESSION (fresh agent, same user_id)\")\n", "print(\"=\" * 60)\n", "\n", "agent2 = Mem0Agent(memory=m, user_id=\"bob\")\n", "\n", "# Bob returns days later. The agent should remember him\n", "user_msg = \"Hey, I just finished my first Rust project! What do you remember about me?\"\n", "print(f\"\\nUser: {user_msg}\\n\")\n", "response = agent2.chat(user_msg)\n", "print(f\"Agent: {response}\")\n", "\n", "print(\"\\n\\u2713 Memories persisted across sessions. The agent remembers Bob!\")" ] }, { "cell_type": "markdown", "id": "380b21a5", "metadata": {}, "source": "## Custom Configuration\n\nMem0's default setup (OpenAI + local Qdrant) works well for development. For production or alternative LLM providers, you can customize every component:\n\n```python\nfrom mem0 import Memory\n\nconfig = {\n \"llm\": {\n \"provider\": \"openai\", # or \"anthropic\", \"azure_openai\", etc.\n \"config\": {\n \"model\": \"gpt-4o-mini\",\n \"temperature\": 0.1, # low temp for deterministic extraction\n },\n },\n \"embedder\": {\n \"provider\": \"openai\",\n \"config\": {\n \"model\": \"text-embedding-3-small\",\n \"embedding_dims\": 1536,\n },\n },\n \"vector_store\": {\n \"provider\": \"qdrant\", # or \"chroma\", \"pgvector\", \"milvus\"\n \"config\": {\n \"host\": \"localhost\",\n \"port\": 6333,\n \"collection_name\": \"my_app_memories\",\n },\n },\n}\n\nm = Memory.from_config(config)\n```\n\n**Supported providers:**\n- **LLM**: OpenAI, Anthropic, Azure OpenAI, Google AI, Groq, Ollama, and more\n- **Embedder**: OpenAI, Hugging Face, Google, Ollama\n- **Vector Store**: Qdrant, Chroma, pgvector, Milvus, Pinecone, Weaviate\n\nSee the [Mem0 Configuration docs](https://docs.mem0.ai/open-source/configuration?utm_source=nirdiamant&utm_medium=github&utm_campaign=agent_memory_techniques) for the full reference." }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Multi-User Isolation\n", "\n", "Each `user_id` gets its own isolated memory space.\n", "Carol's peanut allergy won't leak into Dave's results, even though they share the same Mem0 instance." ] }, { "cell_type": "code", "execution_count": null, "id": "89abca0b", "metadata": {}, "outputs": [], "source": [ "# ── Multi-user isolation demo ──\n", "# Memories are strictly scoped by user_id (no leakage).\n", "\n", "# Add memories for two different users\n", "m.add(\"I'm allergic to peanuts and I love spicy food\", user_id=\"carol\")\n", "m.add(\"I adore peanut butter and I can't handle any spice\", user_id=\"dave\")\n", "\n", "# Search for food preferences. Each user sees only their own\n", "print(\"=== Carol's food preferences ===\")\n", "carol_food = m.search(\"food preferences and allergies\", user_id=\"carol\")\n", "for mem in (carol_food.get(\"results\", carol_food) if isinstance(carol_food, dict) else carol_food):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "print()\n", "print(\"=== Dave's food preferences ===\")\n", "dave_food = m.search(\"food preferences and allergies\", user_id=\"dave\")\n", "for mem in (dave_food.get(\"results\", dave_food) if isinstance(dave_food, dict) else dave_food):\n", " if isinstance(mem, dict):\n", " print(f\" \\u2022 {mem.get('memory', mem.get('text', str(mem)))}\")\n", "\n", "print(\"\\n\\u2713 User isolation confirmed. No cross-contamination\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Metadata for Categorization\n", "\n", "You can attach metadata (extra labels) to memories when you add them.\n", "This helps organize memories into categories like \"travel\" or \"budget\" for structured retrieval." ] }, { "cell_type": "code", "execution_count": null, "id": "168717cc", "metadata": {}, "outputs": [], "source": [ "# ── Using metadata for categorization ──\n", "# You can attach metadata to memories for organized retrieval.\n", "\n", "m.add(\n", " \"I prefer window seats on flights and I'm in the Delta SkyMiles program\",\n", " user_id=\"alice\",\n", " metadata={\"category\": \"travel_preferences\"},\n", ")\n", "\n", "m.add(\n", " \"My budget for dinner is usually around $30-40 per person\",\n", " user_id=\"alice\",\n", " metadata={\"category\": \"budget\"},\n", ")\n", "\n", "# Search with metadata context\n", "print(\"=== All Alice's memories (with metadata) ===\")\n", "all_alice = m.get_all(user_id=\"alice\")\n", "mem_list = all_alice.get(\"results\", all_alice) if isinstance(all_alice, dict) else all_alice\n", "for mem in mem_list:\n", " text = mem.get(\"memory\", mem.get(\"text\", str(mem)))\n", " meta = mem.get(\"metadata\", {})\n", " cat = meta.get(\"category\", \"auto\") if meta else \"auto\"\n", " print(f\" [{cat}] {text}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cleanup\n", "\n", "Remove all demo memories to keep the local Qdrant store tidy." ] }, { "cell_type": "code", "execution_count": null, "id": "c8ed266a", "metadata": {}, "outputs": [], "source": [ "# ── Cleanup: remove demo memories ──\n", "# Delete all memories for demo users to keep things tidy.\n", "\n", "for uid in [\"alice\", \"bob\", \"carol\", \"dave\"]:\n", " m.delete_all(user_id=uid)\n", " print(f\"\\u2713 Deleted all memories for '{uid}'\")\n", "\n", "print(\"\\n\\u2713 Cleanup complete\")" ] }, { "cell_type": "markdown", "id": "9a59425e", "metadata": {}, "source": "## Discussion & Tradeoffs\n\n### Strengths\n\n- **Minimal setup code:** A single `add()` call handles extraction, deduplication, and storage. No need to build custom NLP pipelines, manage embedding models, or write vector database queries.\n- **Automatic conflict resolution:** When a user's preferences or facts change, Mem0 detects the contradiction and updates the stored memory. This is surprisingly hard to do correctly with a DIY approach.\n- **User isolation by default:** Memory scoping by `user_id` is built in. This prevents the cross-contamination bugs that plague hand-rolled memory systems.\n- **Self-improving:** Repeated `add()` calls refine and merge related memories. The memory profile becomes more accurate and concise over time.\n- **Provider flexibility:** You can swap LLMs, embedders, and vector stores through configuration. No application code changes needed.\n\n### Weaknesses\n\n- **Opaque extraction:** You don't control exactly what gets extracted. Mem0's LLM pipeline decides what's \"memorable.\" This may not align with your domain's needs. Critical facts can be missed, or irrelevant details stored.\n- **LLM cost per add:** Every `add()` triggers an LLM call for extraction. In high-throughput applications, this adds significant cost. Batching strategies help but aren't built in.\n- **Limited query control:** Semantic search is capable but you can't run structured queries as easily. For example, \"all memories where category = 'dietary' AND created after 2025-01-01\" is harder than with a relational database.\n- **Local Qdrant limitations:** The default local Qdrant is file-based and single-process. Production deployments need a proper Qdrant server, pgvector, or a cloud-hosted vector store.\n- **No built-in TTL or decay:** TTL (Time To Live) is a rule that automatically deletes data after a set period. Mem0 memories persist forever unless you explicitly delete them. For long-lived agents, you need to implement your own forgetting strategy on top of Mem0.\n\n### When to Use Mem0 vs. Build Your Own\n\n| Scenario | Recommendation |\n|----------|---------------|\n| Prototyping a personalized chatbot | **Use Mem0.** Fastest path to working memory. |\n| You need fine-grained control over what gets stored | **Build your own.** Mem0's extraction is opaque. |\n| Multi-user application with user isolation | **Use Mem0.** Isolation is built in. |\n| High-throughput (>1000 messages/min) | **Build your own.** Avoid per-message LLM costs. |\n| You need memory decay, TTLs, or forgetting | **Build your own** (or layer on top of Mem0). |\n| Team wants to ship fast without memory infrastructure | **Use Mem0.** Single dependency, no vector DB setup. |\n| You need graph-based memory (relationships between entities) | **Consider Graphiti or Zep.** Mem0 is flat, not graph-based. |\n\n### Mem0 vs. Other Memory Frameworks\n\n| Aspect | Mem0 | Zep | Letta (MemGPT) |\n|--------|------|-----|-----------------|\n| Primary model | Flat memory store | Temporal knowledge graph | Self-editing memory blocks |\n| Extraction | Automatic via LLM | Dialog classification + entity extraction | Inner monologue |\n| Conflict resolution | Built-in | Graph-based | Agent-driven |\n| Self-hosted | Yes (OSS) | Yes (OSS) | Yes (OSS) |\n| Managed cloud | Yes | Yes | Yes |\n| Best for | Straightforward personalization | Complex relationships | Agentic memory management |" }, { "cell_type": "markdown", "id": "c8b03135", "metadata": {}, "source": "## Further Reading\n\n- [Mem0 Documentation](https://docs.mem0.ai/?utm_source=nirdiamant&utm_medium=github&utm_campaign=agent_memory_techniques): Official docs covering all operations, configuration, and integrations.\n- [Mem0 GitHub Repository](https://github.com/mem0ai/mem0): Open-source code, issues, and examples.\n- [Mem0 Python SDK on PyPI](https://pypi.org/project/mem0ai/?utm_source=nirdiamant&utm_medium=github&utm_campaign=agent_memory_techniques): Package details and version history.\n- [Mem0 Platform](https://app.mem0.ai/?utm_source=nirdiamant&utm_medium=github&utm_campaign=agent_memory_techniques): Managed cloud service with dashboard and analytics.\n- [Anthropic: Building Effective Agents (2025)](https://www.anthropic.com/engineering/building-effective-agents?utm_source=nirdiamant&utm_medium=github&utm_campaign=agent_memory_techniques): Agent architecture patterns that complement memory.\n- [Letta (MemGPT): Self-Editing Memory](https://github.com/letta-ai/letta): An alternative approach where the agent manages memory with inner monologue.\n- [Zep: Temporal Knowledge Graphs](https://github.com/getzep/zep): An alternative approach using graph-based memory with temporal awareness.\n\n---\n\n*← Previous: [24 - Graph Memory with Graphiti](../24_graph_memory_graphiti/) · Next: [26 - Letta (MemGPT) Patterns](../26_letta_memgpt_patterns/) →*" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 🧪 Try It Yourself\n", "\n", "Three small challenges to deepen your understanding. Each should take 10-30 minutes.\n", "\n", "### Challenge 1: User-scoped isolation\n", "Create two users by calling `memory.add()` with different `user_id` values. Store 5 facts for each user. Then call `memory.search()` scoped to each user and verify no cross-user leakage. Print both result sets side by side.\n", "\n", "### Challenge 2: Deduplication accuracy\n", "Add 10 semantically similar facts (paraphrases of the same information) via `memory.add()`. Call `memory.get_all()` and count how many distinct entries Mem0 created vs. how many it merged. Compute the deduplication rate and note any cases where it kept duplicates or wrongly merged different facts.\n", "\n", "### Challenge 3: Full memory lifecycle\n", "Walk a single fact through its complete lifecycle: `add()` it, `search()` for it, `update()` it with new information, `search()` again to confirm the update, then `delete()` it. Verify it no longer appears in `get_all()`. This end-to-end flow mirrors the tool-based control pattern from 23 Memory with Tools.\n" ], "id": "3836cb5e1118" }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](https://europe-west1-amt-views-tracker.cloudfunctions.net/amt-tracker?notebook=all-techniques--25-mem0-patterns--mem0-patterns)\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 5 }