chore: import upstream snapshot with attribution
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: "Configure the OSS Stack"
|
||||
description: "Configure Mem0 OSS in Python or TypeScript with your own LLM, embedder, and vector store."
|
||||
icon: "sliders"
|
||||
---
|
||||
|
||||
Mem0 OSS works out of the box with OpenAI defaults. Point it at your own LLM, embedder, and vector store by passing a config when you create `Memory`. The Python SDK also supports a reranker and graph memory.
|
||||
|
||||
<Info>
|
||||
**Prerequisites**
|
||||
- Python 3.10+ (`pip`) or Node.js 18+ (`npm`)
|
||||
- A running vector store such as Qdrant or Postgres + pgvector (Python's default Qdrant and Node's in-memory store need nothing extra)
|
||||
- API keys for your chosen LLM and embedder providers
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
New to Mem0 OSS? Run the <Link href="/open-source/python-quickstart">Python</Link> or <Link href="/open-source/node-quickstart">Node.js</Link> quickstart first, then come back to swap in your own providers.
|
||||
</Tip>
|
||||
|
||||
## Install dependencies
|
||||
|
||||
<CodeGroup>
|
||||
```bash pip
|
||||
pip install mem0ai
|
||||
```
|
||||
|
||||
```bash npm
|
||||
npm install mem0ai
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Using Qdrant as your vector store? Install its Python client (the Node SDK talks to Qdrant over REST) and run the server locally:
|
||||
|
||||
```bash
|
||||
pip install qdrant-client # Python only
|
||||
docker run -p 6333:6333 qdrant/qdrant
|
||||
```
|
||||
|
||||
## Define your configuration
|
||||
|
||||
Each component takes a `provider` and a `config`. Keys are `snake_case` in Python and `camelCase` in TypeScript. Pass the config when you create `Memory`:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from mem0 import Memory
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {"host": "localhost", "port": 6333},
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {"model": "gpt-5-mini", "temperature": 0.1},
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {"model": "text-embedding-3-small"},
|
||||
},
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
"config": {"model": "rerank-v3.5"},
|
||||
},
|
||||
}
|
||||
|
||||
memory = Memory.from_config(config)
|
||||
```
|
||||
|
||||
```ts Node.js
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const memory = new Memory({
|
||||
llm: {
|
||||
provider: "openai",
|
||||
config: { apiKey: process.env.OPENAI_API_KEY || "", model: "gpt-5-mini", temperature: 0.1 },
|
||||
},
|
||||
embedder: {
|
||||
provider: "openai",
|
||||
config: { apiKey: process.env.OPENAI_API_KEY || "", model: "text-embedding-3-small" },
|
||||
},
|
||||
vectorStore: {
|
||||
provider: "qdrant",
|
||||
config: { host: "localhost", port: 6333, collectionName: "memories" },
|
||||
},
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Set your provider keys as environment variables:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="..."
|
||||
export COHERE_API_KEY="..." # Python reranker only
|
||||
```
|
||||
|
||||
<Note>
|
||||
The TypeScript OSS SDK configures the LLM, embedder, vector store, and history store. Reranker and graph memory are Python-only today.
|
||||
</Note>
|
||||
|
||||
Prefer a config file? Load YAML into Python's `from_config`:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
from mem0 import Memory
|
||||
|
||||
with open("config.yaml") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
memory = Memory.from_config(config)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Verify it works: add a memory and search it back. `memory.add(...)` followed by `memory.search(...)` should populate your vector store and return the memory as a top hit.
|
||||
</Info>
|
||||
|
||||
## Available providers
|
||||
|
||||
Change the `provider` string to switch backends. The most common options:
|
||||
|
||||
| Component | Python | TypeScript |
|
||||
| --- | --- | --- |
|
||||
| LLM | `openai`, `anthropic`, `gemini`, `groq`, `ollama`, `aws_bedrock`, `azure_openai`, `litellm` | `openai`, `anthropic`, `gemini`, `groq`, `ollama`, `aws_bedrock`, `azure_openai`, `mistral`, `deepseek` |
|
||||
| Embedder | `openai`, `gemini`, `azure_openai`, `ollama`, `huggingface`, `vertexai`, `aws_bedrock` | `openai`, `gemini`, `azure_openai`, `ollama` |
|
||||
| Vector store | `qdrant`, `pgvector`, `chroma`, `pinecone`, `redis`, `weaviate`, `milvus`, `elasticsearch` | `memory`, `qdrant`, `pgvector`, `redis`, `supabase`, `azure-ai-search`, `vectorize`, `milvus` |
|
||||
|
||||
See the full catalog in <Link href="/components/llms/overview">Components</Link>.
|
||||
|
||||
## Tune component settings
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Vector store collections">
|
||||
Name collections explicitly in production (`collection_name` / `collectionName`) to isolate tenants and enable per-tenant retention policies.
|
||||
</Accordion>
|
||||
<Accordion title="LLM extraction temperature">
|
||||
Keep extraction temperature at or below 0.2 so memories stay deterministic. Raise it only when you see facts being missed.
|
||||
</Accordion>
|
||||
<Accordion title="Reranker depth (Python)">
|
||||
Limit `top_k` to 10 to 20 results. Sending more adds latency without meaningful gains.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Warning>
|
||||
Mixing managed and self-hosted components? Make sure every outbound provider call has a secure network path. Managed rerankers and embedders often require outbound internet even if your vector store is on-prem.
|
||||
</Warning>
|
||||
|
||||
## Quick recovery
|
||||
|
||||
- Qdrant connection errors: confirm port `6333` is exposed and the API key (if set) matches.
|
||||
- Empty search results: verify the embedder model name. A mismatch causes dimension errors.
|
||||
- `Unknown reranker` (Python): upgrade the SDK with `pip install --upgrade mem0ai` to load the latest provider registry.
|
||||
- `Cannot find module` (Node): import from the OSS entry point, `import { Memory } from "mem0ai/oss"`, not `"mem0ai"`.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Pick Providers"
|
||||
description="Browse the LLM, vector store, embedder, and reranker catalogs."
|
||||
icon="sitemap"
|
||||
href="/components/llms/overview"
|
||||
/>
|
||||
<Card
|
||||
title="Deploy with Docker Compose"
|
||||
description="Follow the end-to-end OSS deployment walkthrough."
|
||||
icon="server"
|
||||
href="/open-source/features/rest-api"
|
||||
/>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,414 @@
|
||||
---
|
||||
title: Async Memory
|
||||
description: Run Mem0 operations without blocking your event loop.
|
||||
icon: "bolt"
|
||||
---
|
||||
|
||||
`AsyncMemory` gives you a non-blocking interface to Mem0’s storage layer so Python applications can add, search, and manage memories directly from async code. Use it when you embed Mem0 inside FastAPI services, background workers, or any workflow that relies on `asyncio`.
|
||||
|
||||
<Info>
|
||||
**You’ll use this when…**
|
||||
- Your agent already runs in an async framework and you need memory calls to await cleanly.
|
||||
- You want to embed Mem0’s storage locally without sending requests through the synchronous client.
|
||||
- You plan to mix memory operations with other async APIs (OpenAI, HTTP calls, databases).
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
`AsyncMemory` expects a running event loop. Always call it inside `async def` functions or through helpers like `asyncio.run()` to avoid runtime errors.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Working in TypeScript? The OSS `Memory` class in the Node SDK (`mem0ai/oss`) is also fully async: every method returns a `Promise` and must be `await`ed. Python’s `AsyncMemory` serves the same purpose within Python async frameworks like FastAPI. Both runtimes support awaited memory operations; choose the SDK that matches your language.
|
||||
</Note>
|
||||
|
||||
## Feature anatomy
|
||||
|
||||
- **Direct storage access:** `AsyncMemory` talks to the same backends as the synchronous client but keeps everything in-process for lower latency.
|
||||
- **Method parity:** Each memory operation (`add`, `search`, `get_all`, `delete`, etc.) mirrors the synchronous API, letting you reuse payload shapes.
|
||||
- **Concurrent execution:** Non-blocking I/O lets you schedule multiple memory tasks with `asyncio.gather`.
|
||||
- **Scoped organization:** Continue using `user_id`, `agent_id`, and `run_id` to separate memories across sessions and agents.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Async method parity">
|
||||
| Operation | Async signature | Notes |
|
||||
| --- | --- | --- |
|
||||
| Create memories | `await memory.add(...)` | Same arguments as synchronous `Memory.add`. |
|
||||
| Search memories | `await memory.search(...)` | Returns dict with `results`, identical shape. |
|
||||
| List memories | `await memory.get_all(...)` | Filter by `user_id`, `agent_id`, `run_id`. |
|
||||
| Retrieve memory | `await memory.get(memory_id=...)` | Raises `ValueError` if ID is invalid. |
|
||||
| Update memory | `await memory.update(memory_id=..., text=...)` | Accepts partial updates. |
|
||||
| Delete memory | `await memory.delete(memory_id=...)` | Returns confirmation payload. |
|
||||
| Delete in bulk | `await memory.delete_all(...)` | Requires at least one scope filter. |
|
||||
| History | `await memory.history(memory_id=...)` | Fetches change log for auditing. |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
### Initialize the client
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
# Default configuration
|
||||
memory = AsyncMemory()
|
||||
|
||||
# Custom configuration
|
||||
from mem0.configs.base import MemoryConfig
|
||||
custom_config = MemoryConfig(
|
||||
# Your custom configuration here
|
||||
)
|
||||
memory = AsyncMemory(config=custom_config)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Run `await memory.search(...)` once right after initialization. If it returns memories without errors, your configuration works.
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
Keep configuration objects close to the async client so you can reuse them across workers without recreating vector store connections.
|
||||
</Tip>
|
||||
|
||||
### Manage lifecycle and concurrency
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_memory():
|
||||
memory = AsyncMemory()
|
||||
try:
|
||||
yield memory
|
||||
finally:
|
||||
# Clean up resources if needed
|
||||
pass
|
||||
|
||||
async def safe_memory_usage():
|
||||
async with get_memory() as memory:
|
||||
return await memory.search("test query", filters={"user_id": "alice"})
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Wrap the client in an async context manager when you need a clean shutdown (for example, inside FastAPI startup/shutdown hooks).
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
async def batch_operations():
|
||||
memory = AsyncMemory()
|
||||
|
||||
tasks = [
|
||||
memory.add(
|
||||
messages=[{"role": "user", "content": f"Message {i}"}],
|
||||
user_id=f"user_{i}"
|
||||
)
|
||||
for i in range(5)
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
for i, result in enumerate(results):
|
||||
if isinstance(result, Exception):
|
||||
print(f"Task {i} failed: {result}")
|
||||
else:
|
||||
print(f"Task {i} completed successfully")
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
When concurrency works correctly, successful tasks return memory IDs while failures surface as exceptions in the `results` list.
|
||||
</Info>
|
||||
|
||||
### Add resilience with retries
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
async def with_timeout_and_retry(operation, max_retries=3, timeout=10.0):
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
return await asyncio.wait_for(operation(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
print(f"Timeout on attempt {attempt + 1}")
|
||||
except Exception as exc:
|
||||
print(f"Error on attempt {attempt + 1}: {exc}")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
await asyncio.sleep(2 ** attempt)
|
||||
|
||||
raise Exception(f"Operation failed after {max_retries} attempts")
|
||||
|
||||
async def robust_memory_search():
|
||||
memory = AsyncMemory()
|
||||
|
||||
async def search_operation():
|
||||
return await memory.search("test query", filters={"user_id": "alice"})
|
||||
|
||||
return await with_timeout_and_retry(search_operation)
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Always cap retries. Runaway loops can keep the event loop busy and block other tasks.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## See it in action
|
||||
|
||||
### Core operations
|
||||
|
||||
```python
|
||||
# Create memories
|
||||
result = await memory.add(
|
||||
messages=[
|
||||
{"role": "user", "content": "I'm travelling to SF"},
|
||||
{"role": "assistant", "content": "That's great to hear!"}
|
||||
],
|
||||
user_id="alice"
|
||||
)
|
||||
|
||||
# Search memories
|
||||
results = await memory.search(
|
||||
query="Where am I travelling?",
|
||||
filters={"user_id": "alice"}
|
||||
)
|
||||
|
||||
# List memories
|
||||
all_memories = await memory.get_all(filters={"user_id": "alice"})
|
||||
|
||||
# Get a specific memory
|
||||
specific_memory = await memory.get(memory_id="memory-id-here")
|
||||
|
||||
# Update a memory
|
||||
updated_memory = await memory.update(
|
||||
memory_id="memory-id-here",
|
||||
text="I'm travelling to Seattle"
|
||||
)
|
||||
|
||||
# Delete a memory
|
||||
await memory.delete(memory_id="memory-id-here")
|
||||
|
||||
# Delete scoped memories
|
||||
await memory.delete_all(user_id="alice")
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Confirm each call returns the same response fields as the synchronous client (IDs, `results`, or confirmation objects). Missing keys usually mean the coroutine wasn’t awaited.
|
||||
</Info>
|
||||
|
||||
<Note>
|
||||
`delete_all` requires at least one of `user_id`, `agent_id`, or `run_id`. Provide all three to narrow deletion to a single session.
|
||||
</Note>
|
||||
|
||||
### Scoped organization
|
||||
|
||||
```python
|
||||
await memory.add(
|
||||
messages=[{"role": "user", "content": "I prefer vegetarian food"}],
|
||||
user_id="alice",
|
||||
agent_id="diet-assistant",
|
||||
run_id="consultation-001"
|
||||
)
|
||||
|
||||
all_user_memories = await memory.get_all(filters={"user_id": "alice"})
|
||||
agent_memories = await memory.get_all(filters={"user_id": "alice", "agent_id": "diet-assistant"})
|
||||
session_memories = await memory.get_all(filters={"user_id": "alice", "run_id": "consultation-001"})
|
||||
specific_memories = await memory.get_all(
|
||||
filters={"user_id": "alice", "agent_id": "diet-assistant", "run_id": "consultation-001"}
|
||||
)
|
||||
|
||||
history = await memory.history(memory_id="memory-id-here")
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Use `history` when you need audit trails for compliance or debugging update logic.
|
||||
</Tip>
|
||||
|
||||
### Blend with other async APIs
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from openai import AsyncOpenAI
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
async_openai_client = AsyncOpenAI()
|
||||
async_memory = AsyncMemory()
|
||||
|
||||
async def chat_with_memories(message: str, user_id: str = "default_user") -> str:
|
||||
search_result = await async_memory.search(query=message, filters={"user_id": user_id}, top_k=3)
|
||||
relevant_memories = search_result["results"]
|
||||
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories)
|
||||
|
||||
system_prompt = (
|
||||
"You are a helpful AI. Answer the question based on query and memories.\n"
|
||||
f"User Memories:\n{memories_str}"
|
||||
)
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": message},
|
||||
]
|
||||
|
||||
response = await async_openai_client.chat.completions.create(
|
||||
model="gpt-5-mini",
|
||||
messages=messages
|
||||
)
|
||||
|
||||
assistant_response = response.choices[0].message.content
|
||||
messages.append({"role": "assistant", "content": assistant_response})
|
||||
await async_memory.add(messages, user_id=user_id)
|
||||
|
||||
return assistant_response
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
When everything is wired correctly, the OpenAI response should incorporate recent memories and the follow-up `add` call should persist the new assistant turn.
|
||||
</Info>
|
||||
|
||||
### Handle errors gracefully
|
||||
|
||||
```python
|
||||
from mem0 import AsyncMemory
|
||||
from mem0.configs.base import MemoryConfig
|
||||
|
||||
async def handle_initialization_errors():
|
||||
try:
|
||||
config = MemoryConfig(
|
||||
vector_store={"provider": "chroma", "config": {"path": "./chroma_db"}},
|
||||
llm={"provider": "openai", "config": {"model": "gpt-5-mini"}}
|
||||
)
|
||||
AsyncMemory(config=config)
|
||||
print("AsyncMemory initialized successfully")
|
||||
except ValueError as err:
|
||||
print(f"Configuration error: {err}")
|
||||
except ConnectionError as err:
|
||||
print(f"Connection error: {err}")
|
||||
|
||||
async def handle_memory_operation_errors():
|
||||
memory = AsyncMemory()
|
||||
try:
|
||||
await memory.get(memory_id="non-existent-id")
|
||||
except ValueError as err:
|
||||
print(f"Invalid memory ID: {err}")
|
||||
|
||||
try:
|
||||
await memory.search(query="", filters={"user_id": "alice"})
|
||||
except ValueError as err:
|
||||
print(f"Invalid search query: {err}")
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Catch and log `ValueError` exceptions from invalid inputs: async stack traces can otherwise disappear inside background tasks.
|
||||
</Warning>
|
||||
|
||||
### Serve through FastAPI
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
app = FastAPI()
|
||||
memory = AsyncMemory()
|
||||
|
||||
@app.post("/memories/")
|
||||
async def add_memory(messages: list, user_id: str):
|
||||
try:
|
||||
result = await memory.add(messages=messages, user_id=user_id)
|
||||
return {"status": "success", "data": result}
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
@app.get("/memories/search")
|
||||
async def search_memories(query: str, user_id: str, limit: int = 10):
|
||||
try:
|
||||
result = await memory.search(query=query, filters={"user_id": user_id}, top_k=limit)
|
||||
return {"status": "success", "data": result}
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Create one `AsyncMemory` instance per process when using FastAPI: startup hooks are a good place to configure and reuse it.
|
||||
</Tip>
|
||||
|
||||
### Instrument logging
|
||||
|
||||
```python
|
||||
import logging
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def log_async_operation(operation_name):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
logger.info(f"Starting {operation_name}")
|
||||
try:
|
||||
result = await func(*args, **kwargs)
|
||||
duration = time.time() - start_time
|
||||
logger.info(f"{operation_name} completed in {duration:.2f}s")
|
||||
return result
|
||||
except Exception as exc:
|
||||
duration = time.time() - start_time
|
||||
logger.error(f"{operation_name} failed after {duration:.2f}s: {exc}")
|
||||
raise
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@log_async_operation("Memory Add")
|
||||
async def logged_memory_add(memory, messages, user_id):
|
||||
return await memory.add(messages=messages, user_id=user_id)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Logged durations give you the baseline needed to spot regressions once AsyncMemory is in production.
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- Run a quick add/search cycle and confirm the returned memory content matches your input.
|
||||
- Inspect application logs to ensure async tasks complete without blocking the event loop.
|
||||
- In FastAPI or other frameworks, hit health endpoints to verify the shared client handles concurrent requests.
|
||||
- Monitor retry counters: unexpected spikes indicate configuration or connectivity issues.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Keep operations awaited:** Forgetting `await` is the fastest way to miss writes: lint for it or add helper wrappers.
|
||||
2. **Scope deletions carefully:** Always supply `user_id`, `agent_id`, or `run_id` to avoid purging too much data.
|
||||
3. **Batch writes thoughtfully:** Use `asyncio.gather` for throughput but cap concurrency based on backend capacity.
|
||||
4. **Log errors with context:** Capture user and agent scopes to triage failures quickly.
|
||||
5. **Reuse clients:** Instantiate `AsyncMemory` once per worker to avoid repeated backend handshakes.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Possible causes | Fix |
|
||||
| --- | --- | --- |
|
||||
| Initialization fails | Missing dependencies, invalid config | Validate `MemoryConfig` settings and environment variables. |
|
||||
| Slow operations | Large datasets, network latency | Cache heavy queries and tune vector store parameters. |
|
||||
| Memory not found | Invalid ID or deleted record | Check ID source and handle soft-deleted states. |
|
||||
| Connection timeouts | Network issues, overloaded backend | Apply retries/backoff and inspect infrastructure health. |
|
||||
| Out-of-memory errors | Oversized batches | Reduce concurrency or chunk operations into smaller sets. |
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Master Memory Operations" icon="layers" href="/core-concepts/memory-operations/add">
|
||||
Review how add, search, update, and delete behave across synchronous and async clients.
|
||||
</Card>
|
||||
<Card title="Connect Async Agents" icon="plug" href="/cookbooks/integrations/openai-tool-calls">
|
||||
Follow a full workflow that mixes AsyncMemory with OpenAI tool-call automation.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: Custom Instructions
|
||||
description: Tailor fact extraction so Mem0 stores only the details you care about.
|
||||
icon: "wand-magic-sparkles"
|
||||
---
|
||||
|
||||
Custom instructions let you decide exactly which facts Mem0 records from a conversation. Define a focused prompt, give a few examples, and Mem0 will add only the memories that match your use case.
|
||||
|
||||
<Info>
|
||||
**You'll use this when...**
|
||||
- A project needs domain-specific facts (order numbers, customer info) without storing casual chatter.
|
||||
- You already have a clear schema for memories and want the LLM to follow it.
|
||||
- You must prevent irrelevant details from entering long-term storage.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
Prompts that are too broad cause unrelated facts to slip through. Keep instructions tight and test them with real transcripts.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The `custom_fact_extraction_prompt` parameter has been renamed to `custom_instructions`. If you are upgrading from an older version, update your configuration accordingly.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Feature anatomy
|
||||
|
||||
- **Prompt instructions:** Describe which entities or phrases to keep. Specific guidance keeps the extractor focused.
|
||||
- **Few-shot examples:** Show positive and negative cases so the model copies the right format.
|
||||
- **Structured output:** Responses return JSON with a `facts` array that Mem0 converts into individual memories.
|
||||
- **LLM configuration:** `custom_instructions` (Python) or `customInstructions` (TypeScript) lives alongside your model settings.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Prompt blueprint">
|
||||
1. State the allowed fact types.
|
||||
2. Include short examples that mirror production messages.
|
||||
3. Show both empty (`[]`) and populated outputs.
|
||||
4. Remind the model to return JSON with a `facts` key only.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
### Write the custom prompt
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
custom_instructions = """
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
Here are some few shot examples:
|
||||
|
||||
Input: Hi.
|
||||
Output: {"facts" : []}
|
||||
|
||||
Input: The weather is nice today.
|
||||
Output: {"facts" : []}
|
||||
|
||||
Input: My order #12345 hasn't arrived yet.
|
||||
Output: {"facts" : ["Order #12345 not received"]}
|
||||
|
||||
Input: I'm John Doe, and I'd like to return the shoes I bought last week.
|
||||
Output: {"facts" : ["Customer name: John Doe", "Wants to return shoes", "Purchase made last week"]}
|
||||
|
||||
Input: I ordered a red shirt, size medium, but received a blue one instead.
|
||||
Output: {"facts" : ["Ordered red shirt, size medium", "Received blue shirt instead"]}
|
||||
|
||||
Return the facts and customer information in a json format as shown above.
|
||||
"""
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
const customInstructions = `
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
Here are some few shot examples:
|
||||
|
||||
Input: Hi.
|
||||
Output: {"facts" : []}
|
||||
|
||||
Input: The weather is nice today.
|
||||
Output: {"facts" : []}
|
||||
|
||||
Input: My order #12345 hasn't arrived yet.
|
||||
Output: {"facts" : ["Order #12345 not received"]}
|
||||
|
||||
Input: I am John Doe, and I would like to return the shoes I bought last week.
|
||||
Output: {"facts" : ["Customer name: John Doe", "Wants to return shoes", "Purchase made last week"]}
|
||||
|
||||
Input: I ordered a red shirt, size medium, but received a blue one instead.
|
||||
Output: {"facts" : ["Ordered red shirt, size medium", "Received blue shirt instead"]}
|
||||
|
||||
Return the facts and customer information in a json format as shown above.
|
||||
`;
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Tip>
|
||||
Keep example pairs short and mirror the capitalization, punctuation, and tone you see in real user messages.
|
||||
</Tip>
|
||||
|
||||
### Load the prompt in configuration
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from mem0 import Memory
|
||||
|
||||
config = {
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-5-mini",
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 2000,
|
||||
}
|
||||
},
|
||||
"custom_instructions": custom_instructions,
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const config = {
|
||||
llm: {
|
||||
provider: "openai",
|
||||
config: {
|
||||
apiKey: process.env.OPENAI_API_KEY ?? "",
|
||||
model: "gpt-4-turbo-preview",
|
||||
temperature: 0.2,
|
||||
maxTokens: 1500,
|
||||
},
|
||||
},
|
||||
customInstructions: customInstructions,
|
||||
};
|
||||
|
||||
const memory = new Memory(config);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Info icon="check">
|
||||
After initialization, run a quick `add` call with a known example and confirm the response splits into separate facts.
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## See it in action
|
||||
|
||||
### Example: Order support memory
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
m.add("Yesterday, I ordered a laptop, the order id is 12345", user_id="alice")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
await memory.add("Yesterday, I ordered a laptop, the order id is 12345", { userId: "user123" });
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{"memory": "Ordered a laptop", "event": "ADD"},
|
||||
{"memory": "Order ID: 12345", "event": "ADD"},
|
||||
{"memory": "Order placed yesterday", "event": "ADD"}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Info icon="check">
|
||||
The output contains only the facts described in your prompt, each stored as a separate memory entry.
|
||||
</Info>
|
||||
|
||||
### Example: Irrelevant message filtered out
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
m.add("I like going to hikes", user_id="alice")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
await memory.add("I like going to hikes", { userId: "user123" });
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": []
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Tip>
|
||||
Empty `results` show the prompt successfully ignored content outside your target domain.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- Log every call during rollout and confirm the `facts` array matches your schema.
|
||||
- Check that unrelated messages return an empty `results` array.
|
||||
- Run regression samples whenever you edit the prompt to ensure previously accepted facts still pass.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Be precise:** Call out the exact categories or fields you want to capture.
|
||||
2. **Show negative cases:** Include examples that should produce `[]` so the model learns to skip them.
|
||||
3. **Keep JSON strict:** Avoid extra keys; only return `facts` to simplify downstream parsing.
|
||||
4. **Version prompts:** Track prompt changes with a version number so you can roll back quickly.
|
||||
5. **Review outputs regularly:** Spot-check stored memories to catch drift early.
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Review Add Operations" icon="list" href="/core-concepts/memory-operations/add">
|
||||
Refresh how Mem0 stores memories and how prompts influence fact creation.
|
||||
</Card>
|
||||
<Card title="Automate Support Triage" icon="inbox" href="/cookbooks/operations/support-inbox">
|
||||
Apply custom extraction to route customer requests in a full workflow.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,430 @@
|
||||
---
|
||||
title: Enhanced Metadata Filtering
|
||||
description: Fine-grained metadata queries for precise OSS memory retrieval.
|
||||
icon: "filter"
|
||||
---
|
||||
|
||||
Enhanced metadata filtering in Mem0 lets you run complex queries across memory metadata. Combine comparisons, logical operators, and wildcard matches to zero in on the exact memories your agent needs.
|
||||
|
||||
---
|
||||
|
||||
## Feature anatomy
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Operator quick reference">
|
||||
| Operator | Meaning | When to use it |
|
||||
| --- | --- | --- |
|
||||
| `eq` / `ne` | Equals / not equals | Exact matches on strings, numbers, or booleans. |
|
||||
| `gt` / `gte` | Greater than / greater than or equal | Rank results by score, confidence, or any numeric field. |
|
||||
| `lt` / `lte` | Less than / less than or equal | Cap numeric values (e.g., ratings, timestamps). |
|
||||
| `in` / `nin` | In list / not in list | Pre-approve or block sets of values without chaining multiple filters. |
|
||||
| `contains` / `icontains` | Case-sensitive / case-insensitive substring match | Scan text fields for keywords. |
|
||||
| `*` | Wildcard | Require that a field exists, regardless of value. |
|
||||
| `AND` / `OR` / `NOT` | Combine filters | Build logic trees so multiple conditions work together. |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Metadata selectors
|
||||
|
||||
Start with key-value filters when you need direct matches on metadata fields.
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
m = Memory()
|
||||
|
||||
# Search with simple metadata filters
|
||||
results = m.search(
|
||||
"What are my preferences?",
|
||||
filters={"user_id": "alice", "category": "preferences"}
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Expect only memories tagged with `category="preferences"` to return for the given `user_id`.
|
||||
</Info>
|
||||
|
||||
### Comparison operators
|
||||
|
||||
Layer greater-than/less-than comparisons to rank results by score, confidence, or any numeric field. Equality helpers (`eq`, `ne`) keep string and boolean checks explicit.
|
||||
|
||||
```python
|
||||
# Greater than / Less than
|
||||
results = m.search(
|
||||
"recent activities",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"score": {"gt": 0.8},
|
||||
"priority": {"gte": 5},
|
||||
"confidence": {"lt": 0.9},
|
||||
"rating": {"lte": 3}
|
||||
}
|
||||
)
|
||||
|
||||
# Equality operators
|
||||
results = m.search(
|
||||
"specific content",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"status": {"eq": "active"},
|
||||
"archived": {"ne": True}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### List-based operators
|
||||
|
||||
Use `in` and `nin` when you want to pre-approve or exclude specific values without writing multiple equality checks.
|
||||
|
||||
```python
|
||||
# In / Not in operators
|
||||
results = m.search(
|
||||
"multi-category search",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"category": {"in": ["food", "travel", "entertainment"]},
|
||||
"status": {"nin": ["deleted", "archived"]}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Verify the response includes only memories in the whitelisted categories and omits any with archived or deleted status.
|
||||
</Info>
|
||||
|
||||
### String operators
|
||||
|
||||
`contains` and `icontains` capture substring matches, making it easy to scan descriptions or tags for keywords without retrieving irrelevant memories.
|
||||
|
||||
```python
|
||||
# Text matching operators
|
||||
results = m.search(
|
||||
"content search",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"title": {"contains": "meeting"},
|
||||
"description": {"icontains": "important"},
|
||||
"tags": {"contains": "urgent"}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Wildcard matching
|
||||
|
||||
Allow any value for a field while still requiring the field to exist: handy when the mere presence of a field matters.
|
||||
|
||||
```python
|
||||
# Match any value for a field
|
||||
results = m.search(
|
||||
"all with category",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"category": "*"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Logical combinations
|
||||
|
||||
Combine filters with `AND`, `OR`, and `NOT` to express complex decision trees. Nest logical operators to encode multi-branch workflows.
|
||||
|
||||
```python
|
||||
# Logical AND
|
||||
results = m.search(
|
||||
"complex query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"category": "work"},
|
||||
{"priority": {"gte": 7}},
|
||||
{"status": {"ne": "completed"}}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# Logical OR
|
||||
results = m.search(
|
||||
"flexible query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"OR": [
|
||||
{"category": "urgent"},
|
||||
{"priority": {"gte": 9}},
|
||||
{"deadline": {"contains": "today"}}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# Logical NOT
|
||||
results = m.search(
|
||||
"exclusion query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"NOT": [
|
||||
{"category": "archived"},
|
||||
{"status": "deleted"}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# Complex nested logic
|
||||
results = m.search(
|
||||
"advanced query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{
|
||||
"OR": [
|
||||
{"category": "work"},
|
||||
{"category": "personal"}
|
||||
]
|
||||
},
|
||||
{"priority": {"gte": 5}},
|
||||
{
|
||||
"NOT": [
|
||||
{"status": "archived"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Inspect the response metadata: each returned memory should satisfy the combined logic tree exactly. If results look too broad, log the raw filters sent to your vector store.
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
Tune your vector store so filter-heavy queries stay fast. Index fields you frequently filter on and keep complex checks for later in the evaluation order.
|
||||
|
||||
```python
|
||||
# Ensure your vector store supports indexing on filtered fields
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333,
|
||||
"indexed_fields": ["category", "priority", "status", "user_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
After enabling indexing, benchmark the same query: latency should drop once the store can prune documents on indexed fields before vector scoring.
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
Put simple key=value filters on indexed fields before your range or text conditions so the store trims results early.
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
# More efficient: Filter on indexed fields first
|
||||
good_filters = {
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"category": "work"},
|
||||
{"content": {"contains": "meeting"}}
|
||||
]
|
||||
}
|
||||
|
||||
# Less efficient: Complex operations first
|
||||
avoid_filters = {
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"description": {"icontains": "complex text search"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
When you reorder filters so indexed fields come first (`good_filters` example), queries typically return faster than the `avoid_filters` pattern where expensive text searches run before simple checks.
|
||||
</Info>
|
||||
|
||||
Vector store support varies. Confirm operator coverage before shipping:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Qdrant">
|
||||
Full comparison, list, and logical support. Handles deeply nested boolean logic efficiently.
|
||||
</Accordion>
|
||||
<Accordion title="Chroma">
|
||||
Equality and basic comparisons only. Limited nesting: break large trees into smaller calls.
|
||||
</Accordion>
|
||||
<Accordion title="Pinecone">
|
||||
Comparisons plus `in`/`nin`. Text operators are constrained; rely on tags where possible.
|
||||
</Accordion>
|
||||
<Accordion title="Weaviate">
|
||||
Full operator coverage with advanced text filters. Best option when you need hybrid text + metadata queries.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Warning>
|
||||
If an operator is unsupported, most stores silently ignore that branch. Add validation before execution so you can fall back to simpler queries instead of returning empty results.
|
||||
</Warning>
|
||||
|
||||
### Migrate from earlier filters
|
||||
|
||||
```python
|
||||
# Before (v0.x) - simple key-value filtering only
|
||||
results = m.search(
|
||||
"query",
|
||||
filters={"user_id": "alice", "category": "work", "status": "active"}
|
||||
)
|
||||
|
||||
# After (v1.0.0) - enhanced filtering with operators
|
||||
results = m.search(
|
||||
"query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"category": "work"},
|
||||
{"status": {"ne": "archived"}},
|
||||
{"priority": {"gte": 5}}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Existing equality filters continue to work; add new operator branches gradually so agents can adopt richer queries without downtime.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## See it in action
|
||||
|
||||
### Project management filtering
|
||||
|
||||
```python
|
||||
# Find high-priority active tasks
|
||||
results = m.search(
|
||||
"What tasks need attention?",
|
||||
filters={
|
||||
"user_id": "project_manager",
|
||||
"AND": [
|
||||
{"project": {"in": ["alpha", ""]}},
|
||||
{"priority": {"gte": 8}},
|
||||
{"status": {"ne": "completed"}},
|
||||
{
|
||||
"OR": [
|
||||
{"assignee": "alice"},
|
||||
{"assignee": "bob"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Tasks returned should belong to the targeted projects, remain incomplete, and be assigned to one of the listed teammates.
|
||||
</Info>
|
||||
|
||||
### Customer support filtering
|
||||
|
||||
```python
|
||||
# Find recent unresolved tickets
|
||||
results = m.search(
|
||||
"pending support issues",
|
||||
filters={
|
||||
"agent_id": "support_bot",
|
||||
"AND": [
|
||||
{"ticket_status": {"ne": "resolved"}},
|
||||
{"priority": {"in": ["high", "critical"]}},
|
||||
{"created_date": {"gte": "2024-01-01"}},
|
||||
{
|
||||
"NOT": [
|
||||
{"category": "spam"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Pair agent ID filters with ticket-specific metadata so shared support bots return only the tickets they can act on in the current session.
|
||||
</Tip>
|
||||
|
||||
### Content recommendation filtering
|
||||
|
||||
```python
|
||||
# Personalized content filtering
|
||||
results = m.search(
|
||||
"recommend content",
|
||||
filters={
|
||||
"user_id": "reader123",
|
||||
"AND": [
|
||||
{
|
||||
"OR": [
|
||||
{"genre": {"in": ["sci-fi", "fantasy"]}},
|
||||
{"author": {"contains": "favorite"}}
|
||||
]
|
||||
},
|
||||
{"rating": {"gte": 4.0}},
|
||||
{"read_status": {"ne": "completed"}},
|
||||
{"language": "english"}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Confirm personalized feeds show only unread titles that meet the rating and language criteria.
|
||||
</Info>
|
||||
|
||||
### Handle invalid operators
|
||||
|
||||
```python
|
||||
try:
|
||||
results = m.search(
|
||||
"test query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"invalid_operator": {"unknown": "value"}
|
||||
}
|
||||
)
|
||||
except ValueError as e:
|
||||
print(f"Filter error: {e}")
|
||||
results = m.search(
|
||||
"test query",
|
||||
filters={"user_id": "alice", "category": "general"}
|
||||
)
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Validate filters before executing searches so you can catch typos or unsupported operators during development instead of at runtime.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- Log the filters sent to your vector store and confirm the response metadata matches every clause.
|
||||
- Benchmark queries before and after indexing to ensure latency improvements materialize.
|
||||
- Add analytics or debug logging to track how often fallbacks execute when operators fail validation.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Use indexed fields first:** Order filters so equality checks run before complex string operations.
|
||||
2. **Combine operators intentionally:** Keep logical trees readable: large nests are harder to debug.
|
||||
3. **Test performance regularly:** Benchmark critical queries with production-like payloads.
|
||||
4. **Plan graceful degradation:** Provide fallback filters when an operator isn’t available.
|
||||
5. **Validate syntax early:** Catch malformed filters during development to protect agents at runtime.
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Explore Vector Store Options" icon="database" href="/components/vectordbs/overview">
|
||||
Compare operator coverage and indexing strategies across supported stores.
|
||||
</Card>
|
||||
<Card title="Tag and Organize Memories" icon="tag" href="/cookbooks/essentials/tagging-and-organizing-memories">
|
||||
Practice building workflows that label and retrieve memories with clear metadata filters.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,331 @@
|
||||
---
|
||||
title: Multimodal Support
|
||||
description: Capture and recall memories from both text and images.
|
||||
icon: "image"
|
||||
---
|
||||
|
||||
Multimodal support lets Mem0 extract facts from images alongside regular text. Add screenshots, receipts, or product photos and Mem0 will store the insights as searchable memories so agents can recall them later.
|
||||
|
||||
<Info>
|
||||
**You’ll use this when…**
|
||||
- Users share screenshots, menus, or documents and you want the details to become memories.
|
||||
- You already collect text conversations but need visual context for better answers.
|
||||
- You want a single workflow that handles both URLs and local image files.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
Mem0 passes images straight to your configured vision model, so per-image size and resolution limits come from that provider (for example, OpenAI caps images at 20 MB). Compress or resize large files to stay within your provider's limits and keep processing fast.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Feature anatomy
|
||||
|
||||
- **Vision processing:** Mem0 runs the image through a vision model that extracts text and key details.
|
||||
- **Memory creation:** Extracted information is stored as standard memories so search, filters, and analytics continue to work.
|
||||
- **Context linking:** Visual and textual turns in the same conversation stay linked, giving agents richer context.
|
||||
- **Flexible inputs:** Accept publicly accessible URLs or base64-encoded local files in both Python and JavaScript SDKs.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Supported formats">
|
||||
| Format | Used for | Notes |
|
||||
| --- | --- | --- |
|
||||
| JPEG / JPG | Photos and screenshots | Default option for camera captures. |
|
||||
| PNG | Images with transparency | Keeps sharp text and UI elements crisp. |
|
||||
| WebP | Web-optimized images | Smaller payloads for faster uploads. |
|
||||
| GIF | Static or animated graphics | Works for simple graphics and short loops. |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
<Warning>
|
||||
You must set `enable_vision: True` in your LLM config for image content to be processed. Without it, image turns are silently dropped and no vision memories are created. Example:
|
||||
```python
|
||||
config = {"llm": {"provider": "openai", "config": {"enable_vision": True, "vision_details": "auto"}}}
|
||||
client = Memory.from_config(config)
|
||||
```
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
`vision_details` maps to the vision model's image `detail` setting and accepts `"auto"` (the default), `"low"`, or `"high"`. Use `"high"` for dense images like receipts or documents; `"low"` is faster and cheaper for simple photos.
|
||||
</Note>
|
||||
|
||||
### Add image messages from URLs
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from mem0 import Memory
|
||||
|
||||
client = Memory()
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "Hi, my name is Alice."},
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "https://example.com/menu.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
client.add(messages, user_id="alice")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const client = new Memory();
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "Hi, my name is Alice." },
|
||||
{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "image_url",
|
||||
image_url: { url: "https://example.com/menu.jpg" }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
await client.add(messages, { userId: "alice" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Info icon="check">
|
||||
Inspect the response payload: the memories list should include entries extracted from the menu image as well as the text turns.
|
||||
</Info>
|
||||
|
||||
### Upload local images as base64
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import base64
|
||||
from mem0 import Memory
|
||||
|
||||
def encode_image(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
return base64.b64encode(image_file.read()).decode("utf-8")
|
||||
|
||||
client = Memory()
|
||||
base64_image = encode_image("path/to/your/image.jpg")
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "What's in this image?"},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
client.add(messages, user_id="alice")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
import fs from "fs";
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
function encodeImage(imagePath: string) {
|
||||
const buffer = fs.readFileSync(imagePath);
|
||||
return buffer.toString("base64");
|
||||
}
|
||||
|
||||
const client = new Memory();
|
||||
const base64Image = encodeImage("path/to/your/image.jpg");
|
||||
|
||||
const messages = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What's in this image?" },
|
||||
{
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: `data:image/jpeg;base64,${base64Image}`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
await client.add(messages, { userId: "alice" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Tip>
|
||||
Smaller images upload and process faster. Compress or resize before encoding to base64, and check your vision provider's per-image size limit for large files.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## See it in action
|
||||
|
||||
### Restaurant menu memory
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
client = Memory()
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Help me remember which dishes I liked."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "https://example.com/restaurant-menu.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I’m allergic to peanuts and prefer vegetarian meals."
|
||||
}
|
||||
]
|
||||
|
||||
result = client.add(messages, user_id="user123")
|
||||
print(result)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
The response should capture both the allergy note and menu items extracted from the photo so future searches can combine them.
|
||||
</Info>
|
||||
|
||||
### Document capture
|
||||
|
||||
```python
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Store this receipt information for expenses."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "https://example.com/receipt.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
client.add(messages, user_id="user123")
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Combine the receipt upload with structured metadata (tags, categories) if you need to filter expenses later.
|
||||
</Tip>
|
||||
|
||||
### Error handling
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from mem0 import Memory
|
||||
|
||||
client = Memory()
|
||||
|
||||
try:
|
||||
messages = [{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "image_url",
|
||||
"image_url": {"url": "https://example.com/image.jpg"}
|
||||
}
|
||||
}]
|
||||
|
||||
client.add(messages, user_id="user123")
|
||||
print("Image processed successfully")
|
||||
|
||||
except ValueError as exc:
|
||||
# Raised when an image_url part is missing its url
|
||||
print(f"Invalid image message: {exc}")
|
||||
except Exception as exc:
|
||||
# Raised when the image can't be downloaded or the vision model call fails
|
||||
print(f"Could not process image: {exc}")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const client = new Memory();
|
||||
|
||||
try {
|
||||
const messages = [{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "image_url",
|
||||
image_url: { url: "https://example.com/image.jpg" }
|
||||
}
|
||||
}];
|
||||
|
||||
await client.add(messages, { userId: "user123" });
|
||||
console.log("Image processed successfully");
|
||||
} catch (error: any) {
|
||||
// A missing image_url, a failed download, or a vision model error surfaces here
|
||||
console.log(`Could not process image: ${error.message}`);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
Fail fast on invalid formats so you can prompt users to re-upload before losing their context.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- After calling `add`, inspect the returned memories and confirm they include image-derived text (menu items, receipt totals, etc.).
|
||||
- Run a follow-up `search` for a detail from the image; the memory should surface alongside related text.
|
||||
- Monitor image upload latency: large files should still complete under your acceptable response time.
|
||||
- Log file size and URL sources to troubleshoot repeated failures.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Ask for intent:** Prompt users to explain why they sent an image so the memory includes the right context.
|
||||
2. **Keep images readable:** Encourage clear photos without heavy filters or shadows for better extraction.
|
||||
3. **Split bulk uploads:** Send multiple images as separate `add` calls to isolate failures and improve reliability.
|
||||
4. **Watch privacy:** Avoid uploading sensitive documents unless your environment is secured for that data.
|
||||
5. **Validate file size early:** Check file size before encoding to save bandwidth and time.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| Upload rejected | Image exceeds your vision provider's size limit | Compress or resize before sending. |
|
||||
| Memory missing image data | Low-quality or blurry image | Retake the photo with better lighting. |
|
||||
| Invalid format error | Unsupported file type | Convert to JPEG or PNG first. |
|
||||
| Slow processing | High-resolution images | Downscale or compress to under 5 MB. |
|
||||
| Base64 errors | Incorrect prefix or encoding | Ensure `data:image/<type>;base64,` is present and the string is valid. |
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Connect Vision Models" icon="circle-dot" href="/components/llms/models/openai">
|
||||
Review supported vision-capable models and configuration details.
|
||||
</Card>
|
||||
<Card title="Build Multimodal Retrieval" icon="image" href="/cookbooks/frameworks/multimodal-retrieval">
|
||||
Follow an end-to-end workflow pairing text and image memories.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: OpenAI Compatibility
|
||||
description: Use Mem0 with the same chat-completions flow you already built for OpenAI.
|
||||
icon: "message-bot"
|
||||
---
|
||||
|
||||
Mem0 mirrors the OpenAI client interface so you can plug memories into existing chat-completion code with minimal changes. Point your OpenAI-compatible client at Mem0, keep the same request shape, and gain persistent memory between calls.
|
||||
|
||||
<Info>
|
||||
**You’ll use this when…**
|
||||
- Your app already relies on OpenAI chat completions and you want Mem0 to feel familiar.
|
||||
- You need to reuse existing middleware that expects OpenAI-compatible responses.
|
||||
- You plan to switch between Mem0 Platform and the self-hosted client without rewriting code.
|
||||
</Info>
|
||||
|
||||
## Feature
|
||||
|
||||
- **Drop-in client:** `client.chat.completions.create(...)` works the same as OpenAI’s method signatures.
|
||||
- **Shared parameters:** Mem0 accepts `messages`, `model`, and optional memory-scoping fields (`user_id`, `agent_id`, `run_id`).
|
||||
- **Memory-aware responses:** Each call saves relevant facts so future prompts automatically reflect past conversations.
|
||||
- **OSS parity:** Use the same API surface whether you call the hosted proxy or the OSS configuration.
|
||||
|
||||
<Info icon="check">
|
||||
Run one request with `user_id` set. If the next call references that ID and its reply uses the stored memory, compatibility is confirmed.
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
### Call the managed Mem0 proxy
|
||||
|
||||
```python
|
||||
from mem0.proxy.main import Mem0
|
||||
|
||||
client = Mem0(api_key="m0-xxx")
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "I love Indian food but I cannot eat pizza since I'm allergic to cheese."}
|
||||
]
|
||||
|
||||
chat_completion = client.chat.completions.create(
|
||||
messages=messages,
|
||||
model="gpt-5-mini",
|
||||
user_id="alice"
|
||||
)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Reuse the same identifiers your OpenAI client already sends so you can switch between providers without branching logic.
|
||||
</Tip>
|
||||
|
||||
### Use the OpenAI-compatible OSS client
|
||||
|
||||
```python
|
||||
from mem0.proxy.main import Mem0
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client = Mem0(config=config)
|
||||
|
||||
chat_completion = client.chat.completions.create(
|
||||
messages=[{"role": "user", "content": "What's the capital of France?"}],
|
||||
model="gpt-5-mini"
|
||||
)
|
||||
```
|
||||
|
||||
## See it in action
|
||||
|
||||
### Memory-aware restaurant recommendation
|
||||
|
||||
```python
|
||||
from mem0.proxy.main import Mem0
|
||||
|
||||
client = Mem0(api_key="m0-xxx")
|
||||
|
||||
# Store preferences
|
||||
client.chat.completions.create(
|
||||
messages=[{"role": "user", "content": "I love Indian food but I'm allergic to cheese."}],
|
||||
model="gpt-5-mini",
|
||||
user_id="alice"
|
||||
)
|
||||
|
||||
# Later conversation reuses the memory
|
||||
response = client.chat.completions.create(
|
||||
messages=[{"role": "user", "content": "Suggest dinner options in San Francisco."}],
|
||||
model="gpt-5-mini",
|
||||
user_id="alice"
|
||||
)
|
||||
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
The second response should call out Indian restaurants and avoid cheese, proving Mem0 recalled the stored preference.
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- Compare responses from Mem0 vs. OpenAI for identical prompts. Both should return the same structure (`choices`, `usage`, etc.).
|
||||
- Inspect stored memories after each request to confirm the fact extraction captured the right details.
|
||||
- Test switching between hosted (`Mem0(api_key=...)`) and OSS configurations to ensure both respect the same request body.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Scope context intentionally:** Pass identifiers only when you want conversations to persist; skip them for one-off calls.
|
||||
2. **Log memory usage:** Inspect `response.metadata.memories` (if enabled) to see which facts the model recalled.
|
||||
3. **Reuse middleware:** Point your existing OpenAI client wrappers to the Mem0 proxy URL to avoid code drift.
|
||||
4. **Handle fallbacks:** Keep a code path for plain OpenAI calls in case Mem0 is unavailable, then resync memory later.
|
||||
|
||||
---
|
||||
|
||||
## Parameter reference
|
||||
|
||||
| Parameter | Type | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `user_id` | `str` | Associates the conversation with a user so memories persist. |
|
||||
| `agent_id` | `str` | Optional agent or bot identifier for multi-agent scenarios. |
|
||||
| `run_id` | `str` | Optional session/run identifier for short-lived flows. |
|
||||
| `metadata` | `dict` | Store extra fields alongside each memory entry. |
|
||||
| `filters` | `dict` | Restrict retrieval to specific memories while responding. |
|
||||
| `top_k` | `int` | Cap how many memories Mem0 pulls into the context (default 10). |
|
||||
|
||||
Other request fields mirror OpenAI’s chat completion API.
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Connect Vision Models" icon="circle-dot" href="/components/llms/models/openai">
|
||||
Review LLM options that support OpenAI-compatible calls in Mem0.
|
||||
</Card>
|
||||
<Card title="Automate OpenAI Tool Calls" icon="plug" href="/cookbooks/integrations/openai-tool-calls">
|
||||
See a full workflow that layers Mem0 memories on top of tool-calling agents.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "Overview"
|
||||
description: "Self-hosting features that extend Mem0 beyond basic memory storage"
|
||||
icon: "list"
|
||||
---
|
||||
|
||||
# Self-Hosting Features Overview
|
||||
|
||||
Mem0 Open Source ships with capabilities that adapt memory behavior for production workloads: async operations, multimodal inputs, and fine-tuned retrieval. Configure these features with code or YAML to match your application's needs.
|
||||
|
||||
<Info>
|
||||
Start with the <Link href="/open-source/python-quickstart">Python quickstart</Link> to validate basic memory operations, then enable the features below when you need them.
|
||||
</Info>
|
||||
|
||||
## Choose your path
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Advanced Metadata Filtering" icon="filter" href="/open-source/features/metadata-filtering">
|
||||
Query with logical operators and nested conditions.
|
||||
</Card>
|
||||
<Card title="Search with Reranking" icon="ranking-star" href="/open-source/features/reranker-search">
|
||||
Boost search relevance with specialized models.
|
||||
</Card>
|
||||
<Card title="Async Memory Operations" icon="bolt" href="/open-source/features/async-memory">
|
||||
Non-blocking operations for high-throughput apps.
|
||||
</Card>
|
||||
<Card title="Multimodal Support" icon="image" href="/open-source/features/multimodal-support">
|
||||
Process images, audio, and video memories.
|
||||
</Card>
|
||||
<Card title="Custom Instructions" icon="wand-magic-sparkles" href="/open-source/features/custom-instructions">
|
||||
Tailor how facts are extracted from text.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="REST API" icon="code" href="/open-source/features/rest-api">
|
||||
HTTP endpoints for language-agnostic integrations.
|
||||
</Card>
|
||||
<Card title="OpenAI Compatibility" icon="message-bot" href="/open-source/features/openai_compatibility">
|
||||
Drop-in replacement for OpenAI chat endpoints.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Tip>
|
||||
Looking for managed features instead? Compare self-hosting vs managed in the <Link href="/platform/platform-vs-oss">Platform vs OSS guide</Link>.
|
||||
</Tip>
|
||||
|
||||
## Keep going
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Configure Components"
|
||||
description="Choose your LLM, embedder, vector store, and reranker with YAML or code."
|
||||
icon="sliders"
|
||||
href="/open-source/configuration"
|
||||
/>
|
||||
<Card
|
||||
title="Explore Cookbooks"
|
||||
description="Follow production-ready examples that combine multiple features."
|
||||
icon="book"
|
||||
href="/cookbooks/overview"
|
||||
/>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,554 @@
|
||||
---
|
||||
title: Reranker-Enhanced Search
|
||||
description: Boost relevance by reordering vector hits with reranking models.
|
||||
icon: "ranking-star"
|
||||
---
|
||||
|
||||
Reranker-enhanced search adds a second scoring pass after vector retrieval so Mem0 can return the most relevant memories first. Enable it when keyword similarity alone misses nuance or when you need the highest-confidence context for an agent decision.
|
||||
|
||||
<Info>
|
||||
**You’ll use this when…**
|
||||
- Queries are nuanced and require semantic understanding beyond vector distance.
|
||||
- Large memory collections produce too many near matches to review manually.
|
||||
- You want consistent scoring across providers by delegating ranking to a dedicated model.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
Reranking raises latency and, for hosted models, API spend. Benchmark with production traffic and define a fallback path for latency-sensitive requests.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The `Configure it` and `See it in action` snippets below use the Python SDK. The self-hosted **TypeScript SDK** supports the Cohere, Zero Entropy, Sentence Transformer, Hugging Face, and LLM rerankers; see [TypeScript SDK](#typescript-sdk).
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## TypeScript SDK
|
||||
|
||||
The self-hosted TypeScript SDK (`mem0ai/oss`) ships five rerankers: **Cohere**, **Zero Entropy**, **Sentence Transformer**, **Hugging Face**, and the **LLM reranker**. Configure one under `reranker`, then opt in per search with `rerank: true`. Keys are camelCase (`apiKey`, not `api_key`).
|
||||
|
||||
Provider SDKs are peer dependencies. Install the one your reranker needs:
|
||||
|
||||
```bash
|
||||
pnpm add cohere-ai # cohere
|
||||
pnpm add zeroentropy # zero_entropy
|
||||
pnpm add @huggingface/transformers # sentence_transformer, huggingface
|
||||
# llm_reranker defaults to openai (already a core dependency); install another
|
||||
# provider's SDK only if you nest a different one under config.llm
|
||||
```
|
||||
|
||||
### Hosted rerankers (Cohere, Zero Entropy)
|
||||
|
||||
Both call a hosted API and read their key from config or the provider's environment variable (`COHERE_API_KEY`, `ZERO_ENTROPY_API_KEY`).
|
||||
|
||||
```typescript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
// Cohere reranker (defaults to the rerank-v3.5 model)
|
||||
const memory = new Memory({
|
||||
reranker: {
|
||||
provider: "cohere",
|
||||
config: { apiKey: process.env.COHERE_API_KEY },
|
||||
},
|
||||
});
|
||||
|
||||
const results = await memory.search("What are my food preferences?", {
|
||||
filters: { userId: "alice" },
|
||||
rerank: true,
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Zero Entropy reranker (defaults to the zerank-1 model)
|
||||
const memory = new Memory({
|
||||
reranker: {
|
||||
provider: "zero_entropy",
|
||||
config: { apiKey: process.env.ZERO_ENTROPY_API_KEY },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Local cross-encoders (Sentence Transformer, Hugging Face)
|
||||
|
||||
Both run a cross-encoder locally with [Transformers.js](https://huggingface.co/docs/transformers.js): no API key, no network at inference time. Because Transformers.js runs ONNX weights, the default models are the ONNX mirrors of the Python SDK's defaults (`sentence_transformer` → `Xenova/ms-marco-MiniLM-L-6-v2`, `huggingface` → `Xenova/bge-reranker-base`). Point `model` at any ONNX-exported cross-encoder on the Hub to override.
|
||||
|
||||
```typescript
|
||||
const memory = new Memory({
|
||||
reranker: {
|
||||
provider: "sentence_transformer", // or "huggingface"
|
||||
config: {
|
||||
// model: "Xenova/bge-reranker-base", // override the default
|
||||
device: "cpu", // Transformers.js device: "cpu" | "wasm" | "webgpu"
|
||||
maxLength: 512, // max tokens per query-document pair
|
||||
normalize: true, // sigmoid-normalize logits to [0, 1] (default)
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const results = await memory.search("What movies do I like?", {
|
||||
filters: { userId: "alice" },
|
||||
rerank: true,
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
`batchSize` and `showProgressBar` are accepted for config parity with the Python SDK but are no-ops in this runtime, because a memory search reranks a small candidate set in a single in-process forward pass. The model is downloaded once and cached in-process on first use.
|
||||
</Note>
|
||||
|
||||
### LLM reranker
|
||||
|
||||
To score with an LLM instead of a dedicated reranker, use the `llm_reranker` provider. It builds its own LLM from the reranker's config (defaulting to `openai` / `gpt-4o-mini`) rather than reusing the Memory's main `llm`:
|
||||
|
||||
```typescript
|
||||
const memory = new Memory({
|
||||
reranker: {
|
||||
provider: "llm_reranker",
|
||||
config: { apiKey: process.env.OPENAI_API_KEY },
|
||||
},
|
||||
});
|
||||
|
||||
const results = await memory.search("What movies do I like?", {
|
||||
filters: { userId: "alice" },
|
||||
rerank: true,
|
||||
});
|
||||
```
|
||||
|
||||
Nest a different provider under `config.llm` to override the default:
|
||||
|
||||
```typescript
|
||||
const memory = new Memory({
|
||||
reranker: {
|
||||
provider: "llm_reranker",
|
||||
config: {
|
||||
llm: {
|
||||
provider: "anthropic",
|
||||
config: { apiKey: process.env.ANTHROPIC_API_KEY },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Config reference
|
||||
|
||||
| Provider | Default model | Key config fields |
|
||||
| --- | --- | --- |
|
||||
| `cohere` | `rerank-v3.5` | `apiKey`, `model`, `topK` |
|
||||
| `zero_entropy` | `zerank-1` | `apiKey`, `model`, `topK` |
|
||||
| `sentence_transformer` | `Xenova/ms-marco-MiniLM-L-6-v2` | `model`, `device`, `maxLength`, `normalize`, `topK` |
|
||||
| `huggingface` | `Xenova/bge-reranker-base` | `model`, `device`, `maxLength`, `normalize`, `topK` |
|
||||
| `llm_reranker` | `openai` / `gpt-4o-mini` | `provider`, `model`, `apiKey`, `llm` (nested override), `topK` |
|
||||
|
||||
<Note>
|
||||
`rerank` is opt-in per search and a no-op when no `reranker` is configured. If the reranker call fails, Mem0 logs a warning and returns the original vector-ranked results.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Feature anatomy
|
||||
|
||||
- **Initial vector search:** Retrieve candidate memories by similarity.
|
||||
- **Reranker pass:** A specialized model scores each candidate against the original query.
|
||||
- **Reordered results:** Mem0 sorts responses using the reranker’s scores before returning them.
|
||||
- **Optional fallbacks:** Toggle reranking per request or disable it entirely if performance or cost becomes a concern.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Supported providers">
|
||||
- **[Cohere](/components/rerankers/models/cohere)**: Multilingual hosted reranker with API-based scoring.
|
||||
- **[Sentence Transformer](/components/rerankers/models/sentence_transformer)**: Local Hugging Face cross-encoders for GPU or CPU.
|
||||
- **[Hugging Face](/components/rerankers/models/huggingface)**: Bring any hosted or on-prem reranker model ID.
|
||||
- **[LLM Reranker](/components/rerankers/models/llm_reranker)**: Use your preferred LLM (OpenAI, etc.) for prompt-driven scoring.
|
||||
- **[Zero Entropy](/components/rerankers/models/zero_entropy)**: High-quality neural reranking tuned for retrieval tasks.
|
||||
</Accordion>
|
||||
<Accordion title="Provider comparison">
|
||||
| Provider | Latency | Quality | Cost | Local deploy |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Cohere | Medium | High | API cost | ❌ |
|
||||
| Sentence Transformer | Low | Good | Free | ✅ |
|
||||
| Hugging Face | Low–Medium | Variable | Free | ✅ |
|
||||
| LLM Reranker | High | Very high | API cost | Depends |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
### Basic setup
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
"config": {
|
||||
"model": "rerank-v3.5",
|
||||
"api_key": "your-cohere-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Confirm `results["results"][0]["score"]` reflects the reranker output: if the field is missing, the reranker was not applied.
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
Set `top_k` to the smallest candidate pool that still captures relevant hits. Smaller pools keep reranking costs down.
|
||||
</Tip>
|
||||
|
||||
### Provider-specific options
|
||||
|
||||
```python
|
||||
# Cohere reranker
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
"config": {
|
||||
"model": "rerank-v3.5",
|
||||
"api_key": "your-cohere-api-key",
|
||||
"top_k": 10,
|
||||
"return_documents": True
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Sentence Transformer reranker
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "sentence_transformer",
|
||||
"config": {
|
||||
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2",
|
||||
"device": "cuda",
|
||||
"max_length": 512
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Hugging Face reranker
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "huggingface",
|
||||
"config": {
|
||||
"model": "BAAI/bge-reranker-base",
|
||||
"device": "cuda",
|
||||
"batch_size": 32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# LLM-based reranker
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-api-key",
|
||||
"top_k": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
Keep authentication keys in environment variables when you plug these configs into production projects.
|
||||
</Note>
|
||||
|
||||
### Full stack example
|
||||
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-api-key"
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small",
|
||||
"api_key": "your-openai-api-key"
|
||||
}
|
||||
},
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
"config": {
|
||||
"model": "rerank-v3.5",
|
||||
"api_key": "your-cohere-api-key",
|
||||
"top_k": 15,
|
||||
"return_documents": True
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
A quick search should now return results with both vector and reranker scores, letting you compare improvements immediately.
|
||||
</Info>
|
||||
|
||||
### Async support
|
||||
|
||||
```python
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
async_memory = AsyncMemory.from_config(config)
|
||||
|
||||
async def search_with_rerank():
|
||||
return await async_memory.search(
|
||||
"What are my preferences?",
|
||||
filters={"user_id": "alice"},
|
||||
rerank=True
|
||||
)
|
||||
|
||||
import asyncio
|
||||
results = asyncio.run(search_with_rerank())
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Inspect the async response to confirm reranking still applies; the scores should match the synchronous implementation.
|
||||
</Info>
|
||||
|
||||
### Tune performance and cost
|
||||
|
||||
```python
|
||||
# GPU-friendly local reranker configuration
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "sentence_transformer",
|
||||
"config": {
|
||||
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2",
|
||||
"device": "cuda",
|
||||
"batch_size": 32,
|
||||
"top_k": 10,
|
||||
"max_length": 256
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Smart toggle for hosted rerankers
|
||||
def smart_search(query, user_id, use_rerank=None):
|
||||
if use_rerank is None:
|
||||
use_rerank = len(query.split()) > 3
|
||||
return m.search(query, filters={"user_id": user_id}, rerank=use_rerank)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Use heuristics (query length, user tier) to decide when to rerank so high-signal queries benefit without taxing every request.
|
||||
</Tip>
|
||||
|
||||
### Handle failures gracefully
|
||||
|
||||
```python
|
||||
try:
|
||||
results = m.search("test query", filters={"user_id": "alice"}, rerank=True)
|
||||
except Exception as exc:
|
||||
print(f"Reranking failed: {exc}")
|
||||
results = m.search("test query", filters={"user_id": "alice"}, rerank=False)
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Always fall back to vector-only search: dropped queries introduce bigger accuracy issues than slightly less relevant ordering.
|
||||
</Warning>
|
||||
|
||||
### Migrate from v0.x
|
||||
|
||||
```python
|
||||
# Before: basic vector search
|
||||
results = m.search("query", filters={"user_id": "alice"})
|
||||
|
||||
# After: same API with reranking enabled via config
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "sentence_transformer",
|
||||
"config": {
|
||||
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
results = m.search("query", filters={"user_id": "alice"})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See it in action
|
||||
|
||||
### Basic reranked search
|
||||
|
||||
```python
|
||||
results = m.search(
|
||||
"What are my food preferences?",
|
||||
filters={"user_id": "alice"}
|
||||
)
|
||||
|
||||
for result in results["results"]:
|
||||
print(f"Memory: {result['memory']}")
|
||||
print(f"Score: {result['score']}")
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Expect each result to list the reranker-adjusted score so you can compare ordering against baseline vector results.
|
||||
</Info>
|
||||
|
||||
### Toggle reranking per request
|
||||
|
||||
```python
|
||||
results_with_rerank = m.search(
|
||||
"What movies do I like?",
|
||||
filters={"user_id": "alice"},
|
||||
rerank=True
|
||||
)
|
||||
|
||||
results_without_rerank = m.search(
|
||||
"What movies do I like?",
|
||||
filters={"user_id": "alice"},
|
||||
rerank=False
|
||||
)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Log the reranked vs. non-reranked lists during rollout so stakeholders can see the improvement before enforcing it everywhere.
|
||||
</Tip>
|
||||
|
||||
<Info icon="check">
|
||||
You should see the same memories in both lists, but the reranked response will reorder them based on semantic relevance.
|
||||
</Info>
|
||||
|
||||
### Combine with metadata filters
|
||||
|
||||
```python
|
||||
results = m.search(
|
||||
"important work tasks",
|
||||
filters={
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{"category": "work"},
|
||||
{"priority": {"gte": 7}}
|
||||
]
|
||||
},
|
||||
rerank=True,
|
||||
top_k=20
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Verify filtered reranked searches still respect every metadata clause: reranking only reorders candidates, it never bypasses filters.
|
||||
</Info>
|
||||
|
||||
### Real-world playbooks
|
||||
|
||||
#### Customer support
|
||||
|
||||
```python
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
"config": {
|
||||
"model": "rerank-v3.5",
|
||||
"api_key": "your-cohere-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
|
||||
results = m.search(
|
||||
"customer having login issues with mobile app",
|
||||
filters={"agent_id": "support_bot", "category": "technical_support"},
|
||||
rerank=True
|
||||
)
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Top results should highlight tickets matching the login issue context so agents can respond faster.
|
||||
</Info>
|
||||
|
||||
#### Content recommendation
|
||||
|
||||
```python
|
||||
results = m.search(
|
||||
"science fiction books with space exploration themes",
|
||||
filters={"user_id": "reader123", "content_type": "book_recommendation"},
|
||||
rerank=True,
|
||||
top_k=10
|
||||
)
|
||||
|
||||
for result in results["results"]:
|
||||
print(f"Recommendation: {result['memory']}")
|
||||
print(f"Relevance: {result['score']:.3f}")
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Expect high-scoring recommendations that match both the requested theme and any metadata limits you applied.
|
||||
</Info>
|
||||
|
||||
#### Personal assistant
|
||||
|
||||
```python
|
||||
results = m.search(
|
||||
"What restaurants did I enjoy last month that had good vegetarian options?",
|
||||
filters={
|
||||
"AND": [
|
||||
{"user_id": "foodie_user"},
|
||||
{"category": "dining"},
|
||||
{"rating": {"gte": 4}},
|
||||
{"date": {"gte": "2024-01-01"}}
|
||||
]
|
||||
},
|
||||
rerank=True
|
||||
)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Reuse this pattern for other lifestyle queries: swap the filters and prompt text without changing the rerank configuration.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
Each workflow keeps the same `m.search(...)` signature, so you can template these queries across agents with only the prompt and filters changing.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- Inspect result payloads for both `score` (vector) and reranker scores; mismatched fields indicate the reranker didn’t execute.
|
||||
- Track latency before and after enabling reranking to ensure SLAs hold.
|
||||
- Review provider logs or dashboards for throttling or quota warnings.
|
||||
- Run A/B comparisons (rerank on/off) to validate improved relevance before defaulting to reranked responses.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Start local:** Try Sentence Transformer models to prove value before paying for hosted APIs.
|
||||
2. **Monitor latency:** Add metrics around reranker duration so you notice regressions quickly.
|
||||
3. **Control spend:** Use `top_k` and selective toggles to cap hosted reranker costs.
|
||||
4. **Keep a fallback:** Always catch reranker failures and continue with vector-only ordering.
|
||||
5. **Experiment often:** Swap providers or models to find the best fit for your domain and language mix.
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Configure Rerankers" icon="sliders" href="/components/rerankers/config">
|
||||
Review provider fields, defaults, and environment variables before going live.
|
||||
</Card>
|
||||
<Card title="Build a Custom LLM Reranker" icon="sparkles" href="/components/rerankers/models/llm_reranker">
|
||||
Extend scoring with prompt-tuned LLM rerankers for niche workflows.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Reranking
|
||||
description: 'Redirect to the canonical reranker-enhanced search guide.'
|
||||
---
|
||||
|
||||
<Redirect href="/open-source/features/reranker-search" />
|
||||
@@ -0,0 +1,341 @@
|
||||
---
|
||||
title: REST API Server
|
||||
description: Reach every Mem0 OSS capability through a FastAPI-powered REST layer.
|
||||
icon: "code"
|
||||
---
|
||||
|
||||
The Mem0 REST API server exposes every OSS memory operation over HTTP. Run it alongside your stack to add, search, update, and delete memories from any language that speaks REST.
|
||||
|
||||
<Info>
|
||||
**You’ll use this when…**
|
||||
- Your services already talk to REST APIs and you want Mem0 to match that style.
|
||||
- Teams on languages without the Mem0 SDK still need access to memories.
|
||||
- You plan to explore or debug endpoints through the built-in OpenAPI page at `/docs`.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
**First time self-hosting, or upgrading from a pre-1.x build?** Start at [Self-Hosted Setup](/open-source/setup). It walks through the stack, the setup wizard, and the upgrade path for deployments that relied on open endpoints or `ADMIN_API_KEY`. This page covers the API surface and auth modes only.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
**OSS vs Platform API paths:** The self-hosted OSS server does **not** use the `/v1/` prefix. For example, the endpoint is `POST /memories`, not `POST /v1/memories/`. The [API Reference](/api-reference) documents the hosted platform at `api.mem0.ai` which uses `/v1/` paths: those do not apply to the OSS server.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
Enable API key authentication (see below) and HTTPS before exposing the server to anything beyond your internal network.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Feature
|
||||
|
||||
- **CRUD endpoints:** Create, retrieve, search, update, delete, and reset memories by `user_id`, `agent_id`, or `run_id`.
|
||||
- **Authentication:** On by default. Dashboard sessions use JWTs; programmatic clients use per-user `X-API-Key` headers. Legacy `ADMIN_API_KEY` is still supported.
|
||||
- **Status health check:** Access base routes to confirm the server is online.
|
||||
- **OpenAPI explorer:** Visit `/docs` for interactive testing and schema reference.
|
||||
|
||||
---
|
||||
|
||||
## Configure it
|
||||
|
||||
### Run with Docker Compose (development)
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Steps">
|
||||
1. Create `server/.env` with your keys:
|
||||
|
||||
```bash
|
||||
OPENAI_API_KEY=your-openai-api-key
|
||||
JWT_SECRET=$(openssl rand -base64 48)
|
||||
```
|
||||
|
||||
2. Bootstrap the stack in one command:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
make bootstrap # starts Compose, creates an admin, issues the first API key
|
||||
```
|
||||
|
||||
Or to start the stack only and finish setup via the browser wizard at http://localhost:3000:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
3. API is at `http://localhost:8888`. Code edits auto-reload.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Other install paths">
|
||||
**Run with Docker**
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Pull image">
|
||||
```bash
|
||||
docker pull mem0/mem0-api-server
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Build locally">
|
||||
```bash
|
||||
docker build -t mem0-api-server .
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
1. Create a `.env` file with `OPENAI_API_KEY` and `JWT_SECRET`.
|
||||
2. Run the container:
|
||||
|
||||
```bash
|
||||
docker run -p 8000:8000 --env-file .env mem0-api-server
|
||||
```
|
||||
|
||||
3. Visit `http://localhost:8000`.
|
||||
|
||||
**Run directly (no Docker)**
|
||||
|
||||
<Warning>
|
||||
This path skips Docker and assumes Postgres is already running and reachable at `POSTGRES_HOST:POSTGRES_PORT`. For a single-command local setup with Postgres included, use Docker Compose above.
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
Compose publishes internal port 8000 as 8888 on the host. Raw Docker and raw uvicorn listen on 8000 unless remapped.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
`JWT_SECRET` is required once auth is enabled: the server returns `500` on auth endpoints if it's unset. Generate one with `openssl rand -base64 48`. See [Self-Hosted Setup](/open-source/setup#configure-the-environment) for the full env var table.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
Use a process manager such as `systemd`, Supervisor, or PM2 when deploying the FastAPI server for production resilience.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
The REST server reads the same configuration you use locally, so you can point it at your preferred LLM, vector store, and reranker without changing code.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Auth is on by default. Protected endpoints require either a JWT (from the dashboard login flow) or an `X-API-Key` header. The `/` redirect, `/docs`, and `/openapi.json` routes stay open so you can reach the OpenAPI explorer.
|
||||
|
||||
| Mode | How to send it | When to use it |
|
||||
|---|---|---|
|
||||
| Bearer JWT | `Authorization: Bearer <access_token>` | Dashboard sessions; tokens come from `POST /auth/login` and refresh via `POST /auth/refresh` |
|
||||
| Per-user API key | `X-API-Key: m0sk_...` | Programmatic access scoped to a single dashboard user |
|
||||
| Legacy `ADMIN_API_KEY` | `X-API-Key: <env value>` | Back-compat for deployments that set the `ADMIN_API_KEY` env var |
|
||||
| `AUTH_DISABLED=true` | N/A | Local development only; bypasses auth entirely |
|
||||
|
||||
The `/docs` OpenAPI explorer supports both auth modes. Click **Authorize** at the top of the page and paste either `Bearer <access_token>` (JWT) or your `X-API-Key` value. Protected endpoints return `401` until you authorize.
|
||||
|
||||
### Log in and use a JWT
|
||||
|
||||
Register the first admin (only works when no user exists yet), then log in:
|
||||
|
||||
```bash
|
||||
# First admin only: returns 403 after the first admin is registered
|
||||
curl -X POST http://localhost:8888/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "Admin", "email": "admin@example.com", "password": "strong-password"}'
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "admin@example.com", "password": "your-password"}'
|
||||
```
|
||||
|
||||
Use the returned `access_token` as a bearer token:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <access_token>" \
|
||||
-d '{
|
||||
"messages": [{"role": "user", "content": "I love pizza."}],
|
||||
"user_id": "alice"
|
||||
}'
|
||||
```
|
||||
|
||||
When the access token expires, exchange the refresh token at `POST /auth/refresh`.
|
||||
|
||||
### Create and use a per-user API key
|
||||
|
||||
Create a key from the dashboard **API Keys** page, or call `POST /api-keys` with a JWT. The full `m0sk_...` value is returned **once** at creation time: store it securely.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: m0sk_your_key_here" \
|
||||
-d '{
|
||||
"messages": [{"role": "user", "content": "I love pizza."}],
|
||||
"user_id": "alice"
|
||||
}'
|
||||
```
|
||||
|
||||
Per-user keys inherit the creating user's scope. List or revoke them via `GET /api-keys` and `DELETE /api-keys/{id}`.
|
||||
|
||||
### Legacy `ADMIN_API_KEY`
|
||||
|
||||
Set the `ADMIN_API_KEY` environment variable and send it as `X-API-Key`. The request is treated as admin-level and is not tied to a dashboard user. This mode is kept for back-compat with older self-hosted deployments: prefer JWT or per-user keys for new setups.
|
||||
|
||||
```bash
|
||||
ADMIN_API_KEY=your-long-admin-key
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Setting `AUTH_DISABLED=true` makes every protected endpoint open: the server logs a warning at startup when it's enabled. The server also warns when `ADMIN_API_KEY` is shorter than 16 characters. Never enable `AUTH_DISABLED` in production, and always use a long `ADMIN_API_KEY` if you rely on the legacy fallback.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## See it in action
|
||||
|
||||
### Create and search memories via HTTP
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"messages": [
|
||||
{"role": "user", "content": "I love fresh vegetable pizza."}
|
||||
],
|
||||
"user_id": "alice"
|
||||
}'
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Expect a JSON response containing the new memory IDs and events (`ADD`, etc.).
|
||||
</Info>
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/search \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "vegetable",
|
||||
"user_id": "alice"
|
||||
}'
|
||||
```
|
||||
|
||||
Set `explain` to inspect the scoring signals used by OSS hybrid search:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/search \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "vegetable pizza",
|
||||
"user_id": "alice",
|
||||
"explain": true
|
||||
}'
|
||||
```
|
||||
|
||||
Each returned memory includes `score_details` only when explanation mode is enabled.
|
||||
|
||||
### Explore with OpenAPI docs
|
||||
|
||||
1. Navigate to `http://localhost:8888/docs` (Compose) or `http://localhost:8000/docs` (raw Docker / uvicorn).
|
||||
2. Pick an endpoint (e.g., `POST /search`).
|
||||
3. Fill in parameters and click **Execute** to try requests in-browser.
|
||||
|
||||
<Tip>
|
||||
Export the generated `curl` snippets from the OpenAPI UI to bootstrap integration tests.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Endpoint reference
|
||||
|
||||
The OSS REST server exposes the following endpoints. None use the `/v1/` prefix.
|
||||
|
||||
### Memory operations
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `POST` | `/configure` | Set memory configuration. Rejects unbundled providers with a 400 |
|
||||
| `GET` | `/configure` | Get the current memory configuration |
|
||||
| `GET` | `/configure/providers` | List the LLM and embedder providers bundled in the container |
|
||||
| `POST` | `/memories` | Create memories |
|
||||
| `GET` | `/memories` | Get all memories (filter by `user_id`, `agent_id`, or `run_id`) |
|
||||
| `GET` | `/memories/{memory_id}` | Get a specific memory |
|
||||
| `PUT` | `/memories/{memory_id}` | Update a memory |
|
||||
| `DELETE` | `/memories/{memory_id}` | Delete a specific memory |
|
||||
| `DELETE` | `/memories` | Delete all memories for an identifier |
|
||||
| `GET` | `/memories/{memory_id}/history` | Get memory history |
|
||||
| `POST` | `/search` | Search memories |
|
||||
| `POST` | `/reset` | Reset all memories |
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/auth/setup-status` | Returns `{needsSetup: bool}`. Open, no auth required |
|
||||
| `POST` | `/auth/register` | Register the first admin. Registration closes after the first admin is created; additional accounts are provisioned by the existing admin. |
|
||||
| `POST` | `/auth/login` | Exchange email and password for access and refresh JWTs |
|
||||
| `POST` | `/auth/refresh` | Exchange a refresh token for a new access token |
|
||||
| `GET` | `/auth/me` | Get the current authenticated user (JWT required) |
|
||||
| `PATCH` | `/auth/me` | Update the caller's name or email. 409 if the new email is already in use |
|
||||
| `POST` | `/auth/change-password` | Change the caller's password. 401 if the current password is wrong; new password must be at least 8 characters |
|
||||
|
||||
### API keys
|
||||
|
||||
All `/api-keys` endpoints require a JWT.
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/api-keys` | List the caller's API keys |
|
||||
| `POST` | `/api-keys` | Create a new key; the full `m0sk_...` value is returned once |
|
||||
| `DELETE` | `/api-keys/{id}` | Revoke an API key |
|
||||
|
||||
### Request logs
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/requests?limit=N` | Recent API call log (JWT or admin key) |
|
||||
|
||||
### Entities
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/entities` | Distinct `user_id` / `agent_id` / `run_id` values with memory counts |
|
||||
| `DELETE` | `/entities/{entity_type}/{entity_id}` | Cascade-delete all memories for an entity; `entity_type` is `user`, `agent`, or `run` |
|
||||
|
||||
The `/auth/*`, `/api-keys`, `/requests`, and `/entities` routes are new to the self-hosted server and primarily back the dashboard, but you can call them directly from your own tooling.
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
|
||||
- Hit the root route and `/docs` to confirm the server is reachable.
|
||||
- Run a full cycle: `POST /memories` → `GET /memories/{id}` → `DELETE /memories/{id}`.
|
||||
- Watch server logs for import errors or provider misconfigurations during startup.
|
||||
- Confirm environment variables (API keys, vector store credentials) load correctly when containers restart.
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Keep auth on:** Auth is enabled by default. Never set `AUTH_DISABLED=true` in production. If you rely on `ADMIN_API_KEY`, use a long value (16+ chars) or prefer per-user API keys.
|
||||
2. **Use HTTPS:** Terminate TLS at your load balancer or reverse proxy.
|
||||
3. **Monitor uptime:** Track request rates, latency, and error codes per endpoint.
|
||||
4. **Version configs:** Keep environment files and Docker Compose definitions in source control.
|
||||
5. **Limit exposure:** Bind to private networks unless you explicitly need public access.
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Configure OSS Components" icon="sliders" href="/open-source/configuration">
|
||||
Fine-tune LLMs, vector stores, and rerankers that power the REST server.
|
||||
</Card>
|
||||
<Card title="Automate Agent Integrations" icon="plug" href="/cookbooks/integrations/agents-sdk-tool">
|
||||
See how services call the REST endpoints as part of an automation pipeline.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Node SDK Quickstart
|
||||
description: "Store and search Mem0 memories from a TypeScript or JavaScript app in minutes."
|
||||
icon: "js"
|
||||
---
|
||||
|
||||
Spin up Mem0 with the Node SDK in just a few steps. You'll install the package, initialize the client, add a memory, and confirm retrieval with a single search.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18 or higher
|
||||
- (Optional) OpenAI API key stored in your environment when you want to customize providers
|
||||
|
||||
## Install and run your first memory
|
||||
|
||||
<Steps>
|
||||
<Step title="Install the SDK">
|
||||
```bash
|
||||
npm install mem0ai
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initialize the client">
|
||||
```ts
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const memory = new Memory();
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add a memory">
|
||||
```ts
|
||||
const messages = [
|
||||
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
|
||||
{ role: "assistant", content: "How about thriller movies? They can be quite engaging." },
|
||||
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
|
||||
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." }
|
||||
];
|
||||
|
||||
await memory.add(messages, { userId: "alice", metadata: { category: "movie_recommendations" } });
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Search memories">
|
||||
```ts
|
||||
const results = await memory.search("What do you know about me?", { filters: { userId: "alice" } });
|
||||
console.log(results);
|
||||
```
|
||||
|
||||
**Output**
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "892db2ae-06d9-49e5-8b3e-585ef9b85b8e",
|
||||
"memory": "User is planning to watch a movie tonight.",
|
||||
"score": 0.38920719231944799,
|
||||
"metadata": {
|
||||
"category": "movie_recommendations"
|
||||
},
|
||||
"userId": "alice"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Snippet file="star-on-github.mdx" />
|
||||
|
||||
<Note>
|
||||
By default the Node SDK uses local-friendly settings (OpenAI `gpt-5-mini`, `text-embedding-3-small`, in-memory vector store, and SQLite history). Pass a config to swap any of them.
|
||||
</Note>
|
||||
|
||||
## Configure providers
|
||||
|
||||
Pass a config object to `new Memory()` to use your own LLM, embedder, and vector store:
|
||||
|
||||
```ts
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const memory = new Memory({
|
||||
llm: {
|
||||
provider: "openai",
|
||||
config: { apiKey: process.env.OPENAI_API_KEY || "", model: "gpt-4-turbo-preview" }
|
||||
},
|
||||
embedder: {
|
||||
provider: "openai",
|
||||
config: { apiKey: process.env.OPENAI_API_KEY || "", model: "text-embedding-3-small" }
|
||||
},
|
||||
vectorStore: {
|
||||
provider: "memory",
|
||||
config: { collectionName: "memories", dimension: 1536 }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
For the full provider catalog, history stores, and every config option, see [Configuration](/open-source/configuration).
|
||||
|
||||
## What's next?
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Memory operations" icon="database" href="/core-concepts/memory-operations/add">
|
||||
Search, update, and manage memories with the full CRUD API.
|
||||
</Card>
|
||||
<Card title="Configure for production" icon="sliders" href="/open-source/configuration">
|
||||
Swap in your own LLM, embedder, and vector store.
|
||||
</Card>
|
||||
<Card title="Add to your framework" icon="plug" href="/integrations">
|
||||
Wire Mem0 into LangChain, CrewAI, LangGraph, and 20+ more.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
If you have any questions, please feel free to reach out:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "Overview"
|
||||
description: "Self-host Mem0 with full control over your infrastructure and data"
|
||||
icon: "house"
|
||||
---
|
||||
|
||||
Mem0 Open Source is the same memory engine as the Platform, running on your own infrastructure. You own the stack, the data, and every component, and you choose where each one runs.
|
||||
|
||||
Run it two ways:
|
||||
|
||||
- **As a library** in your app. `pip install mem0ai` (or `npm install mem0ai`), call `Memory()`, and you have memory in a few lines.
|
||||
- **As a self-hosted server.** A Docker stack with a dashboard, per-user API keys, and a request audit log.
|
||||
|
||||
## Get started
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Python Quickstart" icon="python" href="/open-source/python-quickstart">
|
||||
Install the SDK and verify the add/search loop in a few lines.
|
||||
</Card>
|
||||
<Card title="Node.js Quickstart" icon="node" href="/open-source/node-quickstart">
|
||||
Install the TypeScript SDK and run the starter script.
|
||||
</Card>
|
||||
<Card title="Self-hosted server" icon="rocket-launch" href="/open-source/setup">
|
||||
Run `make bootstrap` to launch the server, dashboard, and your first API key.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Go further
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Configure components" icon="sliders" href="/open-source/configuration">
|
||||
Set your LLM, embedder, vector store, and reranker.
|
||||
</Card>
|
||||
<Card title="Self-hosting features" icon="server" href="/open-source/features/overview">
|
||||
Async memory, metadata filters, reranker search, multimodal, and more.
|
||||
</Card>
|
||||
<Card title="Build with cookbooks" icon="book-open" href="/cookbooks/overview">
|
||||
End-to-end examples: companions, agents, and integrations.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Tip>
|
||||
Need a managed alternative? Compare hosting models in the <Link href="/platform/platform-vs-oss">Platform vs OSS guide</Link> or switch tabs to the Platform documentation.
|
||||
</Tip>
|
||||
|
||||
## Default components
|
||||
|
||||
Mem0 runs out of the box with the defaults below. Override any of them through [configuration](/open-source/configuration).
|
||||
|
||||
**As a library** (you `import` Mem0 and call `Memory()`):
|
||||
|
||||
| Component | Default |
|
||||
|---|---|
|
||||
| LLM | OpenAI `gpt-5-mini` (set `OPENAI_API_KEY`) |
|
||||
| Embeddings | OpenAI `text-embedding-3-small` |
|
||||
| Vector store | Local Qdrant at `/tmp/qdrant` |
|
||||
| History store | SQLite at `~/.mem0/history.db` |
|
||||
| Reranker | Disabled until configured |
|
||||
|
||||
Override any component with [`Memory.from_config`](/open-source/configuration).
|
||||
|
||||
**As a self-hosted server** (the `server/` Docker Compose stack):
|
||||
|
||||
| Component | Default |
|
||||
|---|---|
|
||||
| LLM | OpenAI `gpt-5-mini` (override with `MEM0_DEFAULT_LLM_MODEL`) |
|
||||
| Embeddings | OpenAI `text-embedding-3-small` (override with `MEM0_DEFAULT_EMBEDDER_MODEL`) |
|
||||
| Vector store | Postgres + pgvector |
|
||||
| Bundled providers | `openai`, `anthropic`, `gemini` (switch on the Configuration page) |
|
||||
|
||||
See [Self-Hosted Setup](/open-source/setup#supported-providers) for the full provider list and how to extend it.
|
||||
|
||||
<Snippet file="star-on-github.mdx" />
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Python SDK Quickstart
|
||||
description: "Install the Mem0 Python SDK, configure your environment, and store your first memory in under five minutes."
|
||||
icon: "snake"
|
||||
---
|
||||
|
||||
Get started with Mem0's Python SDK in under 5 minutes. This guide shows you how to install Mem0 and store your first memory.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10 or higher
|
||||
- OpenAI API key ([Get one here](https://platform.openai.com/api-keys))
|
||||
|
||||
Set your OpenAI API key:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your-openai-api-key"
|
||||
```
|
||||
|
||||
<Note>
|
||||
Uses OpenAI by default. Want to use Ollama, Anthropic, or local models? See [Configuration](/open-source/configuration).
|
||||
</Note>
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
<Step title="Install via pip">
|
||||
```bash
|
||||
pip install mem0ai
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initialize Memory">
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
m = Memory()
|
||||
|
||||
````
|
||||
</Step>
|
||||
|
||||
<Step title="Add a memory">
|
||||
```python
|
||||
messages = [
|
||||
{"role": "user", "content": "Hi, I'm Alex. I love basketball and gaming."},
|
||||
{"role": "assistant", "content": "Hey Alex! I'll remember your interests."}
|
||||
]
|
||||
m.add(messages, user_id="alex")
|
||||
````
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Search memories">
|
||||
```python
|
||||
results = m.search("What do you know about me?", filters={"user_id": "alex"})
|
||||
print(results)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "mem_123abc",
|
||||
"memory": "Name is Alex. Enjoys basketball and gaming.",
|
||||
"user_id": "alex",
|
||||
"categories": ["personal_info"],
|
||||
"created_at": "2025-10-22T04:40:22.864647-07:00",
|
||||
"score": 0.89
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Snippet file="star-on-github.mdx" />
|
||||
|
||||
<Note>
|
||||
By default `Memory()` wires up:
|
||||
- OpenAI `gpt-5-mini` for fact extraction and updates
|
||||
- OpenAI `text-embedding-3-small` embeddings (1536 dimensions)
|
||||
- Qdrant vector store with on-disk data at `/tmp/qdrant`
|
||||
- SQLite history at `~/.mem0/history.db`
|
||||
- No reranker (add one in the config when you need it)
|
||||
</Note>
|
||||
|
||||
## What's next?
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Memory operations" icon="database" href="/core-concepts/memory-operations/add">
|
||||
Search, update, and manage memories with the full CRUD API.
|
||||
</Card>
|
||||
|
||||
<Card title="Configure for production" icon="sliders" href="/open-source/configuration">
|
||||
Swap in your own LLM, embedder, and vector store.
|
||||
</Card>
|
||||
|
||||
<Card title="Add to your framework" icon="plug" href="/integrations">
|
||||
Wire Mem0 into LangChain, CrewAI, LangGraph, and 20+ more.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
If you have any questions, please feel free to reach out:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
@@ -0,0 +1,221 @@
|
||||
---
|
||||
title: "Self-Hosted Setup"
|
||||
sidebarTitle: "Self-Hosted Server"
|
||||
description: "Stand up the Mem0 REST server and dashboard in a few minutes, with an admin account, API keys, and a live audit log included."
|
||||
icon: "rocket-launch"
|
||||
---
|
||||
|
||||
The self-hosted bundle ships the REST API and a web dashboard together. Configure your LLM provider and secrets in a `.env` file, start the containers, then choose how to create your admin account: through the browser-based setup wizard, or from the command line.
|
||||
|
||||
<Info>
|
||||
**Use this page when…**
|
||||
- You want a self-hosted Mem0 with a dashboard, not just the Python or Node library.
|
||||
- You need per-user API keys and a request audit log for your team.
|
||||
- You're upgrading from a pre-1.x server that relied on `ADMIN_API_KEY` or open endpoints.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
**Upgrading from 1.x?** Auth is now on by default. Deployments that ran with an empty `ADMIN_API_KEY` will return `401` on every protected endpoint until you either set `ADMIN_API_KEY`, register an admin through the wizard, or set `AUTH_DISABLED=true` for local development. See [Upgrade notes](#upgrade-notes) below.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose (the reference path).
|
||||
- An `OPENAI_API_KEY` or another provider key; the server reads the same component config as the library.
|
||||
- A free port `8888` for the API and `3000` for the dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Configure the environment
|
||||
|
||||
Copy `server/.env.example` to `server/.env` and fill in the required values. The server refuses to start if `JWT_SECRET` is unset once auth is enabled.
|
||||
|
||||
| Variable | Required | Purpose |
|
||||
|---|---|---|
|
||||
| `OPENAI_API_KEY` | Yes | Default LLM and embedder provider. |
|
||||
| `JWT_SECRET` | Yes | Signs access and refresh tokens. Use a long random value. A missing secret causes auth endpoints to return `500`. |
|
||||
| `ADMIN_API_KEY` | Optional | Legacy shared admin key. Kept for back-compat; prefer per-user keys for new setups. |
|
||||
| `AUTH_DISABLED` | Optional | `true` turns off auth for local development only. Never enable in production. |
|
||||
| `DASHBOARD_URL` | Optional | Origin the API accepts for CORS. Defaults to `http://localhost:3000`. Set this when you front the dashboard on a custom domain. |
|
||||
| `POSTGRES_*` | Optional | Override the bundled Postgres / pgvector connection. |
|
||||
|
||||
<Tip>
|
||||
Generate a `JWT_SECRET` with `openssl rand -base64 48` or `python -c "import secrets; print(secrets.token_urlsafe(48))"`.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Start the stack
|
||||
|
||||
Pick the path that fits your workflow.
|
||||
|
||||
### Browser-first (setup wizard)
|
||||
|
||||
```bash
|
||||
cd server
|
||||
make up
|
||||
```
|
||||
|
||||
This starts the containers and runs database migrations. The REST API listens on `http://localhost:8888` and the dashboard on `http://localhost:3000`.
|
||||
|
||||
Open `http://localhost:3000`. Since no admin account exists yet, the dashboard redirects to the one-time setup wizard at `/setup`. See [Run the setup wizard](#run-the-setup-wizard) below.
|
||||
|
||||
### Agent-first (command line)
|
||||
|
||||
First, set `OPENAI_API_KEY` (or `ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`) in `server/.env`. `make bootstrap` does not prompt for it, and the runtime test will fail without a valid provider key.
|
||||
|
||||
```bash
|
||||
cd server
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
`make bootstrap` starts the same containers, then automatically creates the admin account and generates the first API key via the CLI. The CLI prints the admin credentials and API key in your terminal. No browser required.
|
||||
|
||||
You can override the generated credentials:
|
||||
|
||||
```bash
|
||||
make bootstrap EMAIL=admin@company.com PASSWORD='strong-password' NAME='Admin'
|
||||
```
|
||||
|
||||
Because `make bootstrap` already creates the admin, the setup wizard is skipped. Opening `http://localhost:3000` takes you straight to the login page.
|
||||
|
||||
<Tip>
|
||||
For machine-readable output (useful in CI), run `OUTPUT=json make seed` after `make up`.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Run the setup wizard
|
||||
|
||||
<Info>
|
||||
This section applies to the **browser-first** path (`make up`). If you used `make bootstrap`, the admin and API key were already created, so skip ahead to [What the dashboard gives you](#what-the-dashboard-gives-you).
|
||||
</Info>
|
||||
|
||||
On a fresh install the dashboard redirects to `/setup`. Each step submits on Enter.
|
||||
|
||||
**1. Create the admin account.** Name, email, password. This account becomes the first admin. Registration closes after the first admin is created; additional accounts are provisioned by the existing admin.
|
||||
|
||||
**2. Review the effective config.** Read-only display of the LLM and embedder the server is running with, sourced from your environment. If anything is wrong here, stop the stack, fix the `.env`, and restart. The dashboard intentionally does not let you change provider secrets at runtime.
|
||||
|
||||
**3. Generate your first API key.** The full `m0sk_...` value is shown **once**. Copy it immediately. The server stores only the prefix and a bcrypt hash.
|
||||
|
||||
**4. Tell us your use case.** Pick a preset or describe your use case in a few words. Mem0 generates custom instructions that tell the memory system what to prioritize. You can edit the instructions before saving, or skip this step entirely.
|
||||
|
||||
**5. Test the key.** A ready-to-paste `curl` exercises `POST /memories` against your new key. Click "Run Test" to fire it from the browser. Success lands you in the dashboard at `/dashboard/requests`, where you'll see the test call in the live audit log.
|
||||
|
||||
---
|
||||
|
||||
## What the dashboard gives you
|
||||
|
||||
| Page | What it does |
|
||||
|---|---|
|
||||
| **Requests** | Default landing page. Live audit log of every API call, with status, latency, and auth mode. |
|
||||
| **Memories** | Browse and search the memories your server has stored. |
|
||||
| **Entities** | Distinct `user_id` / `agent_id` / `run_id` values with memory counts and cascade-delete. |
|
||||
| **API Keys** | Issue per-user keys, label them, and revoke. |
|
||||
| **Configuration** | Runtime override for LLM and embedder. Changes persist to the app database and reapply on restart, layered over the values from your `.env`. |
|
||||
| **Settings** | Account and session controls. |
|
||||
|
||||
For the underlying endpoints (including `/auth/*`, `/api-keys`, `/requests`, `/entities`), see the [REST API reference](/open-source/features/rest-api).
|
||||
|
||||
<Snippet file="star-on-github.mdx" />
|
||||
|
||||
---
|
||||
|
||||
## Supported providers
|
||||
|
||||
The shipped container bundles the Python packages for:
|
||||
|
||||
- **LLMs:** `openai`, `anthropic`, `gemini`
|
||||
- **Embedders:** `openai`, `gemini`
|
||||
|
||||
The Configuration page and `POST /configure` only accept providers from these lists. Anything else returns a 400 up front instead of failing at the first memory write.
|
||||
|
||||
**To add another provider**, for example to run embeddings locally with `sentence-transformers`:
|
||||
|
||||
1. Add the package to `server/requirements.txt` (e.g. `sentence-transformers>=2.0`).
|
||||
2. Extend `BUNDLED_LLM_PROVIDERS` or `BUNDLED_EMBEDDER_PROVIDERS` in `server/main.py`.
|
||||
3. Rebuild the image (`make up` or `docker compose build`).
|
||||
|
||||
Heavy providers (`sentence-transformers` pulls in PyTorch, ~2 GB) are intentionally kept out of the default image.
|
||||
|
||||
---
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
### Upgrading from a pre-auth build
|
||||
|
||||
Previous self-hosted builds allowed open access when `ADMIN_API_KEY` was unset. This build enables auth by default. After pulling the new image, pick **one**:
|
||||
|
||||
1. **Fastest, zero client changes:** set `ADMIN_API_KEY` to a long random value (16+ characters). Existing clients that send `X-API-Key: <your-key>` keep working unchanged.
|
||||
2. **Recommended for teams:** visit `http://<host>:3000`, run the setup wizard, and switch clients to per-user API keys. You get the audit log and revocation for free.
|
||||
3. **Local development only:** set `AUTH_DISABLED=true`. The server logs a warning on every boot. Never use this in production.
|
||||
|
||||
The server prints an unmissable startup banner when it detects the "upgraded but not configured" state so you know exactly which option to pick.
|
||||
|
||||
### Other changes in this release
|
||||
|
||||
- Dashboard ships as a second container in the reference Compose stack, wired to the API over the internal Docker network.
|
||||
- New tables: `users`, `api_keys`, `request_logs`. Alembic handles the migration automatically on first boot.
|
||||
|
||||
If `alembic upgrade head` fails on first boot, see the [Troubleshooting](#troubleshooting) section below.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Port 3000 or 8888 is already in use">
|
||||
Find the owning process on either port:
|
||||
```bash
|
||||
lsof -iTCP:3000 -sTCP:LISTEN
|
||||
lsof -iTCP:8888 -sTCP:LISTEN
|
||||
```
|
||||
Kill it (`kill <PID>`) or change the host port in `server/docker-compose.yaml`.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="JWT_SECRET is required">
|
||||
The server refuses to start without one. Generate a secret and add it to `server/.env`:
|
||||
```bash
|
||||
echo "JWT_SECRET=$(openssl rand -base64 48)" >> server/.env
|
||||
```
|
||||
`AUTH_DISABLED=true` is valid for local dev only, never production.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title=".env changes aren't applied after editing">
|
||||
`docker compose restart` does not re-read `env_file`. To pick up changes:
|
||||
```bash
|
||||
cd server && docker compose up -d --force-recreate mem0
|
||||
# or
|
||||
cd server && make up
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Provider returns 401 (bad API key)">
|
||||
Provider credential errors surface as `502 Upstream provider error.`. Check `docker compose logs mem0` for the full trace, then fix the key on the Configuration page and hit **Save**.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Alembic migrations fail on startup">
|
||||
Inspect the logs:
|
||||
```bash
|
||||
docker compose logs mem0 | grep -i alembic
|
||||
```
|
||||
If the database is unrecoverable, reset the volume (**this destroys all memories and users**):
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="REST API reference" icon="code" href="/open-source/features/rest-api">
|
||||
Endpoint tables, auth modes, and example requests.
|
||||
</Card>
|
||||
<Card title="Configure components" icon="sliders" href="/open-source/configuration">
|
||||
Swap LLMs, embedders, vector stores, and rerankers.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
Reference in New Issue
Block a user