Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92b958c1cc | |||
| c239d8a483 | |||
| 9d6b79a14e | |||
| e44b46ef2e |
@@ -266,7 +266,7 @@ config = MemoryConfig(
|
||||
graph_store=GraphStoreConfig(provider="neo4j", config={...}), # optional
|
||||
history_db_path="~/.mem0/history.db",
|
||||
version="v1.1",
|
||||
custom_fact_extraction_prompt="Custom prompt...",
|
||||
custom_instructions="Custom prompt...",
|
||||
custom_update_memory_prompt="Custom prompt..."
|
||||
)
|
||||
```
|
||||
@@ -684,7 +684,7 @@ Conversation: {messages}
|
||||
"""
|
||||
|
||||
config = MemoryConfig(
|
||||
custom_fact_extraction_prompt=custom_extraction_prompt
|
||||
custom_instructions=custom_extraction_prompt
|
||||
)
|
||||
memory = Memory(config)
|
||||
```
|
||||
|
||||
@@ -47,8 +47,6 @@ Provide at least one message or direct memory string. Most callers supply `messa
|
||||
| `messages` | array | No* | Conversation turns for Mem0 to infer memories from. Each object should include `role` and `content`. |
|
||||
| `metadata` | object | Optional | Custom key/value metadata (e.g., `{"topic": "preferences"}`). |
|
||||
| `infer` | boolean (default `true`) | Optional | Set to `false` to skip inference and store the provided text as-is. |
|
||||
| `async_mode` | boolean (default `true`) | Optional | Controls asynchronous processing. Most clients leave this enabled. |
|
||||
| `output_format` | string (default `v1.1`) | Optional | Response format. `v1.1` wraps results in a `results` array. |
|
||||
|
||||
> \* Provide at least one `messages` entry to describe what you are storing. For scoped memories, include `user_id`. You can also attach `agent_id`, `app_id`, `run_id`, `project_id`, or `org_id` to refine ownership.
|
||||
|
||||
@@ -83,20 +81,3 @@ Successful requests return an array of events queued for processing. Each event
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Graph relationships
|
||||
|
||||
Add Memories can enrich the knowledge graph on write. Set `enable_graph: true` to create entity nodes and relationships for the stored memory. Use this when you want downstream `get_all` or search calls to traverse connected entities.
|
||||
|
||||
<CodeGroup>
|
||||
```json Graph-aware request
|
||||
{
|
||||
"user_id": "alice",
|
||||
"messages": [
|
||||
{ "role": "user", "content": "I met with Dr. Lee at General Hospital." }
|
||||
],
|
||||
"enable_graph": true
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The response follows the same format, and related entities become available in [Graph Memory](/platform/features/graph-memory) queries.
|
||||
|
||||
@@ -62,8 +62,7 @@ To retrieve graph memory relationships between entities, pass `output_format="v1
|
||||
memories = client.get_all(
|
||||
filters={
|
||||
"user_id": "alex"
|
||||
},
|
||||
output_format="v1.1"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Example with the mem0 Python package:
|
||||
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
client = MemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID')
|
||||
client = MemoryClient(api_key="your-api-key")
|
||||
```
|
||||
|
||||
</Tab>
|
||||
@@ -41,10 +41,7 @@ client = MemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID')
|
||||
|
||||
```javascript
|
||||
import { MemoryClient } from "mem0ai";
|
||||
const client = new MemoryClient({
|
||||
organizationId: "YOUR_ORG_ID",
|
||||
projectId: "YOUR_PROJECT_ID"
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: "your-api-key" });
|
||||
```
|
||||
|
||||
</Tab>
|
||||
@@ -98,9 +95,6 @@ client.project.update(
|
||||
custom_instructions="..."
|
||||
)
|
||||
|
||||
# Enable graph memory for the project
|
||||
client.project.update(enable_graph=True)
|
||||
|
||||
# Use the input language for memory storage and retrieval
|
||||
client.project.update(multilingual=True)
|
||||
|
||||
@@ -111,7 +105,6 @@ client.project.update(
|
||||
{"personal_info": "User personal information and preferences"},
|
||||
{"work_context": "Professional context and work-related information"}
|
||||
],
|
||||
enable_graph=True,
|
||||
multilingual=True
|
||||
)
|
||||
```
|
||||
@@ -172,11 +165,11 @@ All project methods are available in async mode:
|
||||
from mem0 import AsyncMemoryClient
|
||||
|
||||
async def manage_project():
|
||||
client = AsyncMemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID')
|
||||
client = AsyncMemoryClient(api_key="your-api-key")
|
||||
|
||||
# All methods support async/await
|
||||
project_info = await client.project.get()
|
||||
await client.project.update(enable_graph=True)
|
||||
await client.project.update(multilingual=True)
|
||||
members = await client.project.get_members()
|
||||
|
||||
# To call the async function properly
|
||||
|
||||
@@ -15,7 +15,7 @@ Mem0 supports LangChain as a provider for vector store integration. LangChain pr
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
from langchain_community.vectorstores import Chroma
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_openai import OpenAIEmbeddings
|
||||
|
||||
# Initialize a LangChain vector store
|
||||
|
||||
@@ -129,13 +129,13 @@ async def search_memories(
|
||||
user_id=USER_ID,
|
||||
limit=5,
|
||||
threshold=0.7, # Higher threshold for more relevant results
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
# Format and return the results
|
||||
if not results.get('results', []):
|
||||
return "I don't have any relevant memories about this topic."
|
||||
|
||||
|
||||
memories = [f"• {result['memory']}" for result in results.get('results', [])]
|
||||
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
|
||||
```
|
||||
@@ -345,13 +345,13 @@ async def search_memories(
|
||||
user_id=USER_ID,
|
||||
limit=5,
|
||||
threshold=0.7, # Higher threshold for more relevant results
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
# Format and return the results
|
||||
if not results.get('results', []):
|
||||
return "I don't have any relevant memories about this topic."
|
||||
|
||||
|
||||
memories = [f"• {result['memory']}" for result in results.get('results', [])]
|
||||
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ With Mem0 and AWS services like Bedrock, OpenSearch, and Neptune Analytics, you
|
||||
<Card title="Neptune Analytics with Mem0" icon="database" href="/cookbooks/integrations/neptune-analytics">
|
||||
Explore graph-based memory storage with AWS Neptune Analytics.
|
||||
</Card>
|
||||
<Card title="Graph Memory Features" icon="sitemap" href="/platform/features/graph-memory">
|
||||
<Card title="Graph Memory Features" icon="sitemap" href="/open-source/features/graph-memory">
|
||||
Learn how to leverage knowledge graphs for entity relationships.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -85,7 +85,6 @@ const messages = [
|
||||
|
||||
await client.add(messages, {
|
||||
user_id: "alice",
|
||||
version: "v2",
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -173,7 +172,6 @@ For full list of supported fields, required formats, and advanced options, see t
|
||||
| Capability | Mem0 Platform | Mem0 OSS |
|
||||
| --- | --- | --- |
|
||||
| Conflict resolution | Automatic with dashboard visibility | SDK handles merges locally; you control storage |
|
||||
| Graph writes | Toggle per request (`enable_graph=True`) | Requires configuring a graph provider |
|
||||
| Rate limits | Managed quotas per workspace | Limited by your hardware and provider APIs |
|
||||
| Dashboard visibility | Yes — inspect memories visually | Inspect via CLI, logs, or custom UI |
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ import MemoryClient from 'mem0ai';
|
||||
|
||||
const client = new MemoryClient({ apiKey: "your-api-key" });
|
||||
|
||||
client.deleteAll({ user_id: "alice" })
|
||||
client.deleteAll({ userId: "alice" })
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -162,12 +162,12 @@ import MemoryClient from 'mem0ai';
|
||||
const client = new MemoryClient({ apiKey: "your-api-key" });
|
||||
|
||||
// Delete all memories across every user in the project
|
||||
client.deleteAll({ user_id: "*" })
|
||||
client.deleteAll({ userId: "*" })
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Full project wipe — all four filters must be explicitly set to "*"
|
||||
client.deleteAll({ user_id: "*", agent_id: "*", app_id: "*", run_id: "*" })
|
||||
client.deleteAll({ userId: "*", agentId: "*", appId: "*", runId: "*" })
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
+42
-18
@@ -70,7 +70,6 @@
|
||||
"platform/features/v2-memory-filters",
|
||||
"platform/features/entity-scoped-memory",
|
||||
"platform/features/async-client",
|
||||
"platform/features/async-mode-default-change",
|
||||
"platform/features/multimodal-support",
|
||||
"platform/features/custom-categories"
|
||||
]
|
||||
@@ -79,7 +78,6 @@
|
||||
"group": "Advanced Features",
|
||||
"icon": "bolt",
|
||||
"pages": [
|
||||
"platform/features/graph-memory",
|
||||
"platform/features/graph-threshold",
|
||||
"platform/features/advanced-retrieval",
|
||||
"platform/advanced-memory-operations",
|
||||
@@ -94,8 +92,7 @@
|
||||
"pages": [
|
||||
"platform/features/direct-import",
|
||||
"platform/features/memory-export",
|
||||
"platform/features/timestamp",
|
||||
"platform/features/expiration-date"
|
||||
"platform/features/timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -122,8 +119,6 @@
|
||||
"icon": "arrow-right",
|
||||
"pages": [
|
||||
"migration/oss-to-platform",
|
||||
"migration/v0-to-v1",
|
||||
"migration/breaking-changes",
|
||||
"migration/api-changes"
|
||||
]
|
||||
},
|
||||
@@ -133,16 +128,6 @@
|
||||
"pages": [
|
||||
"platform/contribute"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Release Notes",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"changelog/highlights",
|
||||
"changelog/sdk",
|
||||
"changelog/platform",
|
||||
"changelog/openclaw"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -182,7 +167,7 @@
|
||||
"open-source/features/reranker-search",
|
||||
"open-source/features/async-memory",
|
||||
"open-source/features/multimodal-support",
|
||||
"open-source/features/custom-fact-extraction-prompt",
|
||||
"open-source/features/custom-instructions",
|
||||
"open-source/features/custom-update-memory-prompt",
|
||||
"open-source/features/rest-api",
|
||||
"open-source/features/openai_compatibility"
|
||||
@@ -562,6 +547,21 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Release Notes",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Release Notes",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"changelog/highlights",
|
||||
"changelog/sdk",
|
||||
"changelog/platform",
|
||||
"changelog/openclaw"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -612,6 +612,30 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/migration/breaking-changes",
|
||||
"destination": "/"
|
||||
},
|
||||
{
|
||||
"source": "/migration/v0-to-v1",
|
||||
"destination": "/"
|
||||
},
|
||||
{
|
||||
"source": "/platform/features/expiration-date",
|
||||
"destination": "/"
|
||||
},
|
||||
{
|
||||
"source": "/platform/features/async-mode-default-change",
|
||||
"destination": "/"
|
||||
},
|
||||
{
|
||||
"source": "/open-source/features/custom-fact-extraction-prompt",
|
||||
"destination": "/open-source/features/custom-instructions"
|
||||
},
|
||||
{
|
||||
"source": "/platform/features/graph-memory",
|
||||
"destination": "/open-source/features/graph-memory"
|
||||
},
|
||||
{
|
||||
"source": "/changelog",
|
||||
"destination": "/changelog/highlights"
|
||||
@@ -966,7 +990,7 @@
|
||||
},
|
||||
{
|
||||
"source": "/features/graph-memory",
|
||||
"destination": "/platform/features/graph-memory"
|
||||
"destination": "/open-source/features/graph-memory"
|
||||
},
|
||||
{
|
||||
"source": "/features/:slug",
|
||||
|
||||
@@ -268,7 +268,7 @@ memories = mem0.search(
|
||||
{"categories": {"contains": "travel"}}
|
||||
]
|
||||
},
|
||||
limit=5
|
||||
top_k=5
|
||||
)
|
||||
|
||||
# Configure agent with custom model settings
|
||||
|
||||
@@ -16,7 +16,7 @@ Combining Mem0 with Keywords AI allows you to:
|
||||
4. Optimize token usage and reduce costs
|
||||
|
||||
<Note>
|
||||
You can get your Mem0 API key, user_id, and org_id from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a>. These are required for proper integration.
|
||||
You can get your Mem0 API key from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a>.
|
||||
</Note>
|
||||
|
||||
## Setup and Configuration
|
||||
@@ -107,7 +107,6 @@ response = client.chat.completions.create(
|
||||
extra_body={
|
||||
"mem0_params": {
|
||||
"user_id": "test_user",
|
||||
"org_id": "org_1",
|
||||
"api_key": os.environ.get("MEM0_API_KEY"),
|
||||
"add_memories": {
|
||||
"messages": messages,
|
||||
|
||||
@@ -29,10 +29,7 @@ import os
|
||||
|
||||
os.environ["MEM0_API_KEY"] = "your-api-key"
|
||||
|
||||
client = MemoryClient(
|
||||
org_id=your_org_id,
|
||||
project_id=your_project_id
|
||||
)
|
||||
client = MemoryClient()
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
@@ -45,7 +45,7 @@ mem0 = MemoryClient()
|
||||
@function_tool
|
||||
def search_memory(query: str, user_id: str) -> str:
|
||||
"""Search through past conversations and memories"""
|
||||
memories = mem0.search(query, user_id=user_id, limit=3)
|
||||
memories = mem0.search(query, user_id=user_id, top_k=3)
|
||||
if memories and memories.get('results'):
|
||||
return "\n".join([f"- {mem['memory']}" for mem in memories['results']])
|
||||
return "No relevant memories found."
|
||||
@@ -215,7 +215,7 @@ Customize memory behavior:
|
||||
memories = mem0.search(
|
||||
query="travel preferences",
|
||||
user_id="alex",
|
||||
limit=5 # Number of memories to retrieve
|
||||
top_k=5 # Number of memories to retrieve
|
||||
)
|
||||
|
||||
# Add metadata to memories
|
||||
|
||||
@@ -155,7 +155,7 @@ openclaw mem0 stats
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `customPrompt` | `string` | *(built-in)* | Extraction prompt for memory processing |
|
||||
| `customInstructions` | `string` | *(built-in)* | Extraction prompt for memory processing |
|
||||
| `oss.embedder.provider` | `string` | `"openai"` | Embedding provider (`"openai"`, `"ollama"`, etc.) |
|
||||
| `oss.embedder.config` | `object` | — | Provider config: `apiKey`, `model`, `baseURL` |
|
||||
| `oss.vectorStore.provider` | `string` | `"memory"` | Vector store (`"memory"`, `"qdrant"`, `"chroma"`, etc.) |
|
||||
|
||||
@@ -52,7 +52,7 @@ npm install @mem0/vercel-ai-provider
|
||||
|
||||
> **Note**: The `openai` provider is set as default. Consider using `MEM0_API_KEY` and `OPENAI_API_KEY` as environment variables for security.
|
||||
|
||||
> **Note**: The `mem0Config` is optional. It is used to set the global config for the Mem0 Client (eg. `user_id`, `agent_id`, `app_id`, `run_id`, `org_id`, `project_id` etc).
|
||||
> **Note**: The `mem0Config` is optional. It is used to set the global config for the Mem0 Client (eg. `user_id`, `agent_id`, `app_id`, `run_id` etc).
|
||||
|
||||
3. Add Memories to Enhance Context:
|
||||
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ Key differentiators:
|
||||
- [Reranker Search](https://docs.mem0.ai/open-source/features/reranker-search): Enhanced search results with reranking models
|
||||
- [Async Memory](https://docs.mem0.ai/open-source/features/async-memory): Asynchronous memory operations for better performance
|
||||
- [Multimodal Support](https://docs.mem0.ai/open-source/features/multimodal-support): Handle text, images, and documents in self-hosted setup
|
||||
- [Custom Fact Extraction](https://docs.mem0.ai/open-source/features/custom-fact-extraction-prompt): Tailor information extraction for specific use cases
|
||||
- [Custom Instructions](https://docs.mem0.ai/open-source/features/custom-instructions): Tailor information extraction for specific use cases
|
||||
- [Custom Memory Update Prompt](https://docs.mem0.ai/open-source/features/custom-update-memory-prompt): Customize how memories are updated and merged
|
||||
- [REST API Server](https://docs.mem0.ai/open-source/features/rest-api): FastAPI-based server with core operations and OpenAPI documentation
|
||||
- [OpenAI Compatibility](https://docs.mem0.ai/open-source/features/openai_compatibility): Seamless integration with OpenAI-compatible APIs
|
||||
|
||||
@@ -319,7 +319,7 @@ config = {
|
||||
"graph_store": {...},
|
||||
"version": "v1.0", # ❌ v1.0 no longer supported
|
||||
"history_db_path": "...",
|
||||
"custom_fact_extraction_prompt": "..."
|
||||
"custom_instructions": "..."
|
||||
}
|
||||
```
|
||||
|
||||
@@ -336,7 +336,7 @@ config = {
|
||||
},
|
||||
"version": "v1.1", # ✅ v1.1+ only
|
||||
"history_db_path": "...",
|
||||
"custom_fact_extraction_prompt": "...",
|
||||
"custom_instructions": "...",
|
||||
"custom_update_memory_prompt": "..." # ✅ NEW: Custom update prompt
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
---
|
||||
title: Breaking Changes in v1.0.0
|
||||
description: 'Complete list of breaking changes when upgrading from v0.x to v1.0.0 '
|
||||
icon: "triangle-exclamation"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
<Warning>
|
||||
**Important:** This page lists all breaking changes. Please review carefully before upgrading.
|
||||
</Warning>
|
||||
|
||||
## API Version Changes
|
||||
|
||||
### Removed v1.0 API Support
|
||||
|
||||
**Breaking Change:** The v1.0 API format is completely removed and no longer supported.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
# This was supported in v0.x
|
||||
config = {
|
||||
"version": "v1.0" # ❌ No longer supported
|
||||
}
|
||||
|
||||
result = m.add(
|
||||
"memory content",
|
||||
user_id="alice"
|
||||
)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# v1.1 is the minimum supported version
|
||||
config = {
|
||||
"version": "v1.1" # ✅ Required minimum
|
||||
}
|
||||
|
||||
result = m.add(
|
||||
"memory content",
|
||||
user_id="alice"
|
||||
)
|
||||
```
|
||||
|
||||
**Error Message:**
|
||||
```
|
||||
ValueError: The v1.0 API format is no longer supported in mem0ai 1.0.0+.
|
||||
Please use v1.1 format which returns a dict with 'results' key.
|
||||
```
|
||||
|
||||
## Parameter Removals
|
||||
|
||||
### 1. version Parameter in Method Calls
|
||||
|
||||
**Breaking Change:** Version parameter removed from method calls.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
result = m.add("content", user_id="alice", version="v1.0")
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
result = m.add("content", user_id="alice")
|
||||
```
|
||||
|
||||
### 2. async_mode Parameter (Platform Client)
|
||||
|
||||
**Change:** For `MemoryClient` (Platform API), `async_mode` now defaults to `True` but can still be configured.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-key")
|
||||
result = client.add("content", user_id="alice", async_mode=True)
|
||||
result = client.add("content", user_id="alice", async_mode=False)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-key")
|
||||
|
||||
# async_mode now defaults to True, but you can still override it
|
||||
result = client.add("content", user_id="alice") # Uses async_mode=True by default
|
||||
|
||||
# You can still explicitly set it to False if needed
|
||||
result = client.add("content", user_id="alice", async_mode=False)
|
||||
```
|
||||
|
||||
## Response Format Changes
|
||||
|
||||
### Standardized Response Structure
|
||||
|
||||
**Breaking Change:** All responses now return a standardized dictionary format.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
# Could return different formats based on version configuration
|
||||
result = m.add("content", user_id="alice")
|
||||
# With v1.0: Returns [{"id": "...", "memory": "...", "event": "ADD"}]
|
||||
# With v1.1: Returns {"results": [{"id": "...", "memory": "...", "event": "ADD"}]}
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# Always returns standardized format
|
||||
result = m.add("content", user_id="alice")
|
||||
# Always returns: {"results": [{"id": "...", "memory": "...", "event": "ADD"}]}
|
||||
|
||||
# Access results consistently
|
||||
for memory in result["results"]:
|
||||
print(memory["memory"])
|
||||
```
|
||||
|
||||
## Configuration Changes
|
||||
|
||||
### Version Configuration
|
||||
|
||||
**Breaking Change:** Default API version changed.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
# v1.0 was supported
|
||||
config = {
|
||||
"version": "v1.0" # ❌ No longer supported
|
||||
}
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# v1.1 is minimum, v1.1 is default
|
||||
config = {
|
||||
"version": "v1.1" # ✅ Minimum supported
|
||||
}
|
||||
|
||||
# Or omit for default
|
||||
config = {
|
||||
# version defaults to v1.1
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Configuration
|
||||
|
||||
**Breaking Change:** Some configuration options have changed defaults.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
# Default configuration in v0.x
|
||||
m = Memory() # Used default settings suitable for v0.x
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
# Default configuration optimized for v1.0.0
|
||||
m = Memory() # Uses v1.1+ optimized defaults
|
||||
|
||||
# Explicit configuration recommended
|
||||
config = {
|
||||
"version": "v1.1",
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
}
|
||||
}
|
||||
m = Memory.from_config(config)
|
||||
```
|
||||
|
||||
## Method Signature Changes
|
||||
|
||||
### Search Method
|
||||
|
||||
**Enhanced but backward compatible:**
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
results = m.search(
|
||||
"query",
|
||||
user_id="alice",
|
||||
filters={"key": "value"} # Simple key-value only
|
||||
)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# Basic usage remains the same
|
||||
results = m.search("query", user_id="alice")
|
||||
|
||||
# Enhanced filtering available (optional)
|
||||
results = m.search(
|
||||
"query",
|
||||
user_id="alice",
|
||||
filters={
|
||||
"AND": [
|
||||
{"key": "value"},
|
||||
{"score": {"gte": 0.8}}
|
||||
]
|
||||
},
|
||||
rerank=True # New parameter
|
||||
)
|
||||
```
|
||||
|
||||
## Error Handling Changes
|
||||
|
||||
### New Error Types
|
||||
|
||||
**Breaking Change:** More specific error types and messages.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
try:
|
||||
result = m.add("content", user_id="alice", version="v1.0")
|
||||
except Exception as e:
|
||||
print(f"Generic error: {e}")
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
try:
|
||||
result = m.add("content", user_id="alice")
|
||||
except ValueError as e:
|
||||
if "v1.0 API format is no longer supported" in str(e):
|
||||
# Handle version error specifically
|
||||
print("Please upgrade your code to use v1.1+ format")
|
||||
else:
|
||||
print(f"Value error: {e}")
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}")
|
||||
```
|
||||
|
||||
### Validation Changes
|
||||
|
||||
**Breaking Change:** Stricter parameter validation.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
# Some invalid parameters might have been ignored
|
||||
result = m.add(
|
||||
"content",
|
||||
user_id="alice",
|
||||
invalid_param="ignored" # Might have been silently ignored
|
||||
)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# Strict validation - unknown parameters cause errors
|
||||
try:
|
||||
result = m.add(
|
||||
"content",
|
||||
user_id="alice",
|
||||
invalid_param="value" # ❌ Will raise TypeError
|
||||
)
|
||||
except TypeError as e:
|
||||
print(f"Invalid parameter: {e}")
|
||||
```
|
||||
|
||||
## Import Changes
|
||||
|
||||
### No Breaking Changes in Imports
|
||||
|
||||
**Good News:** Import statements remain the same.
|
||||
|
||||
```python
|
||||
# These imports work in both v0.x and v1.0.0
|
||||
from mem0 import Memory, AsyncMemory
|
||||
from mem0 import MemoryConfig
|
||||
```
|
||||
|
||||
## Dependency Changes
|
||||
|
||||
### Minimum Python Version
|
||||
|
||||
**Potential Breaking Change:** Check Python version requirements.
|
||||
|
||||
#### Before (v0.x)
|
||||
- Python 3.8+ supported
|
||||
|
||||
#### After (v1.0.0 )
|
||||
- Python 3.9+ required (check current requirements)
|
||||
|
||||
### Package Dependencies
|
||||
|
||||
**Breaking Change:** Some dependencies updated with potential breaking changes.
|
||||
|
||||
```bash
|
||||
# Check for conflicts after upgrade
|
||||
pip install --upgrade mem0ai
|
||||
pip check # Verify no dependency conflicts
|
||||
```
|
||||
|
||||
## Data Migration
|
||||
|
||||
### Database Schema
|
||||
|
||||
**Good News:** No database schema changes required.
|
||||
|
||||
- Existing memories remain compatible
|
||||
- No data migration required
|
||||
- Vector store data unchanged
|
||||
|
||||
### Memory Format
|
||||
|
||||
**Good News:** Memory storage format unchanged.
|
||||
|
||||
- Existing memories work with v1.0.0
|
||||
- Search continues to work with old memories
|
||||
- No re-indexing required
|
||||
|
||||
## Testing Changes
|
||||
|
||||
### Test Updates Required
|
||||
|
||||
**Breaking Change:** Update tests for new response format.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
def test_add_memory():
|
||||
result = m.add("content", user_id="alice")
|
||||
assert isinstance(result, list) # ❌ No longer true
|
||||
assert len(result) > 0
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
def test_add_memory():
|
||||
result = m.add("content", user_id="alice")
|
||||
assert isinstance(result, dict) # ✅ Always dict
|
||||
assert "results" in result # ✅ Always has results key
|
||||
assert len(result["results"]) > 0
|
||||
```
|
||||
|
||||
## Rollback Considerations
|
||||
|
||||
### Safe Rollback Process
|
||||
|
||||
If you need to rollback:
|
||||
|
||||
```bash
|
||||
# 1. Rollback package
|
||||
pip install mem0ai==0.1.20 # Last stable v0.x
|
||||
|
||||
# 2. Revert code changes
|
||||
git checkout previous_commit
|
||||
|
||||
# 3. Test functionality
|
||||
python test_mem0_functionality.py
|
||||
```
|
||||
|
||||
### Data Safety
|
||||
|
||||
- **Safe:** Memories stored in v0.x format work with v1.0.0
|
||||
- **Safe:** Rollback doesn't lose data
|
||||
- **Safe:** Vector store data remains intact
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review all breaking changes** in your codebase
|
||||
2. **Update method calls** to remove deprecated parameters
|
||||
3. **Update response handling** to use standardized format
|
||||
4. **Test thoroughly** with your existing data
|
||||
5. **Update error handling** for new error types
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Migration Guide" icon="arrow-right" href="/migration/v0-to-v1">
|
||||
Step-by-step migration instructions
|
||||
</Card>
|
||||
<Card title="API Changes" icon="code" href="/migration/api-changes">
|
||||
Complete API reference changes
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Warning>
|
||||
**Need Help?** If you encounter issues during migration, check our [GitHub Discussions](https://github.com/mem0ai/mem0/discussions) or community support channels.
|
||||
</Warning>
|
||||
@@ -81,6 +81,10 @@ client = MemoryClient(api_key="m0-...")
|
||||
**Critical Change**: Platform uses v2 endpoints that require filtering parameters to be nested inside a `filters` dictionary.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The `limit` parameter has been removed in favor of `top_k` across all SDKs. Update any code using `limit=` to use `top_k=` instead.
|
||||
</Note>
|
||||
|
||||
| Method | Open Source | Platform |
|
||||
| ------ | ----------- | -------- |
|
||||
| `search()` | `m.search(query, user_id="alex")` | `client.search(query, filters={"user_id": "alex"})` |
|
||||
@@ -121,18 +125,18 @@ Note: `add()` and `delete()` methods remain unchanged. The `update()` method is
|
||||
<CodeGroup>
|
||||
```python Open Source (Old)
|
||||
# Get all memories for a user
|
||||
memories = m.get_all(user_id="alex", limit=10)
|
||||
memories = m.get_all(user_id="alex", top_k=10)
|
||||
|
||||
# Get memories with pagination
|
||||
memories = m.get_all(user_id="alex", limit=5, offset=10)
|
||||
memories = m.get_all(user_id="alex", top_k=5, offset=10)
|
||||
```
|
||||
|
||||
```python Platform (New)
|
||||
# Get all memories for a user
|
||||
memories = client.get_all(filters={"user_id": "alex"}, limit=10)
|
||||
memories = client.get_all(filters={"user_id": "alex"}, top_k=10)
|
||||
|
||||
# Get memories with pagination
|
||||
memories = client.get_all(filters={"user_id": "alex"}, limit=5, offset=10)
|
||||
memories = client.get_all(filters={"user_id": "alex"}, top_k=5, offset=10)
|
||||
```
|
||||
</CodeGroup>
|
||||
</Accordion>
|
||||
@@ -283,7 +287,7 @@ The Platform introduces powerful capabilities not available in OSS:
|
||||
"user preferences",
|
||||
filters={"user_id": "alex"},
|
||||
rerank=True, # Platform exclusive
|
||||
limit=5
|
||||
top_k=5
|
||||
)
|
||||
|
||||
# Search with keyword expansion
|
||||
@@ -335,7 +339,7 @@ The Platform introduces powerful capabilities not available in OSS:
|
||||
{"timestamp": {"gte": "2024-01-01"}}
|
||||
]
|
||||
},
|
||||
limit=100
|
||||
top_k=100
|
||||
)
|
||||
|
||||
# Monitor usage patterns
|
||||
|
||||
@@ -1,481 +0,0 @@
|
||||
---
|
||||
title: Migrating from v0.x to v1.0.0
|
||||
description: 'Complete guide to upgrade your Mem0 implementation to version 1.0.0 '
|
||||
icon: "arrow-right"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
<Warning>
|
||||
**Breaking Changes Ahead!** Mem0 1.0.0 introduces several breaking changes. Please read this guide carefully before upgrading.
|
||||
</Warning>
|
||||
|
||||
## Overview
|
||||
|
||||
Mem0 1.0.0 is a major release that modernizes the API, improves performance, and adds powerful new features. This guide will help you migrate your existing v0.x implementation to the new version.
|
||||
|
||||
## Key Changes Summary
|
||||
|
||||
| Feature | v0.x | v1.0.0 | Migration Required |
|
||||
|---------|------|-------------|-------------------|
|
||||
| API Version | v1.0 supported | v1.0 **removed**, v1.1+ only | ✅ Yes |
|
||||
| Async Mode (Platform Client) | Optional/manual | Defaults to `True`, configurable | ⚠️ Partial |
|
||||
| Metadata Filtering | Basic | Enhanced with operators | ⚠️ Optional |
|
||||
| Reranking | Not available | Full support | ⚠️ Optional |
|
||||
|
||||
## Step-by-Step Migration
|
||||
|
||||
### 1. Update Installation
|
||||
|
||||
```bash
|
||||
# Update to the latest version
|
||||
pip install --upgrade mem0ai
|
||||
```
|
||||
|
||||
### 2. Remove Deprecated Parameters
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
# These parameters are no longer supported
|
||||
m = Memory()
|
||||
result = m.add(
|
||||
"I love pizza",
|
||||
user_id="alice",
|
||||
version="v1.0" # ❌ REMOVED
|
||||
)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
# Clean, simplified API
|
||||
m = Memory()
|
||||
result = m.add(
|
||||
"I love pizza",
|
||||
user_id="alice"
|
||||
# version parameter removed
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Update Configuration
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
},
|
||||
"version": "v1.0" # ❌ No longer supported
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
},
|
||||
"version": "v1.1" # ✅ v1.1 is the minimum supported version
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
```
|
||||
|
||||
### 4. Handle Response Format Changes
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
# Response could be a list or dict depending on version
|
||||
result = m.add("I love coffee", user_id="alice")
|
||||
|
||||
if isinstance(result, list):
|
||||
# Handle list format
|
||||
for item in result:
|
||||
print(item["memory"])
|
||||
else:
|
||||
# Handle dict format
|
||||
print(result["results"])
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# Response is always a standardized dict with "results" key
|
||||
result = m.add("I love coffee", user_id="alice")
|
||||
|
||||
# Always access via "results" key
|
||||
for item in result["results"]:
|
||||
print(item["memory"])
|
||||
```
|
||||
|
||||
### 5. Update Search Operations
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
# Basic search
|
||||
results = m.search("What do I like?", user_id="alice")
|
||||
|
||||
# With filters
|
||||
results = m.search(
|
||||
"What do I like?",
|
||||
user_id="alice",
|
||||
filters={"category": "food"}
|
||||
)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
# Same basic search API
|
||||
results = m.search("What do I like?", user_id="alice")
|
||||
|
||||
# Enhanced filtering with operators (optional upgrade)
|
||||
results = m.search(
|
||||
"What do I like?",
|
||||
user_id="alice",
|
||||
filters={
|
||||
"AND": [
|
||||
{"category": "food"},
|
||||
{"rating": {"gte": 8}}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# New: Reranking support (optional)
|
||||
results = m.search(
|
||||
"What do I like?",
|
||||
user_id="alice",
|
||||
rerank=True # Requires reranker configuration
|
||||
)
|
||||
```
|
||||
|
||||
### 6. Platform Client async_mode Default Changed
|
||||
|
||||
**Change:** For `MemoryClient`, the `async_mode` parameter now defaults to `True` for better performance.
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-key")
|
||||
|
||||
# Had to explicitly set async_mode
|
||||
result = client.add("I enjoy hiking", user_id="alice", async_mode=True)
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-key")
|
||||
|
||||
# async_mode now defaults to True (best performance)
|
||||
result = client.add("I enjoy hiking", user_id="alice")
|
||||
|
||||
# You can still override if needed for synchronous processing
|
||||
result = client.add("I enjoy hiking", user_id="alice", async_mode=False)
|
||||
```
|
||||
|
||||
## Configuration Migration
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
#### Before (v0.x)
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"api_key": "your-key"
|
||||
}
|
||||
},
|
||||
"version": "v1.0"
|
||||
}
|
||||
```
|
||||
|
||||
#### After (v1.0.0 )
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"api_key": "your-key"
|
||||
}
|
||||
},
|
||||
"version": "v1.1", # Minimum supported version
|
||||
|
||||
# New optional features
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
"config": {
|
||||
"model": "rerank-english-v3.0",
|
||||
"api_key": "your-cohere-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Enhanced Features (Optional)
|
||||
|
||||
```python
|
||||
# Take advantage of new features
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": 6333
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4",
|
||||
"api_key": "your-key"
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small",
|
||||
"api_key": "your-key"
|
||||
}
|
||||
},
|
||||
"reranker": {
|
||||
"provider": "sentence_transformer",
|
||||
"config": {
|
||||
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
||||
}
|
||||
},
|
||||
"version": "v1.1"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling Migration
|
||||
|
||||
### Before (v0.x)
|
||||
```python
|
||||
try:
|
||||
result = m.add("memory", user_id="alice", version="v1.0")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
```
|
||||
|
||||
### After (v1.0.0 )
|
||||
```python
|
||||
try:
|
||||
result = m.add("memory", user_id="alice")
|
||||
except ValueError as e:
|
||||
if "v1.0 API format is no longer supported" in str(e):
|
||||
print("Please upgrade your code to use v1.1+ format")
|
||||
else:
|
||||
print(f"Error: {e}")
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}")
|
||||
```
|
||||
|
||||
## Testing Your Migration
|
||||
|
||||
### 1. Basic Functionality Test
|
||||
|
||||
```python
|
||||
def test_basic_functionality():
|
||||
m = Memory()
|
||||
|
||||
# Test add
|
||||
result = m.add("I love testing", user_id="test_user")
|
||||
assert "results" in result
|
||||
assert len(result["results"]) > 0
|
||||
|
||||
# Test search
|
||||
search_results = m.search("testing", user_id="test_user")
|
||||
assert "results" in search_results
|
||||
|
||||
# Test get_all
|
||||
all_memories = m.get_all(user_id="test_user")
|
||||
assert "results" in all_memories
|
||||
|
||||
print("✅ Basic functionality test passed")
|
||||
|
||||
test_basic_functionality()
|
||||
```
|
||||
|
||||
### 2. Enhanced Features Test
|
||||
|
||||
```python
|
||||
def test_enhanced_features():
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "sentence_transformer",
|
||||
"config": {
|
||||
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
|
||||
# Test reranking
|
||||
m.add("I love advanced features", user_id="test_user")
|
||||
results = m.search("features", user_id="test_user", rerank=True)
|
||||
assert "results" in results
|
||||
|
||||
# Test enhanced filtering
|
||||
results = m.search(
|
||||
"features",
|
||||
user_id="test_user",
|
||||
filters={"user_id": {"eq": "test_user"}}
|
||||
)
|
||||
assert "results" in results
|
||||
|
||||
print("✅ Enhanced features test passed")
|
||||
|
||||
test_enhanced_features()
|
||||
```
|
||||
|
||||
## Common Migration Issues
|
||||
|
||||
### Issue 1: Version Error
|
||||
|
||||
**Error:**
|
||||
```
|
||||
ValueError: The v1.0 API format is no longer supported in mem0ai 1.0.0+
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```python
|
||||
# Remove version parameters or set to v1.1+
|
||||
config = {
|
||||
# ... other config
|
||||
"version": "v1.1" # or remove entirely for default
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 2: Response Format Error
|
||||
|
||||
**Error:**
|
||||
```
|
||||
KeyError: 'results'
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```python
|
||||
# Always access response via "results" key
|
||||
result = m.add("memory", user_id="alice")
|
||||
memories = result["results"] # Not result directly
|
||||
```
|
||||
|
||||
### Issue 3: Parameter Error
|
||||
|
||||
**Error:**
|
||||
```
|
||||
TypeError: add() got an unexpected keyword argument 'output_format'
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```python
|
||||
# Remove deprecated parameters
|
||||
result = m.add(
|
||||
"memory",
|
||||
user_id="alice"
|
||||
# Remove: version
|
||||
)
|
||||
```
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If you encounter issues during migration:
|
||||
|
||||
### 1. Immediate Rollback
|
||||
|
||||
```bash
|
||||
# Downgrade to last v0.x version
|
||||
pip install mem0ai==0.1.20 # Replace with your last working version
|
||||
```
|
||||
|
||||
### 2. Gradual Migration
|
||||
|
||||
```python
|
||||
# Test both versions side by side
|
||||
import mem0_v0 # Your old version
|
||||
import mem0 # New version
|
||||
|
||||
def compare_results(query, user_id):
|
||||
old_results = mem0_v0.search(query, user_id=user_id)
|
||||
new_results = mem0.search(query, user_id=user_id)
|
||||
|
||||
print("Old format:", old_results)
|
||||
print("New format:", new_results["results"])
|
||||
```
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
### Before (v0.x)
|
||||
```python
|
||||
# Sequential operations
|
||||
result1 = m.add("memory 1", user_id="alice")
|
||||
result2 = m.add("memory 2", user_id="alice")
|
||||
result3 = m.search("query", user_id="alice")
|
||||
```
|
||||
|
||||
### After (v1.0.0 )
|
||||
```python
|
||||
# Better async performance
|
||||
async def batch_operations():
|
||||
async_memory = AsyncMemory()
|
||||
|
||||
# Concurrent operations
|
||||
results = await asyncio.gather(
|
||||
async_memory.add("memory 1", user_id="alice"),
|
||||
async_memory.add("memory 2", user_id="alice"),
|
||||
async_memory.search("query", user_id="alice")
|
||||
)
|
||||
return results
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Complete the migration** using this guide
|
||||
2. **Test thoroughly** with your existing data
|
||||
3. **Explore new features** like enhanced filtering and reranking
|
||||
4. **Update your documentation** to reflect the new API
|
||||
5. **Monitor performance** and optimize as needed
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Breaking Changes" icon="triangle-exclamation" href="/migration/breaking-changes">
|
||||
Detailed list of all breaking changes
|
||||
</Card>
|
||||
<Card title="API Changes" icon="code" href="/migration/api-changes">
|
||||
Complete API reference changes
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Info>
|
||||
Need help with migration? Check our [GitHub Discussions](https://github.com/mem0ai/mem0/discussions) or reach out to our community for support.
|
||||
</Info>
|
||||
@@ -240,7 +240,7 @@ 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, user_id=user_id, limit=3)
|
||||
search_result = await async_memory.search(query=message, user_id=user_id, top_k=3)
|
||||
relevant_memories = search_result["results"]
|
||||
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories)
|
||||
|
||||
@@ -326,7 +326,7 @@ async def add_memory(messages: list, user_id: str):
|
||||
@app.get("/memories/search")
|
||||
async def search_memories(query: str, user_id: str, limit: int = 10):
|
||||
try:
|
||||
result = await memory.search(query=query, user_id=user_id, limit=limit)
|
||||
result = await memory.search(query=query, user_id=user_id, top_k=limit)
|
||||
return {"status": "success", "data": result}
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
+17
-13
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: Custom Fact Extraction Prompt
|
||||
title: Custom Instructions
|
||||
description: Tailor fact extraction so Mem0 stores only the details you care about.
|
||||
icon: "wand-magic-sparkles"
|
||||
---
|
||||
|
||||
Custom fact extraction prompts 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.
|
||||
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…**
|
||||
**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.
|
||||
@@ -17,6 +17,10 @@ Custom fact extraction prompts let you decide exactly which facts Mem0 records f
|
||||
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
|
||||
@@ -24,13 +28,13 @@ Custom fact extraction prompts let you decide exactly which facts Mem0 records f
|
||||
- **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_fact_extraction_prompt` (Python) or `customPrompt` (TypeScript) lives alongside your model settings.
|
||||
- **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.
|
||||
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>
|
||||
@@ -43,8 +47,8 @@ Custom fact extraction prompts let you decide exactly which facts Mem0 records f
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
custom_fact_extraction_prompt = """
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
custom_instructions = """
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
Here are some few shot examples:
|
||||
|
||||
Input: Hi.
|
||||
@@ -67,8 +71,8 @@ Return the facts and customer information in a json format as shown above.
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
const customPrompt = `
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
const customInstructions = `
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
Here are some few shot examples:
|
||||
|
||||
Input: Hi.
|
||||
@@ -110,7 +114,7 @@ config = {
|
||||
"max_tokens": 2000,
|
||||
}
|
||||
},
|
||||
"custom_fact_extraction_prompt": custom_fact_extraction_prompt,
|
||||
"custom_instructions": custom_instructions,
|
||||
"version": "v1.1"
|
||||
}
|
||||
|
||||
@@ -131,7 +135,7 @@ const config = {
|
||||
maxTokens: 1500,
|
||||
},
|
||||
},
|
||||
customPrompt: customPrompt,
|
||||
customInstructions: customInstructions,
|
||||
};
|
||||
|
||||
const memory = new Memory(config);
|
||||
@@ -263,7 +263,7 @@ Please note to return the IDs in the output from the input IDs only and do not g
|
||||
- Log each decision so product teams can review why a change happened.
|
||||
|
||||
<Note>
|
||||
The prompt works alongside `custom_fact_extraction_prompt`—fact extraction identifies candidate facts, and the update prompt decides how to merge them into long-term storage.
|
||||
The prompt works alongside `custom_instructions`—fact extraction identifies candidate facts, and the update prompt decides how to merge them into long-term storage.
|
||||
</Note>
|
||||
|
||||
---
|
||||
@@ -288,7 +288,7 @@ Please note to return the IDs in the output from the input IDs only and do not g
|
||||
|
||||
## Compare prompts
|
||||
|
||||
| Feature | `custom_update_memory_prompt` | `custom_fact_extraction_prompt` |
|
||||
| Feature | `custom_update_memory_prompt` | `custom_instructions` |
|
||||
| --- | --- | --- |
|
||||
| Primary job | Decide memory actions (ADD/UPDATE/DELETE/NONE) | Pull facts from user and assistant messages |
|
||||
| Inputs | Retrieved facts + existing memory entries | Raw conversation turns |
|
||||
@@ -297,7 +297,7 @@ Please note to return the IDs in the output from the input IDs only and do not g
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Design Fact Extraction" icon="sparkles" href="/open-source/features/custom-fact-extraction-prompt">
|
||||
<Card title="Design Fact Extraction" icon="sparkles" href="/open-source/features/custom-instructions">
|
||||
Coordinate both prompts so fact extraction feeds clean inputs into the update flow.
|
||||
</Card>
|
||||
<Card title="Build Email Automations" icon="inbox" href="/cookbooks/operations/email-automation">
|
||||
|
||||
@@ -94,7 +94,7 @@ memory.add(conversation, user_id="demo-user")
|
||||
results = memory.search(
|
||||
"Who did Alice meet at GraphConf?",
|
||||
user_id="demo-user",
|
||||
limit=3,
|
||||
top_k=3,
|
||||
rerank=True,
|
||||
)
|
||||
|
||||
@@ -123,7 +123,6 @@ export NEO4J_PASSWORD="your-password"
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const config = {
|
||||
enableGraph: true,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
@@ -146,7 +145,7 @@ await memory.add(conversation, { userId: "demo-user" });
|
||||
|
||||
const results = await memory.search(
|
||||
"Who did Alice meet at GraphConf?",
|
||||
{ userId: "demo-user", limit: 3, rerank: true }
|
||||
{ userId: "demo-user", topK: 3, rerank: true }
|
||||
);
|
||||
|
||||
results.results.forEach((hit) => {
|
||||
@@ -196,7 +195,6 @@ memory = Memory.from_config(config_dict=config)
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const config = {
|
||||
enableGraph: true,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
@@ -204,7 +202,7 @@ const config = {
|
||||
username: process.env.NEO4J_USERNAME!,
|
||||
password: process.env.NEO4J_PASSWORD!,
|
||||
},
|
||||
customPrompt: "Please only capture people, organisations, and project links.",
|
||||
customInstructions: "Please only capture people, organisations, and project links.",
|
||||
}
|
||||
};
|
||||
|
||||
@@ -217,14 +215,6 @@ const memory = new Memory(config);
|
||||
|
||||
```python
|
||||
config["graph_store"]["config"]["threshold"] = 0.75
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Toggle graph writes per request">
|
||||
Disable graph writes or reads when you only want vector behaviour.
|
||||
|
||||
```python
|
||||
memory.add(messages, user_id="demo-user", enable_graph=False)
|
||||
results = memory.search("marketing partners", user_id="demo-user", enable_graph=False)
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Organize multi-agent graphs">
|
||||
@@ -257,15 +247,12 @@ Monitor graph growth, especially on free tiers, by periodically cleaning dormant
|
||||
<Accordion title="Neptune Analytics rejects requests">
|
||||
Ensure the graph identifier matches the vector dimension used by your embedder and that the IAM role allows `neptune-graph:*DataViaQuery` actions.
|
||||
</Accordion>
|
||||
<Accordion title="Graph store outage fallback">
|
||||
Catch the provider error and retry with `enable_graph=False` so vector-only search keeps serving responses while the graph backend recovers.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Decision Points
|
||||
|
||||
- Select the graph store that fits your deployment (managed Aura vs. self-hosted Neo4j vs. AWS Neptune vs. local Kuzu vs. Apache AGE on PostgreSQL).
|
||||
- Decide when to enable graph writes per request; routine conversations may stay vector-only to save latency.
|
||||
- Decide whether to include a graph store in your config; routine conversations may stay vector-only to save latency.
|
||||
- Set a policy for pruning stale relationships so your graph stays fast and affordable.
|
||||
|
||||
## Provider setup
|
||||
@@ -280,7 +267,6 @@ Choose your backend and expand the matching panel for configuration details and
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const config = {
|
||||
enableGraph: true,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
|
||||
@@ -81,7 +81,7 @@ const messages = [
|
||||
}
|
||||
];
|
||||
|
||||
await client.add(messages, { user_id: "alice" });
|
||||
await client.add(messages, { userId: "alice" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -148,7 +148,7 @@ const messages = [
|
||||
}
|
||||
];
|
||||
|
||||
await client.add(messages, { user_id: "alice" });
|
||||
await client.add(messages, { userId: "alice" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -264,7 +264,7 @@ try {
|
||||
}
|
||||
}];
|
||||
|
||||
await client.add(messages, { user_id: "user123" });
|
||||
await client.add(messages, { userId: "user123" });
|
||||
console.log("Image processed successfully");
|
||||
} catch (error: any) {
|
||||
if (error.type === "invalid_image") {
|
||||
|
||||
@@ -131,7 +131,7 @@ print(response.choices[0].message.content)
|
||||
| `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. |
|
||||
| `limit` | `int` | Cap how many memories Mem0 pulls into the context (default 10). |
|
||||
| `top_k` | `int` | Cap how many memories Mem0 pulls into the context (default 10). |
|
||||
|
||||
Other request fields mirror OpenAI’s chat completion API.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Mem0 Open Source ships with capabilities that adapt memory behavior for producti
|
||||
<Card title="Multimodal Support" icon="image" href="/open-source/features/multimodal-support">
|
||||
Process images, audio, and video memories.
|
||||
</Card>
|
||||
<Card title="Custom Fact Extraction" icon="wand-magic-sparkles" href="/open-source/features/custom-fact-extraction-prompt">
|
||||
<Card title="Custom Instructions" icon="wand-magic-sparkles" href="/open-source/features/custom-instructions">
|
||||
Tailor how facts are extracted from text.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -321,7 +321,7 @@ results = m.search(
|
||||
]
|
||||
},
|
||||
rerank=True,
|
||||
limit=20
|
||||
top_k=20
|
||||
)
|
||||
```
|
||||
|
||||
@@ -366,7 +366,7 @@ results = m.search(
|
||||
user_id="reader123",
|
||||
filters={"content_type": "book_recommendation"},
|
||||
rerank=True,
|
||||
limit=10
|
||||
top_k=10
|
||||
)
|
||||
|
||||
for result in results["results"]:
|
||||
|
||||
@@ -209,14 +209,6 @@ Mem0 offers granular configuration across vector stores, LLMs, embedders, and hi
|
||||
| `topP` | Probability threshold | All |
|
||||
| `topK` | Token count to keep | All |
|
||||
| `openaiBaseUrl` | Base URL override | OpenAI |
|
||||
</Accordion>
|
||||
<Accordion title="Graph store">
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `provider` | Graph store provider (e.g., `"neo4j"`) | `"neo4j"` |
|
||||
| `url` | Connection URL | `process.env.NEO4J_URL` |
|
||||
| `username` | Username | `process.env.NEO4J_USERNAME` |
|
||||
| `password` | Password | `process.env.NEO4J_PASSWORD` |
|
||||
</Accordion>
|
||||
<Accordion title="Embedder">
|
||||
| Parameter | Description | Default |
|
||||
@@ -230,7 +222,7 @@ Mem0 offers granular configuration across vector stores, LLMs, embedders, and hi
|
||||
| --- | --- | --- |
|
||||
| `historyDbPath` | Path to history database | `"{mem0_dir}/history.db"` |
|
||||
| `version` | API version | `"v1.0"` |
|
||||
| `customPrompt` | Custom processing prompt | `undefined` |
|
||||
| `customInstructions` | Custom processing prompt | `undefined` |
|
||||
</Accordion>
|
||||
<Accordion title="History store">
|
||||
| Parameter | Description | Default |
|
||||
@@ -273,7 +265,7 @@ const config = {
|
||||
}
|
||||
},
|
||||
disableHistory: false,
|
||||
customPrompt: "I'm a virtual assistant. I'm here to help you with your queries."
|
||||
customInstructions: "I'm a virtual assistant. I'm here to help you with your queries."
|
||||
};
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
+26
-26
@@ -183,7 +183,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\nusers = client.users()\nprint(users)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\nusers = client.users()\nprint(users)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -717,7 +717,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\njson_schema = {pydantic_json_schema}\nfilters = {\n \"AND\": [\n {\"user_id\": \"alex\"}\n ]\n}\n\nresponse = client.create_memory_export(\n schema=json_schema,\n filters=filters\n)\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\njson_schema = {pydantic_json_schema}\nfilters = {\n \"AND\": [\n {\"user_id\": \"alex\"}\n ]\n}\n\nresponse = client.create_memory_export(\n schema=json_schema,\n filters=filters\n)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -845,7 +845,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"project_id\")\n\nmemory_export_id = \"<memory_export_id>\"\n\nresponse = client.get_memory_export(memory_export_id=memory_export_id)\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmemory_export_id = \"<memory_export_id>\"\n\nresponse = client.get_memory_export(memory_export_id=memory_export_id)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -1096,7 +1096,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Retrieve memories for a specific user\nuser_memories = client.get_all(user_id=\"<user_id>\")\n\nprint(user_memories)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Retrieve memories for a specific user\nuser_memories = client.get_all(user_id=\"<user_id>\")\n\nprint(user_memories)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -1203,11 +1203,11 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmessages = [\n {\"role\": \"user\", \"content\": \"<user-message>\"},\n {\"role\": \"assistant\", \"content\": \"<assistant-response>\"}\n]\n\nclient.add(messages, user_id=\"<user-id>\", version=\"v2\")"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmessages = [\n {\"role\": \"user\", \"content\": \"<user-message>\"},\n {\"role\": \"assistant\", \"content\": \"<assistant-response>\"}\n]\n\nclient.add(messages, user_id=\"<user-id>\")"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst messages = [\n { role: \"user\", content: \"Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts.\" },\n { role: \"assistant\", content: \"Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions.\" }\n];\n\nclient.add(messages, { user_id: \"<user_id>\", version: \"v2\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst messages = [\n { role: \"user\", content: \"Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts.\" },\n { role: \"assistant\", content: \"Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions.\" }\n];\n\nclient.add(messages, { user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
@@ -1232,7 +1232,7 @@
|
||||
"tags": [
|
||||
"memories"
|
||||
],
|
||||
"description": "Delete memories by filter. At least one filter is required — previously omitting all filters silently deleted everything; now it returns a validation error.",
|
||||
"description": "Delete memories by filter. At least one filter is required \u2014 previously omitting all filters silently deleted everything; now it returns a validation error.",
|
||||
"operationId": "memories_delete",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1315,15 +1315,15 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Delete all memories for a specific user\nclient.delete_all(user_id=\"<user_id>\")\n\n# Delete all memories for every user in the project (wildcard)\nclient.delete_all(user_id=\"*\")\n\n# Full project wipe — all four filters must be explicitly set to \"*\"\nclient.delete_all(user_id=\"*\", agent_id=\"*\", app_id=\"*\", run_id=\"*\")\n\n# NOTE: Calling delete_all() with no filters raises a validation error.\n# At least one filter is required to prevent accidental data loss."
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Delete all memories for a specific user\nclient.delete_all(user_id=\"<user_id>\")\n\n# Delete all memories for every user in the project (wildcard)\nclient.delete_all(user_id=\"*\")\n\n# Full project wipe \u2014 all four filters must be explicitly set to \"*\"\nclient.delete_all(user_id=\"*\", agent_id=\"*\", app_id=\"*\", run_id=\"*\")\n\n# NOTE: Calling delete_all() with no filters raises a validation error.\n# At least one filter is required to prevent accidental data loss."
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Delete all memories for a specific user\nclient.deleteAll({ user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Delete all memories for every user in the project (wildcard)\nclient.deleteAll({ user_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Full project wipe — all four filters must be explicitly set to \"*\"\nclient.deleteAll({ user_id: \"*\", agent_id: \"*\", app_id: \"*\", run_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Delete all memories for a specific user\nclient.deleteAll({ user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Delete all memories for every user in the project (wildcard)\nclient.deleteAll({ user_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Full project wipe \u2014 all four filters must be explicitly set to \"*\"\nclient.deleteAll({ user_id: \"*\", agent_id: \"*\", app_id: \"*\", run_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "# Delete memories for a specific user\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=<user_id>' \\\n --header 'Authorization: Token <api-key>'\n\n# Delete memories for all users (wildcard)\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*' \\\n --header 'Authorization: Token <api-key>'\n\n# Full project wipe — all four filters must be set to *\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*&agent_id=*&app_id=*&run_id=*' \\\n --header 'Authorization: Token <api-key>'"
|
||||
"source": "# Delete memories for a specific user\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=<user_id>' \\\n --header 'Authorization: Token <api-key>'\n\n# Delete memories for all users (wildcard)\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*' \\\n --header 'Authorization: Token <api-key>'\n\n# Full project wipe \u2014 all four filters must be set to *\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*&agent_id=*&app_id=*&run_id=*' \\\n --header 'Authorization: Token <api-key>'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
@@ -1441,11 +1441,11 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Retrieve memories with filters\nmemories = client.get_all(\n filters={\n \"AND\": [\n {\n \"user_id\": \"alex\"\n },\n {\n \"created_at\": {\n \"gte\": \"2024-07-01\",\n \"lte\": \"2024-07-31\"\n }\n }\n ]\n },\n version=\"v2\"\n)\n\nprint(memories)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Retrieve memories with filters\nmemories = client.get_all(\n filters={\n \"AND\": [\n {\n \"user_id\": \"alex\"\n },\n {\n \"created_at\": {\n \"gte\": \"2024-07-01\",\n \"lte\": \"2024-07-31\"\n }\n }\n ]\n }\n)\n\nprint(memories)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst filters = {\n AND: [\n { user_id: 'alex' },\n { created_at: { gte: '2024-07-01', lte: '2024-07-31' } }\n ]\n};\n\nclient.getAll({ filters, api_version: 'v2' })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst filters = {\n AND: [\n { user_id: 'alex' },\n { created_at: { gte: '2024-07-01', lte: '2024-07-31' } }\n ]\n};\n\nclient.getAll({ filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
@@ -1589,11 +1589,11 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nquery = \"Your search query here\"\n\nresults = client.search(query, user_id=\"<user_id>\", output_format=\"v1.1\")\nprint(results)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nquery = \"Your search query here\"\n\nresults = client.search(query, user_id=\"<user_id>\")\nprint(results)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"Your search query here\";\n\nclient.search(query, { user_id: \"<user_id>\", output_format: \"v1.1\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"Your search query here\";\n\nclient.search(query, { user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
@@ -1708,11 +1708,11 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nquery = \"What do you know about me?\"\nfilters = {\n \"OR\":[\n {\n \"user_id\":\"alex\"\n },\n {\n \"agent_id\":{\n \"in\":[\n \"travel-assistant\",\n \"customer-support\"\n ]\n }\n }\n ]\n}\nclient.search(query, version=\"v2\", filters=filters)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nquery = \"What do you know about me?\"\nfilters = {\n \"OR\":[\n {\n \"user_id\":\"alex\"\n },\n {\n \"agent_id\":{\n \"in\":[\n \"travel-assistant\",\n \"customer-support\"\n ]\n }\n }\n ]\n}\nclient.search(query, filters=filters)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"What do you know about me?\";\nconst filters = {\n OR: [\n { user_id: \"alex\" },\n { agent_id: { in: [\"travel-assistant\", \"customer-support\"] } }\n ]\n};\n\nclient.search(query, { api_version: \"v2\", filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"What do you know about me?\";\nconst filters = {\n OR: [\n { user_id: \"alex\" },\n { agent_id: { in: [\"travel-assistant\", \"customer-support\"] } }\n ]\n};\n\nclient.search(query, { filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
@@ -1864,7 +1864,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmemory = client.get(memory_id=\"<memory_id>\")"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmemory = client.get(memory_id=\"<memory_id>\")"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -1989,7 +1989,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Update a memory\nmemory_id = \"<memory_id>\"\nclient.update(\n memory_id=memory_id,\n text=\"Your updated memory message here\",\n metadata={\"category\": \"example\"}\n)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Update a memory\nmemory_id = \"<memory_id>\"\nclient.update(\n memory_id=memory_id,\n text=\"Your updated memory message here\",\n metadata={\"category\": \"example\"}\n)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -2053,7 +2053,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmemory_id = \"<memory_id>\"\nclient.delete(memory_id=memory_id)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmemory_id = \"<memory_id>\"\nclient.delete(memory_id=memory_id)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -2199,7 +2199,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Add some message to create history\nmessages = [{\"role\": \"user\", \"content\": \"<user-message>\"}]\nclient.add(messages, user_id=\"<user-id>\")\n\n# Add second message to update history\nmessages.append({\"role\": \"user\", \"content\": \"<user-message>\"})\nclient.add(messages, user_id=\"<user-id>\")\n\n# Get history of how memory changed over time\nmemory_id = \"<memory-id-here>\"\nhistory = client.history(memory_id)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Add some message to create history\nmessages = [{\"role\": \"user\", \"content\": \"<user-message>\"}]\nclient.add(messages, user_id=\"<user-id>\")\n\n# Add second message to update history\nmessages.append({\"role\": \"user\", \"content\": \"<user-message>\"})\nclient.add(messages, user_id=\"<user-id>\")\n\n# Get history of how memory changed over time\nmemory_id = \"<memory-id-here>\"\nhistory = client.history(memory_id)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -3608,7 +3608,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nresponse = client.get_project()\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\nresponse = client.get_project()\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -4411,7 +4411,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nupdate_memories = [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\",\n \"text\": \"Watches football\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\",\n \"text\": \"Likes to travel\"\n }\n]\n\nresponse = client.batch_update(update_memories)\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nupdate_memories = [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\",\n \"text\": \"Watches football\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\",\n \"text\": \"Likes to travel\"\n }\n]\n\nresponse = client.batch_update(update_memories)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -4490,7 +4490,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\ndelete_memories = [\n {\"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\"},\n {\"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\"}\n]\n\nresponse = client.batch_delete(delete_memories)\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\ndelete_memories = [\n {\"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\"},\n {\"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\"}\n]\n\nresponse = client.batch_delete(delete_memories)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -4758,7 +4758,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -4998,7 +4998,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Delete a webhook\nresponse = client.delete_webhook(webhook_id=\"your_webhook_id\")\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Delete a webhook\nresponse = client.delete_webhook(webhook_id=\"your_webhook_id\")\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -5742,4 +5742,4 @@
|
||||
}
|
||||
},
|
||||
"x-original-swagger-version": "2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ result = await memory.add(
|
||||
conversation,
|
||||
user_id="traveler-42",
|
||||
metadata={"trip": "japan-2025", "preferences": ["boutique", "no-shellfish"]},
|
||||
enable_graph=True,
|
||||
run_id="planning-call-1",
|
||||
)
|
||||
```
|
||||
@@ -101,7 +100,6 @@ const conversation = [
|
||||
const result = await memory.add(conversation, {
|
||||
userId: "traveler-42",
|
||||
metadata: { trip: "japan-2025", preferences: ["boutique", "no-shellfish"] },
|
||||
enableGraph: true,
|
||||
runId: "planning-call-1",
|
||||
});
|
||||
```
|
||||
@@ -163,10 +161,6 @@ await memory.update(matches.results[0].id, {
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Tip>
|
||||
Need to pause graph writes on a per-request basis? Pass `enableGraph: false` (TypeScript) or `enable_graph=False` (Python) when latency matters more than relationship building.
|
||||
</Tip>
|
||||
|
||||
## Clean up
|
||||
|
||||
<Tabs>
|
||||
@@ -192,8 +186,6 @@ await memory.deleteAll({ userId: "traveler-42", runId: "planning-call-1" });
|
||||
|
||||
## Quick recovery
|
||||
|
||||
- `Missing required key enableGraph`: update the SDK to `mem0ai>=0.4.0`.
|
||||
- `Graph backend unavailable`: retry with `enableGraph=False` and inspect your graph provider status.
|
||||
- Empty results with filters: log `filters` values and confirm metadata keys match (case-sensitive).
|
||||
|
||||
<Warning>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Advanced Retrieval
|
||||
description: "Advanced memory search with keyword expansion, intelligent reranking, and precision filtering"
|
||||
description: "Advanced memory search with intelligent reranking for precise results"
|
||||
---
|
||||
|
||||
## What is Advanced Retrieval?
|
||||
@@ -9,40 +9,6 @@ Advanced Retrieval gives you precise control over how memories are found and ran
|
||||
|
||||
## Search Enhancement Options
|
||||
|
||||
### Keyword Search
|
||||
|
||||
Expands results to include memories with specific terms, names, and technical keywords.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="When to Use">
|
||||
- Searching for specific entities, names, or technical terms
|
||||
- Need comprehensive coverage of a topic
|
||||
- Want broader recall even if some results are less relevant
|
||||
- Working with domain-specific terminology
|
||||
</Tab>
|
||||
<Tab title="How it Works">
|
||||
```python Python
|
||||
# Find memories containing specific food-related terms
|
||||
results = client.search(
|
||||
query="What foods should I avoid?",
|
||||
keyword_search=True,
|
||||
user_id="user123"
|
||||
)
|
||||
|
||||
# Results might include:
|
||||
# ✓ "Allergic to peanuts and shellfish"
|
||||
# ✓ "Lactose intolerant - avoid dairy"
|
||||
# ✓ "Mentioned avoiding gluten last week"
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Performance">
|
||||
- **Latency**: ~10ms additional
|
||||
- **Recall**: Significantly increased
|
||||
- **Precision**: Slightly decreased
|
||||
- **Best for**: Entity search, comprehensive coverage
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Reranking
|
||||
|
||||
Reorders results using deep semantic understanding to put the most relevant memories first.
|
||||
@@ -77,41 +43,6 @@ results = client.search(
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Memory Filtering
|
||||
|
||||
Filters results to keep only the most precisely relevant memories.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="When to Use">
|
||||
- Need highly specific, focused results
|
||||
- Working with large datasets where noise is problematic
|
||||
- Quality over quantity is essential
|
||||
- Building production or safety-critical applications
|
||||
</Tab>
|
||||
<Tab title="How it Works">
|
||||
```python Python
|
||||
# Get only the most relevant dietary restrictions
|
||||
results = client.search(
|
||||
query="What are my dietary restrictions?",
|
||||
filter_memories=True,
|
||||
user_id="user123"
|
||||
)
|
||||
|
||||
# Before filtering: After filtering:
|
||||
# • "Allergic to nuts" → • "Allergic to nuts"
|
||||
# • "Likes Italian food" → • "Vegetarian diet"
|
||||
# • "Vegetarian diet" →
|
||||
# • "Eats dinner at 7pm" →
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Performance">
|
||||
- **Latency**: 200-300ms additional
|
||||
- **Precision**: Maximized
|
||||
- **Recall**: May be reduced
|
||||
- **Best for**: Focused queries, production systems
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Real-World Use Cases
|
||||
|
||||
<Tabs>
|
||||
@@ -120,7 +51,6 @@ results = client.search(
|
||||
# Smart home assistant finding device preferences
|
||||
results = client.search(
|
||||
query="How do I like my bedroom temperature?",
|
||||
keyword_search=True, # Find specific temperature mentions
|
||||
rerank=True, # Get most recent preferences first
|
||||
user_id="user123"
|
||||
)
|
||||
@@ -133,8 +63,6 @@ results = client.search(
|
||||
# Find specific product issues with high precision
|
||||
results = client.search(
|
||||
query="Problems with premium subscription billing",
|
||||
keyword_search=True, # Find "premium", "billing", "subscription"
|
||||
filter_memories=True, # Only billing-related issues
|
||||
user_id="customer456"
|
||||
)
|
||||
|
||||
@@ -147,11 +75,10 @@ results = client.search(
|
||||
results = client.search(
|
||||
query="Patient allergies and contraindications",
|
||||
rerank=True, # Most important info first
|
||||
filter_memories=True, # Only medical restrictions
|
||||
user_id="patient789"
|
||||
)
|
||||
|
||||
# Ensures critical allergy info appears first and filters out non-medical data
|
||||
# Ensures critical allergy info appears first
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Learning Platform">
|
||||
@@ -159,7 +86,6 @@ results = client.search(
|
||||
# Find learning progress for specific topics
|
||||
results = client.search(
|
||||
query="Python programming progress and difficulties",
|
||||
keyword_search=True, # Find "Python", "programming", specific concepts
|
||||
rerank=True, # Recent progress first
|
||||
user_id="student123"
|
||||
)
|
||||
@@ -169,63 +95,57 @@ results = client.search(
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Choosing the Right Combination
|
||||
## Choosing the Right Configuration
|
||||
|
||||
### Recommended Configurations
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# Fast and broad - good for exploration
|
||||
# Basic search - good for exploration
|
||||
def quick_search(query, user_id):
|
||||
return client.search(
|
||||
query=query,
|
||||
keyword_search=True,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
# Balanced - good for most applications
|
||||
# Reranked search - good for most applications
|
||||
def standard_search(query, user_id):
|
||||
return client.search(
|
||||
query=query,
|
||||
keyword_search=True,
|
||||
rerank=True,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
# High precision - good for critical applications
|
||||
# Reranked search - good for critical applications
|
||||
def precise_search(query, user_id):
|
||||
return client.search(
|
||||
query=query,
|
||||
rerank=True,
|
||||
filter_memories=True,
|
||||
user_id=user_id
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Fast and broad - good for exploration
|
||||
// Basic search - good for exploration
|
||||
function quickSearch(query, userId) {
|
||||
return client.search(query, {
|
||||
user_id: userId,
|
||||
keyword_search: true
|
||||
user_id: userId
|
||||
});
|
||||
}
|
||||
|
||||
// Balanced - good for most applications
|
||||
// Reranked search - good for most applications
|
||||
function standardSearch(query, userId) {
|
||||
return client.search(query, {
|
||||
user_id: userId,
|
||||
keyword_search: true,
|
||||
rerank: true
|
||||
});
|
||||
}
|
||||
|
||||
// High precision - good for critical applications
|
||||
// Reranked search - good for critical applications
|
||||
function preciseSearch(query, userId) {
|
||||
return client.search(query, {
|
||||
user_id: userId,
|
||||
rerank: true,
|
||||
filter_memories: true
|
||||
rerank: true
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -235,19 +155,15 @@ function preciseSearch(query, userId) {
|
||||
|
||||
### Do
|
||||
|
||||
- Start simple with just one enhancement and measure impact
|
||||
- Use keyword search for entity-heavy queries (names, places, technical terms)
|
||||
- Start simple with basic search and measure impact before enabling reranking
|
||||
- Use reranking when the top result quality matters most
|
||||
- Use filtering for production systems where precision is critical
|
||||
- Handle empty results gracefully when filtering is too aggressive
|
||||
- Monitor latency and adjust based on your application's needs
|
||||
- Handle empty results gracefully
|
||||
|
||||
### Don't
|
||||
|
||||
- Enable all options by default without measuring necessity
|
||||
- Use filtering for broad exploratory queries
|
||||
- Enable reranking by default without measuring necessity
|
||||
- Ignore latency impact in real-time applications
|
||||
- Forget to handle cases where filtering returns no results
|
||||
- Use advanced retrieval for simple, fast lookup scenarios
|
||||
|
||||
## Performance Guidelines
|
||||
@@ -261,20 +177,18 @@ import time
|
||||
start_time = time.time()
|
||||
results = client.search(
|
||||
query="user preferences",
|
||||
keyword_search=True, # +10ms
|
||||
rerank=True, # +150ms
|
||||
filter_memories=True, # +250ms
|
||||
user_id="user123"
|
||||
)
|
||||
latency = time.time() - start_time
|
||||
print(f"Search completed in {latency:.2f}s") # ~0.41s expected
|
||||
print(f"Search completed in {latency:.2f}s")
|
||||
```
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. **Cache frequent queries** to avoid repeated advanced processing
|
||||
2. **Use session-specific search** with `run_id` to reduce search space
|
||||
3. **Implement fallback logic** when filtering returns empty results
|
||||
3. **Implement fallback logic** when search returns empty results
|
||||
4. **Monitor and alert** on search latency patterns
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
|
||||
@@ -50,7 +50,7 @@ const messages = [
|
||||
{"role": "user", "content": "Alice loves playing badminton"},
|
||||
{"role": "assistant", "content": "That's great! Alice is a fitness freak"},
|
||||
];
|
||||
await client.add(messages, { user_id: "alice" });
|
||||
await client.add(messages, { userId: "alice" });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -66,7 +66,7 @@ await client.search("What is Alice's favorite sport?", user_id="alice")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
await client.search("What is Alice's favorite sport?", { user_id: "alice" });
|
||||
await client.search("What is Alice's favorite sport?", { userId: "alice" });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -118,7 +118,7 @@ await client.delete_all(user_id="alice")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
await client.deleteAll({ user_id: "alice" });
|
||||
await client.deleteAll({ userId: "alice" });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
---
|
||||
title: Async Mode Default Change
|
||||
description: "The async_mode parameter now defaults to true for all memory additions, changing from synchronous processing."
|
||||
---
|
||||
|
||||
<Note type="warning">
|
||||
**Important Change**
|
||||
|
||||
The `async_mode` parameter defaults to `true` for all memory additions, changing the default API behavior to asynchronous processing.
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
The Memory Addition API processes all memory additions asynchronously by default. This change improves performance and scalability by queuing memory operations in the background, allowing your application to continue without waiting for memory processing to complete.
|
||||
|
||||
## What's Changing
|
||||
|
||||
The parameter `async_mode` will default to `true` instead of `false`.
|
||||
|
||||
This means memory additions will be **processed asynchronously** by default - queued for background execution instead of waiting for processing to complete.
|
||||
|
||||
## Behavior Comparison
|
||||
|
||||
### Old Default Behavior (async_mode = false)
|
||||
|
||||
When `async_mode` was set to `false`, the API returned fully processed memory objects immediately:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "de0ee948-af6a-436c-835c-efb6705207de",
|
||||
"event": "ADD",
|
||||
"memory": "User Order #1234 was for a 'Nova 2000'",
|
||||
"structured_attributes": {
|
||||
"day": 13,
|
||||
"hour": 16,
|
||||
"year": 2025,
|
||||
"month": 10,
|
||||
"minute": 59,
|
||||
"quarter": 4,
|
||||
"is_weekend": false,
|
||||
"day_of_week": "monday",
|
||||
"day_of_year": 286,
|
||||
"week_of_year": 42
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### New Default Behavior (async_mode = true)
|
||||
|
||||
With `async_mode` defaulting to `true`, memory processing is queued in the background and the API returns immediately:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "d7b5282a-0031-4cc2-98ba-5a02d8531e17"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### If You Need Synchronous Processing
|
||||
|
||||
If your integration relies on receiving the processed memory object immediately, you can explicitly set `async_mode` to `false` in your requests:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-api-key")
|
||||
|
||||
# Explicitly set async_mode=False to preserve synchronous behavior
|
||||
messages = [
|
||||
{"role": "user", "content": "I ordered a Nova 2000"}
|
||||
]
|
||||
|
||||
result = client.add(
|
||||
messages,
|
||||
user_id="user-123",
|
||||
async_mode=False # This ensures synchronous processing
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
const { MemoryClient } = require('mem0ai');
|
||||
|
||||
const client = new MemoryClient({ apiKey: 'your-api-key' });
|
||||
|
||||
// Explicitly set async_mode: false to preserve synchronous behavior
|
||||
const messages = [
|
||||
{ role: "user", content: "I ordered a Nova 2000" }
|
||||
];
|
||||
|
||||
const result = await client.add(messages, {
|
||||
user_id: "user-123",
|
||||
async_mode: false // This ensures synchronous processing
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST https://api.mem0.ai/v1/memories/ \
|
||||
-H "Authorization: Token your-api-key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"messages": [
|
||||
{"role": "user", "content": "I ordered a Nova 2000"}
|
||||
],
|
||||
"user_id": "user-123",
|
||||
"async_mode": false
|
||||
}'
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### If You Want to Adopt Asynchronous Processing
|
||||
|
||||
If you want to benefit from the improved performance of asynchronous processing:
|
||||
|
||||
1. **Remove** any explicit `async_mode=False` parameters from your code
|
||||
2. **Use webhooks** to receive notifications when memory processing completes
|
||||
|
||||
<Note>
|
||||
Learn more about [Webhooks](/platform/features/webhooks) for real-time notifications about memory events.
|
||||
</Note>
|
||||
|
||||
## Benefits of Asynchronous Processing
|
||||
|
||||
Switching to asynchronous processing provides several advantages:
|
||||
|
||||
- **Faster API Response Times**: Your application doesn't wait for memory processing
|
||||
- **Better Scalability**: Handle more memory additions concurrently
|
||||
- **Improved User Experience**: Reduced latency in your application
|
||||
- **Resource Efficiency**: Background processing optimizes server resources
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The default behavior is now `async_mode=true` for asynchronous processing
|
||||
- Explicitly set `async_mode=false` if you need synchronous behavior
|
||||
- Use webhooks to receive notifications when memories are processed
|
||||
|
||||
## Monitoring Memory Processing
|
||||
|
||||
When using asynchronous mode, use webhooks to receive notifications about memory events:
|
||||
|
||||
<Card title="Configure Webhooks" icon="webhook" href="/platform/features/webhooks">
|
||||
Learn how to set up webhooks for memory processing events
|
||||
</Card>
|
||||
|
||||
You can also retrieve all processed memories at any time:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Retrieve all memories for a user
|
||||
# Note: get_all now requires filters
|
||||
memories = client.get_all(filters={"AND": [{"user_id": "user-123"}]})
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Retrieve all memories for a user
|
||||
// Note: getAll now requires filters
|
||||
const memories = await client.getAll({ filters: {"AND": [{"user_id": "user-123"}]} });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you have questions about this change or need assistance updating your integration:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
|
||||
## Related Documentation
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Async Client" icon="bolt" href="/platform/features/async-client">
|
||||
Learn about the asynchronous client for Mem0
|
||||
</Card>
|
||||
<Card title="Add Memories API" icon="plus" href="/api-reference/memory/add-memories">
|
||||
View the complete API reference for adding memories
|
||||
</Card>
|
||||
<Card title="Webhooks" icon="webhook" href="/platform/features/webhooks">
|
||||
Configure webhooks for memory processing events
|
||||
</Card>
|
||||
<Card title="Memory Operations" icon="gear" href="/core-concepts/memory-operations/add">
|
||||
Understand memory addition operations
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -25,7 +25,7 @@ const messages = [
|
||||
{"role": "assistant", "content": "Great! I'll remember your preference for Italian cuisine."}
|
||||
];
|
||||
|
||||
await client.add(messages, { user_id: "user123", version: "v2" });
|
||||
await client.add(messages, { userId: "user123", version: "v2" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -65,14 +65,14 @@ const messages1 = [
|
||||
{"role": "user", "content": "Hi, I'm Sarah from New York"},
|
||||
{"role": "assistant", "content": "Hello Sarah! Nice to meet you."}
|
||||
];
|
||||
await client.add(messages1, { user_id: "sarah", version: "v2" });
|
||||
await client.add(messages1, { userId: "sarah", version: "v2" });
|
||||
|
||||
// Later interaction - just send new messages
|
||||
const messages2 = [
|
||||
{"role": "user", "content": "I'm planning a trip to Italy next month"},
|
||||
{"role": "assistant", "content": "How exciting! Italy is beautiful this time of year."}
|
||||
];
|
||||
await client.add(messages2, { user_id: "sarah", version: "v2" });
|
||||
await client.add(messages2, { userId: "sarah", version: "v2" });
|
||||
// Mem0 automatically knows Sarah is from New York and can use this context
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -104,7 +104,7 @@ const messages = [
|
||||
{"role": "assistant", "content": "I've noted your allergies for future reference."}
|
||||
];
|
||||
|
||||
await client.add(messages, { user_id: "user123", version: "v2" });
|
||||
await client.add(messages, { userId: "user123", version: "v2" });
|
||||
// This allergy info will be available in ALL future interactions
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -143,21 +143,21 @@ const messages1 = [
|
||||
{"role": "user", "content": "I want to plan a 5-day trip to Tokyo"},
|
||||
{"role": "assistant", "content": "Perfect! Let's plan your Tokyo adventure."}
|
||||
];
|
||||
await client.add(messages1, { user_id: "user123", run_id: "tokyo-trip-2024", version: "v2" });
|
||||
await client.add(messages1, { userId: "user123", runId: "tokyo-trip-2024", version: "v2" });
|
||||
|
||||
// Later in the same trip planning session
|
||||
const messages2 = [
|
||||
{"role": "user", "content": "I prefer staying near Shibuya"},
|
||||
{"role": "assistant", "content": "Great choice! Shibuya is very convenient."}
|
||||
];
|
||||
await client.add(messages2, { user_id: "user123", run_id: "tokyo-trip-2024", version: "v2" });
|
||||
await client.add(messages2, { userId: "user123", runId: "tokyo-trip-2024", version: "v2" });
|
||||
|
||||
// Different session for work project (separate context)
|
||||
const workMessages = [
|
||||
{"role": "user", "content": "Let's discuss the Q4 marketing strategy"},
|
||||
{"role": "assistant", "content": "Sure! What are your main goals for Q4?"}
|
||||
];
|
||||
await client.add(workMessages, { user_id: "user123", run_id: "q4-marketing", version: "v2" });
|
||||
await client.add(workMessages, { userId: "user123", runId: "q4-marketing", version: "v2" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -38,11 +38,7 @@ Before defining any criteria, make sure to initialize the `MemoryClient` with yo
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(
|
||||
api_key="your_mem0_api_key",
|
||||
org_id="your_organization_id",
|
||||
project_id="your_project_id"
|
||||
)
|
||||
client = MemoryClient(api_key="your_mem0_api_key")
|
||||
```
|
||||
|
||||
### Define Your Criteria
|
||||
|
||||
@@ -98,7 +98,7 @@ messages = [
|
||||
]
|
||||
|
||||
# Add memories with project-level custom categories
|
||||
client.add(messages, user_id="alice", async_mode=False)
|
||||
client.add(messages, user_id="alice")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -187,7 +187,7 @@ messages = [
|
||||
]
|
||||
|
||||
# Add memories with default categories
|
||||
client.add(messages, user_id='alice', async_mode=False)
|
||||
client.add(messages, user_id='alice')
|
||||
```
|
||||
|
||||
```python Memories with categories
|
||||
|
||||
@@ -33,7 +33,7 @@ Extract only health and wellness information:
|
||||
Exclude: Personal identifiers, financial data
|
||||
`;
|
||||
|
||||
await client.project.update({ custom_instructions: prompt });
|
||||
await client.project.update({ customInstructions: prompt });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -60,11 +60,11 @@ print(response["custom_instructions"])
|
||||
|
||||
```javascript JavaScript
|
||||
// Set instructions for your project
|
||||
await client.project.update({ custom_instructions: "Your guidelines here..." });
|
||||
await client.project.update({ customInstructions: "Your guidelines here..." });
|
||||
|
||||
// Retrieve current instructions
|
||||
const response = await client.project.get({ fields: ["custom_instructions"] });
|
||||
console.log(response.custom_instructions);
|
||||
const response = await client.project.get({ fields: ["customInstructions"] });
|
||||
console.log(response.customInstructions);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -145,7 +145,7 @@ Extract customer service information for better support:
|
||||
Exclude: Payment card numbers, passwords, personal identifiers.
|
||||
`;
|
||||
|
||||
await client.project.update({ custom_instructions: instructions });
|
||||
await client.project.update({ customInstructions: instructions });
|
||||
```
|
||||
</CodeGroup>
|
||||
</Tab>
|
||||
@@ -198,7 +198,7 @@ Extract learning-related information for personalized education:
|
||||
Exclude: Specific grades, personal identifiers, financial information.
|
||||
`;
|
||||
|
||||
await client.project.update({ custom_instructions: educationPrompt });
|
||||
await client.project.update({ customInstructions: educationPrompt });
|
||||
```
|
||||
</CodeGroup>
|
||||
</Tab>
|
||||
@@ -251,7 +251,7 @@ Extract financial planning information for advisory services:
|
||||
Exclude: Account numbers, SSNs, passwords, specific financial amounts.
|
||||
`;
|
||||
|
||||
await client.project.update({ custom_instructions: financePrompt });
|
||||
await client.project.update({ customInstructions: financePrompt });
|
||||
```
|
||||
</CodeGroup>
|
||||
</Tab>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
title: Expiration Date
|
||||
description: 'Set time-bound memories in Mem0 with automatic expiration dates to manage temporal information effectively.'
|
||||
---
|
||||
|
||||
## Benefits of Memory Expiration
|
||||
|
||||
Setting expiration dates for memories offers several advantages:
|
||||
|
||||
- **Time-Sensitive Information Management**: Handle information that is only relevant for a specific time period.
|
||||
- **Event-Based Memory**: Manage information related to upcoming events that becomes irrelevant after the event passes.
|
||||
|
||||
These benefits enable more sophisticated memory management for applications where temporal context matters.
|
||||
|
||||
## Setting Memory Expiration Date
|
||||
|
||||
You can set an expiration date for memories, after which they will no longer be retrieved in searches. This is useful for creating temporary memories or memories that are relevant only for a specific time period.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
import datetime
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-api-key")
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I'll be in San Francisco until the end of this month."
|
||||
}
|
||||
]
|
||||
|
||||
# Set an expiration date for this memory
|
||||
client.add(messages=messages, user_id="alex", expiration_date=str(datetime.datetime.now().date() + datetime.timedelta(days=30)))
|
||||
|
||||
# You can also use an explicit date string
|
||||
client.add(messages=messages, user_id="alex", expiration_date="2023-08-31")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import MemoryClient from 'mem0ai';
|
||||
const client = new MemoryClient({ apiKey: 'your-api-key' });
|
||||
|
||||
const messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I'll be in San Francisco until the end of this month."
|
||||
}
|
||||
];
|
||||
|
||||
// Set an expiration date 30 days from now
|
||||
const expirationDate = new Date();
|
||||
expirationDate.setDate(expirationDate.getDate() + 30);
|
||||
client.add(messages, {
|
||||
user_id: "alex",
|
||||
expiration_date: expirationDate.toISOString().split('T')[0]
|
||||
})
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// You can also use an explicit date string
|
||||
client.add(messages, {
|
||||
user_id: "alex",
|
||||
expiration_date: "2023-08-31"
|
||||
})
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
-H "Authorization: Token your-api-key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I'll be in San Francisco until the end of this month."
|
||||
}
|
||||
],
|
||||
"user_id": "alex",
|
||||
"expiration_date": "2023-08-31"
|
||||
}'
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-4g7h-8i9j-k0l1m2n3o4p5",
|
||||
"data": {
|
||||
"memory": "In San Francisco until the end of this month"
|
||||
},
|
||||
"event": "ADD"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
Once a memory reaches its expiration date, it will not be included in search or get results, though the data remains stored in the system.
|
||||
</Note>
|
||||
|
||||
If you have any questions, please feel free to reach out to us using one of the following methods:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
@@ -1,349 +0,0 @@
|
||||
---
|
||||
title: Graph Memory
|
||||
description: "Enable graph-based memory retrieval for more contextually relevant results"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Graph Memory enhances the memory pipeline by creating relationships between entities in your data. It builds a network of interconnected information for more contextually relevant search results.
|
||||
|
||||
This feature allows your AI applications to understand connections between entities, providing richer context for responses. It's ideal for applications needing relationship tracking and nuanced information retrieval across related memories.
|
||||
|
||||
## How Graph Memory Works
|
||||
|
||||
The Graph Memory feature analyzes how each entity connects and relates to each other. When enabled:
|
||||
|
||||
1. Mem0 automatically builds a graph representation of entities
|
||||
2. Vector search returns the top semantic matches (with any reranker you configure)
|
||||
3. Graph relations are returned alongside those results to provide additional context—they do not reorder the vector hits
|
||||
|
||||
## Using Graph Memory
|
||||
|
||||
To use Graph Memory, you need to enable it in your API calls by setting the `enable_graph=True` parameter.
|
||||
|
||||
### Adding Memories with Graph Memory
|
||||
|
||||
When adding new memories, enable Graph Memory to automatically build relationships with existing memories:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(
|
||||
api_key="your-api-key",
|
||||
org_id="your-org-id",
|
||||
project_id="your-project-id"
|
||||
)
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "My name is Joseph"},
|
||||
{"role": "assistant", "content": "Hello Joseph, it's nice to meet you!"},
|
||||
{"role": "user", "content": "I'm from Seattle and I work as a software engineer"}
|
||||
]
|
||||
|
||||
# Enable graph memory when adding
|
||||
client.add(
|
||||
messages,
|
||||
user_id="joseph",
|
||||
enable_graph=True
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import { MemoryClient } from "mem0";
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: "your-api-key",
|
||||
org_id: "your-org-id",
|
||||
project_id: "your-project-id"
|
||||
});
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "My name is Joseph" },
|
||||
{ role: "assistant", content: "Hello Joseph, it's nice to meet you!" },
|
||||
{ role: "user", content: "I'm from Seattle and I work as a software engineer" }
|
||||
];
|
||||
|
||||
// Enable graph memory when adding
|
||||
await client.add({
|
||||
messages,
|
||||
user_id: "joseph",
|
||||
enable_graph: true
|
||||
});
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"memory": "Name is Joseph",
|
||||
"event": "ADD",
|
||||
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438"
|
||||
},
|
||||
{
|
||||
"memory": "Is from Seattle",
|
||||
"event": "ADD",
|
||||
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d"
|
||||
},
|
||||
{
|
||||
"memory": "Is a software engineer",
|
||||
"event": "ADD",
|
||||
"id": "5f0a184e-ddea-4fe6-9b92-692d6a901df8"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The graph memory would look like this:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/graph-platform.png" alt="Graph Memory Visualization showing relationships between entities" />
|
||||
</Frame>
|
||||
|
||||
<Caption>Graph Memory creates a network of relationships between entities, enabling more contextual retrieval</Caption>
|
||||
|
||||
|
||||
<Note>
|
||||
Response for the graph memory's `add` operation will not be available directly in the response. As adding graph memories is an asynchronous operation due to heavy processing, you can use the `get_all()` endpoint to retrieve the memory with the graph metadata.
|
||||
</Note>
|
||||
|
||||
|
||||
### Searching with Graph Memory
|
||||
|
||||
When searching memories, Graph Memory helps retrieve entities that are contextually important even if they're not direct semantic matches.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Search with graph memory enabled
|
||||
results = client.search(
|
||||
"what is my name?",
|
||||
user_id="joseph",
|
||||
enable_graph=True
|
||||
)
|
||||
|
||||
print(results)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Search with graph memory enabled
|
||||
const results = await client.search({
|
||||
query: "what is my name?",
|
||||
user_id: "joseph",
|
||||
enable_graph: true
|
||||
});
|
||||
|
||||
console.log(results);
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438",
|
||||
"memory": "Name is Joseph",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.146390-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.146404-07:00",
|
||||
"score": 0.3621795393335552
|
||||
},
|
||||
{
|
||||
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d",
|
||||
"memory": "Is from Seattle",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.170680-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.170692-07:00",
|
||||
"score": 0.31212713194651254
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "name",
|
||||
"target": "joseph",
|
||||
"target_type": "person",
|
||||
"score": 0.39
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
`results` always reflects the vector search order (optionally reranked). Graph Memory augments that response by adding related entities in the `relations` array; it does not re-rank the vector results automatically.
|
||||
</Note>
|
||||
|
||||
### Retrieving All Memories with Graph Memory
|
||||
|
||||
When retrieving all memories, Graph Memory provides additional relationship context:
|
||||
|
||||
<Callout type="warning" title="Filters Required">
|
||||
`get_all()` now requires filters to be specified.
|
||||
</Callout>
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Get all memories with graph context
|
||||
memories = client.get_all(
|
||||
filters={"AND": [{"user_id": "joseph"}]},
|
||||
enable_graph=True
|
||||
)
|
||||
|
||||
print(memories)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Get all memories with graph context
|
||||
const memories = await client.getAll({
|
||||
filters: {"AND": [{"user_id": "joseph"}]},
|
||||
enable_graph: true
|
||||
});
|
||||
|
||||
console.log(memories);
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "5f0a184e-ddea-4fe6-9b92-692d6a901df8",
|
||||
"memory": "Is a software engineer",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["professional_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.194116-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.194128-07:00",
|
||||
},
|
||||
{
|
||||
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d",
|
||||
"memory": "Is from Seattle",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.170680-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.170692-07:00",
|
||||
},
|
||||
{
|
||||
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438",
|
||||
"memory": "Name is Joseph",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.146390-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.146404-07:00",
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "name",
|
||||
"target": "joseph",
|
||||
"target_type": "person"
|
||||
},
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "city",
|
||||
"target": "seattle",
|
||||
"target_type": "city"
|
||||
},
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "job",
|
||||
"target": "software engineer",
|
||||
"target_type": "job"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Setting Graph Memory at Project Level
|
||||
|
||||
Instead of passing `enable_graph=True` to every add call, you can enable it once at the project level:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(
|
||||
api_key="your-api-key",
|
||||
org_id="your-org-id",
|
||||
project_id="your-project-id"
|
||||
)
|
||||
|
||||
# Enable graph memory for all operations in this project
|
||||
client.project.update(enable_graph=True)
|
||||
|
||||
# Now all add operations will use graph memory by default
|
||||
messages = [
|
||||
{"role": "user", "content": "My name is Joseph"},
|
||||
{"role": "assistant", "content": "Hello Joseph, it's nice to meet you!"},
|
||||
{"role": "user", "content": "I'm from Seattle and I work as a software engineer"}
|
||||
]
|
||||
|
||||
client.add(
|
||||
messages,
|
||||
user_id="joseph"
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import { MemoryClient } from "mem0";
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: "your-api-key",
|
||||
org_id: "your-org-id",
|
||||
project_id: "your-project-id"
|
||||
});
|
||||
|
||||
// Enable graph memory for all operations in this project
|
||||
await client.project.update({ enable_graph: true });
|
||||
|
||||
// Now all add operations will use graph memory by default
|
||||
const messages = [
|
||||
{ role: "user", content: "My name is Joseph" },
|
||||
{ role: "assistant", content: "Hello Joseph, it's nice to meet you!" },
|
||||
{ role: "user", content: "I'm from Seattle and I work as a software engineer" }
|
||||
];
|
||||
|
||||
await client.add({
|
||||
messages,
|
||||
user_id: "joseph"
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Enable Graph Memory for applications where understanding context and relationships between memories is important.
|
||||
- Graph Memory works best with a rich history of related conversations.
|
||||
- Consider Graph Memory for long-running assistants that need to track evolving information.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
Graph Memory requires additional processing and may increase response times slightly for very large memory stores. However, for most use cases, the improved retrieval quality outweighs the minimal performance impact.
|
||||
|
||||
If you have any questions, please feel free to reach out to us using one of the following methods:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
@@ -206,5 +206,5 @@ config = {"graph_store": {"threshold": 0.7}} # Valid: 0.0 ≤ x ≤ 1.0
|
||||
|
||||
## Related
|
||||
|
||||
- [Graph Memory](/platform/features/graph-memory)
|
||||
- [Graph Memory](/open-source/features/graph-memory)
|
||||
- [Issue #3590](https://github.com/mem0ai/mem0/issues/3590)
|
||||
|
||||
@@ -217,17 +217,16 @@ print(search_response)
|
||||
|
||||
## Async Mode Support
|
||||
|
||||
Group chat also supports async processing for improved performance:
|
||||
Group chat supports async processing for improved performance. Memory additions are processed asynchronously by default.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Group chat with async mode
|
||||
# Group chat — async processing is the default
|
||||
response = client.add(
|
||||
messages,
|
||||
run_id="groupchat_async",
|
||||
infer=True,
|
||||
async_mode=True
|
||||
)
|
||||
print(response)
|
||||
```
|
||||
@@ -268,7 +267,7 @@ Each message in a group chat must include:
|
||||
|
||||
4. **Memory Filtering**: Use filters to retrieve memories from specific participants or sessions when needed.
|
||||
|
||||
5. **Async Processing**: Use `async_mode=True` for large group conversations to improve performance.
|
||||
5. **Async Processing**: Memory additions are processed asynchronously by default, which is ideal for large group conversations.
|
||||
|
||||
6. **Search Context**: Leverage the search functionality to find specific information within group chat contexts.
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ const filters = {
|
||||
const responseWithInstructions = await client.createMemoryExport({
|
||||
schema: json_schema,
|
||||
filters: filters,
|
||||
export_instructions: export_instructions
|
||||
exportInstructions: export_instructions
|
||||
});
|
||||
|
||||
console.log(responseWithInstructions);
|
||||
@@ -176,10 +176,10 @@ print(response)
|
||||
|
||||
```javascript JavaScript
|
||||
// Retrieve using export ID
|
||||
const memory_export_id = "550e8400-e29b-41d4-a716-446655440000";
|
||||
const memoryExportId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
|
||||
const response = await client.getMemoryExport({
|
||||
memory_export_id: memory_export_id
|
||||
memoryExportId: memoryExportId
|
||||
});
|
||||
|
||||
console.log(response);
|
||||
|
||||
@@ -67,7 +67,7 @@ const messages = [
|
||||
},
|
||||
]
|
||||
|
||||
await client.add(messages, { user_id: "alice" })
|
||||
await client.add(messages, { userId: "alice" })
|
||||
```
|
||||
|
||||
```json Output
|
||||
@@ -166,7 +166,7 @@ const imageMessage = {
|
||||
}
|
||||
};
|
||||
|
||||
await client.add([imageMessage], { user_id: "alice" })
|
||||
await client.add([imageMessage], { userId: "alice" })
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Mem0 Platform features help managed deployments scale from basic filtering to gr
|
||||
<Card title="Go Real-Time with Async" icon="bolt" href="/platform/features/async-client">
|
||||
Non-blocking add/search requests for agents.
|
||||
</Card>
|
||||
<Card title="Unlock Graph Memory" icon="circle-nodes" href="/platform/features/graph-memory">
|
||||
<Card title="Unlock Graph Memory" icon="circle-nodes" href="/open-source/features/graph-memory">
|
||||
Relationship-aware recall across entities.
|
||||
</Card>
|
||||
<Card
|
||||
|
||||
@@ -76,7 +76,7 @@ const unixTimestamp = Math.floor(fiveDaysAgo.getTime() / 1000);
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
client.add(messages, { user_id: "user1", timestamp: unixTimestamp })
|
||||
client.add(messages, { userId: "user1", timestamp: unixTimestamp })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -131,7 +131,7 @@ const january2023Timestamp = 1672531200; // Unix timestamp for 2023-01-01 00:00
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
client.add(messages, { user_id: "user1", timestamp: january2023Timestamp })
|
||||
client.add(messages, { userId: "user1", timestamp: january2023Timestamp })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
@@ -68,7 +68,7 @@ const messages = [
|
||||
{"role": "user", "content": "I'm a vegetarian and allergic to nuts."},
|
||||
{"role": "assistant", "content": "Got it! I'll remember your dietary preferences."}
|
||||
];
|
||||
await client.add(messages, { user_id: "user123" });
|
||||
await client.add(messages, { userId: "user123" });
|
||||
````
|
||||
|
||||
```bash cURL
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "2.4.6",
|
||||
"version": "3.0.0-beta.0",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
|
||||
@@ -3,14 +3,17 @@ import type * as MemoryTypes from "./mem0.types";
|
||||
|
||||
// Re-export all types from mem0.types
|
||||
export type {
|
||||
MemoryOptions,
|
||||
EntityOptions,
|
||||
AddMemoryOptions,
|
||||
SearchMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
DeleteAllMemoryOptions,
|
||||
ProjectOptions,
|
||||
Memory,
|
||||
MemoryHistory,
|
||||
MemoryUpdateBody,
|
||||
ProjectResponse,
|
||||
PromptUpdatePayload,
|
||||
SearchOptions,
|
||||
Webhook,
|
||||
WebhookCreatePayload,
|
||||
WebhookUpdatePayload,
|
||||
@@ -19,6 +22,8 @@ export type {
|
||||
AllUsers,
|
||||
User,
|
||||
FeedbackPayload,
|
||||
CreateMemoryExportPayload,
|
||||
GetMemoryExportPayload,
|
||||
} from "./mem0.types";
|
||||
|
||||
// Re-export enums as values (not type-only)
|
||||
|
||||
+87
-233
@@ -4,11 +4,13 @@ import {
|
||||
ProjectOptions,
|
||||
Memory,
|
||||
MemoryHistory,
|
||||
MemoryOptions,
|
||||
AddMemoryOptions,
|
||||
SearchMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
DeleteAllMemoryOptions,
|
||||
MemoryUpdateBody,
|
||||
ProjectResponse,
|
||||
PromptUpdatePayload,
|
||||
SearchOptions,
|
||||
Webhook,
|
||||
WebhookCreatePayload,
|
||||
WebhookUpdatePayload,
|
||||
@@ -18,6 +20,7 @@ import {
|
||||
GetMemoryExportPayload,
|
||||
} from "./mem0.types";
|
||||
import { captureClientEvent, generateHash } from "./telemetry";
|
||||
import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils";
|
||||
import { createExceptionFromResponse, MemoryError } from "../common/exceptions";
|
||||
|
||||
class APIError extends Error {
|
||||
@@ -30,19 +33,13 @@ class APIError extends Error {
|
||||
interface ClientOptions {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
organizationName?: string;
|
||||
projectName?: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
export default class MemoryClient {
|
||||
apiKey: string;
|
||||
host: string;
|
||||
organizationName: string | null;
|
||||
projectName: string | null;
|
||||
organizationId: string | number | null;
|
||||
projectId: string | number | null;
|
||||
private organizationId: string | number | null;
|
||||
private projectId: string | number | null;
|
||||
headers: Record<string, string>;
|
||||
client: any;
|
||||
telemetryId: string;
|
||||
@@ -59,35 +56,11 @@ export default class MemoryClient {
|
||||
}
|
||||
}
|
||||
|
||||
_validateOrgProject(): void {
|
||||
// Check for organizationName/projectName pair
|
||||
if (
|
||||
(this.organizationName === null && this.projectName !== null) ||
|
||||
(this.organizationName !== null && this.projectName === null)
|
||||
) {
|
||||
console.warn(
|
||||
"Warning: Both organizationName and projectName must be provided together when using either. This will be removed from version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.",
|
||||
);
|
||||
}
|
||||
|
||||
// Check for organizationId/projectId pair
|
||||
if (
|
||||
(this.organizationId === null && this.projectId !== null) ||
|
||||
(this.organizationId !== null && this.projectId === null)
|
||||
) {
|
||||
console.warn(
|
||||
"Warning: Both organizationId and projectId must be provided together when using either. This will be removed from version 1.0.40.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(options: ClientOptions) {
|
||||
this.apiKey = options.apiKey;
|
||||
this.host = options.host || "https://api.mem0.ai";
|
||||
this.organizationName = options.organizationName || null;
|
||||
this.projectName = options.projectName || null;
|
||||
this.organizationId = options.organizationId || null;
|
||||
this.projectId = options.projectId || null;
|
||||
this.organizationId = null;
|
||||
this.projectId = null;
|
||||
|
||||
this.headers = {
|
||||
Authorization: `Token ${this.apiKey}`,
|
||||
@@ -101,28 +74,19 @@ export default class MemoryClient {
|
||||
});
|
||||
|
||||
this._validateApiKey();
|
||||
|
||||
// Initialize with a temporary ID that will be updated
|
||||
this.telemetryId = "";
|
||||
|
||||
// Initialize the client
|
||||
this._initializeClient();
|
||||
}
|
||||
|
||||
private async _initializeClient() {
|
||||
try {
|
||||
// Generate telemetry ID
|
||||
await this.ping();
|
||||
|
||||
if (!this.telemetryId) {
|
||||
this.telemetryId = generateHash(this.apiKey);
|
||||
}
|
||||
|
||||
this._validateOrgProject();
|
||||
|
||||
// Capture initialization event
|
||||
captureClientEvent("init", this, {
|
||||
api_version: "v1",
|
||||
client_type: "MemoryClient",
|
||||
}).catch((error: any) => {
|
||||
console.error("Failed to capture event:", error);
|
||||
@@ -160,16 +124,19 @@ export default class MemoryClient {
|
||||
throw createExceptionFromResponse(response.status, errorData);
|
||||
}
|
||||
const jsonResponse = await response.json();
|
||||
return jsonResponse;
|
||||
return snakeToCamelKeys(jsonResponse);
|
||||
}
|
||||
|
||||
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
|
||||
_preparePayload(
|
||||
messages: Array<Message>,
|
||||
options: Record<string, any>,
|
||||
): object {
|
||||
const payload: any = {};
|
||||
payload.messages = messages;
|
||||
return { ...payload, ...options };
|
||||
return camelToSnakeKeys({ ...payload, ...options });
|
||||
}
|
||||
|
||||
_prepareParams(options: MemoryOptions): object {
|
||||
_prepareParams(options: Record<string, any>): object {
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).filter(([_, v]) => v != null),
|
||||
);
|
||||
@@ -195,12 +162,11 @@ export default class MemoryClient {
|
||||
throw new APIError(response.message || "API Key is invalid");
|
||||
}
|
||||
|
||||
const { org_id, project_id, user_email } = response;
|
||||
const { orgId, projectId, userEmail } = response;
|
||||
|
||||
// Only update if values are actually present
|
||||
if (org_id && !this.organizationId) this.organizationId = org_id;
|
||||
if (project_id && !this.projectId) this.projectId = project_id;
|
||||
if (user_email) this.telemetryId = user_email;
|
||||
if (orgId) this.organizationId = orgId;
|
||||
if (projectId) this.projectId = projectId;
|
||||
if (userEmail) this.telemetryId = userEmail;
|
||||
} catch (error: any) {
|
||||
// Pass through structured exceptions and APIError
|
||||
if (error instanceof MemoryError || error instanceof APIError) {
|
||||
@@ -215,30 +181,11 @@ export default class MemoryClient {
|
||||
|
||||
async add(
|
||||
messages: Array<Message>,
|
||||
options: MemoryOptions & Record<string, any> = {},
|
||||
options: AddMemoryOptions & Record<string, any> = {},
|
||||
): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
options.org_id = this.organizationId;
|
||||
options.project_id = this.projectId;
|
||||
|
||||
if (options.org_name) delete options.org_name;
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
|
||||
if (options.api_version) {
|
||||
options.version = options.api_version.toString() || "v2";
|
||||
}
|
||||
|
||||
const payload = this._preparePayload(messages, options);
|
||||
|
||||
// get payload keys whose value is not null or undefined
|
||||
const payloadKeys = Object.keys(payload);
|
||||
this._captureEvent("add", [payloadKeys]);
|
||||
|
||||
@@ -276,7 +223,6 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payload: Record<string, any> = {};
|
||||
if (text !== undefined) payload.text = text;
|
||||
if (metadata !== undefined) payload.metadata = metadata;
|
||||
@@ -307,87 +253,53 @@ export default class MemoryClient {
|
||||
);
|
||||
}
|
||||
|
||||
async getAll(options?: SearchOptions): Promise<Array<Memory>> {
|
||||
async getAll(options?: GetAllMemoryOptions): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_all", [payloadKeys]);
|
||||
const { api_version, page, page_size, ...otherOptions } = options ?? {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
otherOptions.org_name = this.organizationName;
|
||||
otherOptions.project_name = this.projectName;
|
||||
const { page, pageSize, ...rest } = options ?? {};
|
||||
const body: Record<string, any> = {
|
||||
output_format: "v1.1",
|
||||
...camelToSnakeKeys(rest),
|
||||
};
|
||||
|
||||
let url = `${this.host}/v2/memories/`;
|
||||
if (page && pageSize) {
|
||||
url += `?page=${page}&page_size=${pageSize}`;
|
||||
}
|
||||
|
||||
let appendedParams = "";
|
||||
let paginated_response = false;
|
||||
|
||||
if (page && page_size) {
|
||||
appendedParams += `page=${page}&page_size=${page_size}`;
|
||||
paginated_response = true;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
otherOptions.org_id = this.organizationId;
|
||||
otherOptions.project_id = this.projectId;
|
||||
|
||||
if (otherOptions.org_name) delete otherOptions.org_name;
|
||||
if (otherOptions.project_name) delete otherOptions.project_name;
|
||||
}
|
||||
|
||||
if (api_version === "v2") {
|
||||
let url = paginated_response
|
||||
? `${this.host}/v2/memories/?${appendedParams}`
|
||||
: `${this.host}/v2/memories/`;
|
||||
return this._fetchWithErrorHandling(url, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(otherOptions),
|
||||
});
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(this._prepareParams(otherOptions));
|
||||
const url = paginated_response
|
||||
? `${this.host}/v1/memories/?${params}&${appendedParams}`
|
||||
: `${this.host}/v1/memories/?${params}`;
|
||||
return this._fetchWithErrorHandling(url, {
|
||||
headers: this.headers,
|
||||
});
|
||||
}
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
// Unwrap v1.1 format: { results: [...] } → [...]
|
||||
return Array.isArray(response) ? response : (response?.results ?? response);
|
||||
}
|
||||
|
||||
async search(
|
||||
query: string,
|
||||
options?: SearchOptions & Record<string, any>,
|
||||
options?: SearchMemoryOptions,
|
||||
): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("search", [payloadKeys]);
|
||||
const { api_version, ...otherOptions } = options ?? {};
|
||||
const payload = { query, ...otherOptions };
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
payload.org_name = this.organizationName;
|
||||
payload.project_name = this.projectName;
|
||||
}
|
||||
const payload: Record<string, any> = {
|
||||
query,
|
||||
output_format: "v1.1",
|
||||
...camelToSnakeKeys(options ?? {}),
|
||||
};
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
payload.org_id = this.organizationId;
|
||||
payload.project_id = this.projectId;
|
||||
|
||||
if (payload.org_name) delete payload.org_name;
|
||||
if (payload.project_name) delete payload.project_name;
|
||||
}
|
||||
const endpoint =
|
||||
api_version === "v2" ? "/v2/memories/search/" : "/v1/memories/search/";
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}${endpoint}`,
|
||||
`${this.host}/v2/memories/search/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
// Unwrap v1.1 format: { results: [...] } → [...]
|
||||
return Array.isArray(response) ? response : (response?.results ?? response);
|
||||
}
|
||||
|
||||
async delete(memoryId: string): Promise<{ message: string }> {
|
||||
@@ -402,25 +314,15 @@ export default class MemoryClient {
|
||||
);
|
||||
}
|
||||
|
||||
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
|
||||
async deleteAll(
|
||||
options: DeleteAllMemoryOptions = {},
|
||||
): Promise<{ message: string }> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("delete_all", [payloadKeys]);
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
options.org_id = this.organizationId;
|
||||
options.project_id = this.projectId;
|
||||
|
||||
if (options.org_name) delete options.org_name;
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
const snakeOptions = camelToSnakeKeys(this._prepareParams(options));
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(this._prepareParams(options));
|
||||
const params = new URLSearchParams(snakeOptions);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/memories/?${params}`,
|
||||
{
|
||||
@@ -443,31 +345,20 @@ export default class MemoryClient {
|
||||
return response;
|
||||
}
|
||||
|
||||
async users(): Promise<AllUsers> {
|
||||
async users(options?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<AllUsers> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
this._captureEvent("users", []);
|
||||
const options: MemoryOptions = {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
options.org_id = this.organizationId;
|
||||
options.project_id = this.projectId;
|
||||
|
||||
if (options.org_name) delete options.org_name;
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(options);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/entities/?${params}`,
|
||||
{
|
||||
headers: this.headers,
|
||||
},
|
||||
);
|
||||
let url = `${this.host}/v1/entities/`;
|
||||
const params: string[] = [];
|
||||
if (options?.page) params.push(`page=${options.page}`);
|
||||
if (options?.pageSize) params.push(`page_size=${options.pageSize}`);
|
||||
if (params.length) url += `?${params.join("&")}`;
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
headers: this.headers,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -495,26 +386,25 @@ export default class MemoryClient {
|
||||
|
||||
async deleteUsers(
|
||||
params: {
|
||||
user_id?: string;
|
||||
agent_id?: string;
|
||||
app_id?: string;
|
||||
run_id?: string;
|
||||
userId?: string;
|
||||
agentId?: string;
|
||||
appId?: string;
|
||||
runId?: string;
|
||||
} = {},
|
||||
): Promise<{ message: string }> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
|
||||
let to_delete: Array<{ type: string; name: string }> = [];
|
||||
const { user_id, agent_id, app_id, run_id } = params;
|
||||
const { userId, agentId, appId, runId } = params;
|
||||
|
||||
if (user_id) {
|
||||
to_delete = [{ type: "user", name: user_id }];
|
||||
} else if (agent_id) {
|
||||
to_delete = [{ type: "agent", name: agent_id }];
|
||||
} else if (app_id) {
|
||||
to_delete = [{ type: "app", name: app_id }];
|
||||
} else if (run_id) {
|
||||
to_delete = [{ type: "run", name: run_id }];
|
||||
if (userId) {
|
||||
to_delete = [{ type: "user", name: userId }];
|
||||
} else if (agentId) {
|
||||
to_delete = [{ type: "agent", name: agentId }];
|
||||
} else if (appId) {
|
||||
to_delete = [{ type: "app", name: appId }];
|
||||
} else if (runId) {
|
||||
to_delete = [{ type: "run", name: runId }];
|
||||
} else {
|
||||
const entities = await this.users();
|
||||
to_delete = entities.results.map((entity) => ({
|
||||
@@ -527,29 +417,9 @@ export default class MemoryClient {
|
||||
throw new Error("No entities to delete");
|
||||
}
|
||||
|
||||
const requestOptions: MemoryOptions = {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
requestOptions.org_name = this.organizationName;
|
||||
requestOptions.project_name = this.projectName;
|
||||
}
|
||||
|
||||
if (this.organizationId != null && this.projectId != null) {
|
||||
requestOptions.org_id = this.organizationId;
|
||||
requestOptions.project_id = this.projectId;
|
||||
|
||||
if (requestOptions.org_name) delete requestOptions.org_name;
|
||||
if (requestOptions.project_name) delete requestOptions.project_name;
|
||||
}
|
||||
|
||||
// Delete each entity and handle errors
|
||||
for (const entity of to_delete) {
|
||||
try {
|
||||
await this.client.delete(
|
||||
`/v2/entities/${entity.type}/${entity.name}/`,
|
||||
{
|
||||
params: requestOptions,
|
||||
},
|
||||
);
|
||||
await this.client.delete(`/v2/entities/${entity.type}/${entity.name}/`);
|
||||
} catch (error: any) {
|
||||
throw new APIError(
|
||||
`Failed to delete ${entity.type} ${entity.name}: ${error.message}`,
|
||||
@@ -558,18 +428,12 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
this._captureEvent("delete_users", [
|
||||
{
|
||||
user_id: user_id,
|
||||
agent_id: agent_id,
|
||||
app_id: app_id,
|
||||
run_id: run_id,
|
||||
sync_type: "sync",
|
||||
},
|
||||
{ userId, agentId, appId, runId, sync_type: "sync" },
|
||||
]);
|
||||
|
||||
return {
|
||||
message:
|
||||
user_id || agent_id || app_id || run_id
|
||||
userId || agentId || appId || runId
|
||||
? "Entity deleted successfully."
|
||||
: "All users, agents, apps and runs deleted.",
|
||||
};
|
||||
@@ -612,7 +476,6 @@ export default class MemoryClient {
|
||||
|
||||
async getProject(options: ProjectOptions): Promise<ProjectResponse> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_project", [payloadKeys]);
|
||||
const { fields } = options;
|
||||
@@ -624,7 +487,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
fields?.forEach((field) => params.append("fields", field));
|
||||
fields?.forEach((field) => params.append("fields", camelToSnake(field)));
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/orgs/organizations/${this.organizationId}/projects/${this.projectId}/?${params.toString()}`,
|
||||
@@ -639,7 +502,6 @@ export default class MemoryClient {
|
||||
prompts: PromptUpdatePayload,
|
||||
): Promise<Record<string, any>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._validateOrgProject();
|
||||
this._captureEvent("update_project", []);
|
||||
if (!(this.organizationId && this.projectId)) {
|
||||
throw new Error(
|
||||
@@ -652,7 +514,7 @@ export default class MemoryClient {
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(prompts),
|
||||
body: JSON.stringify(camelToSnakeKeys(prompts)),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -736,7 +598,7 @@ export default class MemoryClient {
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(camelToSnakeKeys(data)),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -748,21 +610,16 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("create_memory_export", []);
|
||||
|
||||
// Return if missing filters or schema
|
||||
if (!data.filters || !data.schema) {
|
||||
throw new Error("Missing filters or schema");
|
||||
}
|
||||
|
||||
// Add Org and Project ID
|
||||
data.org_id = this.organizationId?.toString() || null;
|
||||
data.project_id = this.projectId?.toString() || null;
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/exports/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(camelToSnakeKeys(data)),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -775,19 +632,16 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("get_memory_export", []);
|
||||
|
||||
if (!data.memory_export_id && !data.filters) {
|
||||
throw new Error("Missing memory_export_id or filters");
|
||||
if (!data.memoryExportId && !data.filters) {
|
||||
throw new Error("Missing memoryExportId or filters");
|
||||
}
|
||||
|
||||
data.org_id = this.organizationId?.toString() || "";
|
||||
data.project_id = this.projectId?.toString() || "";
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/exports/get/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(camelToSnakeKeys(data)),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
|
||||
@@ -1,59 +1,66 @@
|
||||
interface Common {
|
||||
project_id?: string | null;
|
||||
org_id?: string | null;
|
||||
// ─── Entity Options (for add/delete — top-level identity) ───
|
||||
export interface EntityOptions {
|
||||
userId?: string;
|
||||
agentId?: string;
|
||||
appId?: string;
|
||||
runId?: string;
|
||||
}
|
||||
|
||||
export interface MemoryOptions {
|
||||
api_version?: API_VERSION | string;
|
||||
version?: API_VERSION | string;
|
||||
user_id?: string;
|
||||
agent_id?: string;
|
||||
app_id?: string;
|
||||
run_id?: string;
|
||||
// ─── Per-Method Options ─────────────────────────────────────
|
||||
export interface AddMemoryOptions extends EntityOptions {
|
||||
metadata?: Record<string, any>;
|
||||
filters?: Record<string, any>;
|
||||
org_name?: string | null; // Deprecated
|
||||
project_name?: string | null; // Deprecated
|
||||
org_id?: string | number | null;
|
||||
project_id?: string | number | null;
|
||||
infer?: boolean;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
includes?: string;
|
||||
excludes?: string;
|
||||
enable_graph?: boolean;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
custom_instructions?: string;
|
||||
customCategories?: custom_categories[];
|
||||
customInstructions?: string;
|
||||
timestamp?: number;
|
||||
output_format?: string | OutputFormat;
|
||||
async_mode?: boolean;
|
||||
filter_memories?: boolean;
|
||||
immutable?: boolean;
|
||||
structured_data_schema?: Record<string, any>;
|
||||
structuredDataSchema?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface SearchMemoryOptions {
|
||||
filters?: Record<string, any>;
|
||||
metadata?: Record<string, any>;
|
||||
topK?: number;
|
||||
threshold?: number;
|
||||
rerank?: boolean;
|
||||
fields?: string[];
|
||||
categories?: string[];
|
||||
}
|
||||
|
||||
export interface GetAllMemoryOptions {
|
||||
filters?: Record<string, any>;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
categories?: string[];
|
||||
}
|
||||
|
||||
export interface DeleteAllMemoryOptions extends EntityOptions {}
|
||||
|
||||
// ─── Project Options ────────────────────────────────────────
|
||||
export interface ProjectOptions {
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
export enum OutputFormat {
|
||||
V1 = "v1.0",
|
||||
V1_1 = "v1.1",
|
||||
}
|
||||
|
||||
export enum API_VERSION {
|
||||
V1 = "v1",
|
||||
V2 = "v2",
|
||||
export interface PromptUpdatePayload {
|
||||
customInstructions?: string;
|
||||
customCategories?: custom_categories[];
|
||||
retrievalCriteria?: any[];
|
||||
version?: string;
|
||||
memoryDepth?: string | null;
|
||||
usecaseSetting?: string | number;
|
||||
multilingual?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ─── Enums ──────────────────────────────────────────────────
|
||||
export enum Feedback {
|
||||
POSITIVE = "POSITIVE",
|
||||
NEGATIVE = "NEGATIVE",
|
||||
VERY_NEGATIVE = "VERY_NEGATIVE",
|
||||
}
|
||||
|
||||
// ─── Message Types ──────────────────────────────────────────
|
||||
export interface MultiModalMessages {
|
||||
type: "image_url";
|
||||
image_url: {
|
||||
@@ -68,30 +75,9 @@ export interface Messages {
|
||||
|
||||
export interface Message extends Messages {}
|
||||
|
||||
export interface MemoryHistory {
|
||||
id: string;
|
||||
memory_id: string;
|
||||
input: Array<Messages>;
|
||||
old_memory: string | null;
|
||||
new_memory: string | null;
|
||||
user_id: string;
|
||||
categories: Array<string>;
|
||||
event: Event | string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface SearchOptions extends MemoryOptions {
|
||||
api_version?: API_VERSION | string;
|
||||
limit?: number;
|
||||
enable_graph?: boolean;
|
||||
threshold?: number;
|
||||
top_k?: number;
|
||||
only_metadata_based_search?: boolean;
|
||||
keyword_search?: boolean;
|
||||
fields?: string[];
|
||||
categories?: string[];
|
||||
rerank?: boolean;
|
||||
// ─── Response Types (camelCase — converted from API snake_case) ─────
|
||||
export interface MemoryData {
|
||||
memory: string;
|
||||
}
|
||||
|
||||
enum Event {
|
||||
@@ -101,28 +87,37 @@ enum Event {
|
||||
NOOP = "NOOP",
|
||||
}
|
||||
|
||||
export interface MemoryData {
|
||||
memory: string;
|
||||
}
|
||||
|
||||
export interface Memory {
|
||||
id: string;
|
||||
messages?: Array<Messages>;
|
||||
event?: Event | string;
|
||||
data?: MemoryData | null;
|
||||
memory?: string;
|
||||
user_id?: string;
|
||||
userId?: string;
|
||||
hash?: string;
|
||||
categories?: Array<string>;
|
||||
created_at?: Date;
|
||||
updated_at?: Date;
|
||||
memory_type?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
memoryType?: string;
|
||||
score?: number;
|
||||
metadata?: any | null;
|
||||
owner?: string | null;
|
||||
agent_id?: string | null;
|
||||
app_id?: string | null;
|
||||
run_id?: string | null;
|
||||
agentId?: string | null;
|
||||
appId?: string | null;
|
||||
runId?: string | null;
|
||||
}
|
||||
|
||||
export interface MemoryHistory {
|
||||
id: string;
|
||||
memoryId: string;
|
||||
input: Array<Messages>;
|
||||
oldMemory: string | null;
|
||||
newMemory: string | null;
|
||||
userId: string;
|
||||
categories: Array<string>;
|
||||
event: Event | string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface MemoryUpdateBody {
|
||||
@@ -133,9 +128,9 @@ export interface MemoryUpdateBody {
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
total_memories: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
totalMemories: number;
|
||||
owner: string;
|
||||
type: string;
|
||||
}
|
||||
@@ -148,8 +143,8 @@ export interface AllUsers {
|
||||
}
|
||||
|
||||
export interface ProjectResponse {
|
||||
custom_instructions?: string;
|
||||
custom_categories?: string[];
|
||||
customInstructions?: string;
|
||||
customCategories?: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -157,20 +152,7 @@ interface custom_categories {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface PromptUpdatePayload {
|
||||
custom_instructions?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
retrieval_criteria?: any[];
|
||||
enable_graph?: boolean;
|
||||
version?: string;
|
||||
inclusion_prompt?: string;
|
||||
exclusion_prompt?: string;
|
||||
memory_depth?: string | null;
|
||||
usecase_setting?: string | number;
|
||||
multilingual?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ─── Webhook Types ──────────────────────────────────────────
|
||||
export enum WebhookEvent {
|
||||
MEMORY_ADDED = "memory_add",
|
||||
MEMORY_UPDATED = "memory_update",
|
||||
@@ -179,14 +161,14 @@ export enum WebhookEvent {
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
webhook_id?: string;
|
||||
webhookId?: string;
|
||||
name: string;
|
||||
url: string;
|
||||
project?: string;
|
||||
created_at?: Date;
|
||||
updated_at?: Date;
|
||||
is_active?: boolean;
|
||||
event_types?: WebhookEvent[];
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
isActive?: boolean;
|
||||
eventTypes?: WebhookEvent[];
|
||||
}
|
||||
|
||||
export interface WebhookCreatePayload {
|
||||
@@ -202,19 +184,20 @@ export interface WebhookUpdatePayload {
|
||||
eventTypes?: WebhookEvent[];
|
||||
}
|
||||
|
||||
// ─── Feedback & Export Types ────────────────────────────────
|
||||
export interface FeedbackPayload {
|
||||
memory_id: string;
|
||||
memoryId: string;
|
||||
feedback?: Feedback | null;
|
||||
feedback_reason?: string | null;
|
||||
feedbackReason?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateMemoryExportPayload extends Common {
|
||||
export interface CreateMemoryExportPayload {
|
||||
schema: Record<string, any>;
|
||||
filters: Record<string, any>;
|
||||
export_instructions?: string;
|
||||
exportInstructions?: string;
|
||||
}
|
||||
|
||||
export interface GetMemoryExportPayload extends Common {
|
||||
export interface GetMemoryExportPayload {
|
||||
filters?: Record<string, any>;
|
||||
memory_export_id?: string;
|
||||
memoryExportId?: string;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = await client.add(messages, { user_id: TEST_USER_ID });
|
||||
const result = await client.add(messages, { userId: TEST_USER_ID });
|
||||
|
||||
// API processes memories asynchronously — returns PENDING
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
@@ -60,7 +60,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
// Validate response shape
|
||||
for (const item of result) {
|
||||
expect(item).toHaveProperty("status");
|
||||
expect(item).toHaveProperty("event_id");
|
||||
expect(item).toHaveProperty("eventId");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = await client.add(messages, { user_id: TEST_USER_ID });
|
||||
const result = await client.add(messages, { userId: TEST_USER_ID });
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -103,17 +103,17 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
expect(memory.id).toBe(memoryId);
|
||||
expect(typeof memory.memory).toBe("string");
|
||||
expect(memory.memory!.length).toBeGreaterThan(0);
|
||||
expect(typeof memory.user_id).toBe("string");
|
||||
expect(typeof memory.userId).toBe("string");
|
||||
expect(
|
||||
memory.metadata === null || typeof memory.metadata === "object",
|
||||
).toBe(true);
|
||||
expect(
|
||||
Array.isArray(memory.categories) || memory.categories === null,
|
||||
).toBe(true);
|
||||
expect(new Date(memory.created_at || "").toString()).not.toBe(
|
||||
expect(new Date(memory.createdAt || "").toString()).not.toBe(
|
||||
"Invalid Date",
|
||||
);
|
||||
expect(new Date(memory.updated_at || "").toString()).not.toBe(
|
||||
expect(new Date(memory.updatedAt || "").toString()).not.toBe(
|
||||
"Invalid Date",
|
||||
);
|
||||
});
|
||||
@@ -122,7 +122,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
// ─── Get all ──────────────────────────────────────────────
|
||||
describe("get all memories", () => {
|
||||
test("returns all memories for test user", async () => {
|
||||
const memories = await client.getAll({ user_id: TEST_USER_ID });
|
||||
const memories = await client.getAll({
|
||||
filters: { userId: TEST_USER_ID },
|
||||
});
|
||||
|
||||
expect(Array.isArray(memories)).toBe(true);
|
||||
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
|
||||
@@ -135,9 +137,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("returns paginated results with page and page_size", async () => {
|
||||
const page1 = await client.getAll({
|
||||
user_id: TEST_USER_ID,
|
||||
filters: { userId: TEST_USER_ID },
|
||||
page: 1,
|
||||
page_size: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
|
||||
// Paginated response is an object with results array
|
||||
@@ -189,7 +191,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
},
|
||||
],
|
||||
{
|
||||
user_id: TEST_USER_ID,
|
||||
userId: TEST_USER_ID,
|
||||
metadata: { source: "integration-test", category: "preferences" },
|
||||
},
|
||||
);
|
||||
@@ -200,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("getAll for non-existent user returns empty array", async () => {
|
||||
const memories = await client.getAll({
|
||||
user_id: `nonexistent-user-${randomUUID()}`,
|
||||
filters: { userId: `nonexistent-user-${randomUUID()}` },
|
||||
});
|
||||
|
||||
expect(Array.isArray(memories)).toBe(true);
|
||||
@@ -209,7 +211,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("deleteAll for non-existent user does not throw", async () => {
|
||||
const result = await client.deleteAll({
|
||||
user_id: `nonexistent-user-${randomUUID()}`,
|
||||
userId: `nonexistent-user-${randomUUID()}`,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -239,13 +241,13 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
// ─── Delete all + delete user ─────────────────────────────
|
||||
describe("cleanup operations", () => {
|
||||
test("deletes all memories for test user", async () => {
|
||||
const result = await client.deleteAll({ user_id: TEST_USER_ID });
|
||||
const result = await client.deleteAll({ userId: TEST_USER_ID });
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.message).toBe("string");
|
||||
});
|
||||
|
||||
test("deletes the test user entity", async () => {
|
||||
const result = await client.deleteUsers({ user_id: TEST_USER_ID });
|
||||
const result = await client.deleteUsers({ userId: TEST_USER_ID });
|
||||
expect(result).toBeDefined();
|
||||
expect(result.message).toBe("Entity deleted successfully.");
|
||||
});
|
||||
|
||||
@@ -18,10 +18,10 @@ export default async function globalSetup() {
|
||||
// Full project wipe — all four filters set explicitly
|
||||
try {
|
||||
await client.deleteAll({
|
||||
user_id: "*",
|
||||
agent_id: "*",
|
||||
app_id: "*",
|
||||
run_id: "*",
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore — may 404 if no data exists
|
||||
|
||||
@@ -17,10 +17,10 @@ export default async function globalTeardown() {
|
||||
|
||||
try {
|
||||
await client.deleteAll({
|
||||
user_id: "*",
|
||||
agent_id: "*",
|
||||
app_id: "*",
|
||||
run_id: "*",
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
|
||||
@@ -63,7 +63,9 @@ export async function waitForMemories(
|
||||
maxRetries = 4,
|
||||
): Promise<Memory[]> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
const memories = await withRetry(() => client.getAll({ user_id: userId }));
|
||||
const memories = await withRetry(() =>
|
||||
client.getAll({ filters: { userId } }),
|
||||
);
|
||||
if (Array.isArray(memories) && memories.length >= minCount) {
|
||||
return memories;
|
||||
}
|
||||
@@ -152,7 +154,7 @@ export async function seedTestMemories(
|
||||
"Nice to meet you! I'll remember that your favorite color is blue.",
|
||||
},
|
||||
],
|
||||
{ user_id: userId },
|
||||
{ userId },
|
||||
),
|
||||
);
|
||||
|
||||
@@ -168,7 +170,7 @@ export async function seedTestMemories(
|
||||
content: "Got it, you're a software engineer at Acme Corp!",
|
||||
},
|
||||
],
|
||||
{ user_id: userId },
|
||||
{ userId },
|
||||
),
|
||||
);
|
||||
|
||||
@@ -184,12 +186,12 @@ export async function cleanupTestUser(
|
||||
userId: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await client.deleteAll({ user_id: userId });
|
||||
await client.deleteAll({ userId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await client.deleteUsers({ user_id: userId });
|
||||
await client.deleteUsers({ userId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -207,10 +209,10 @@ export async function fullProjectCleanup(client: MemoryClient): Promise<void> {
|
||||
// Delete all memories — all four filters set explicitly
|
||||
try {
|
||||
await client.deleteAll({
|
||||
user_id: "*",
|
||||
agent_id: "*",
|
||||
app_id: "*",
|
||||
run_id: "*",
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore — may 404 if no data exists
|
||||
|
||||
@@ -31,10 +31,10 @@ describeIntegration("MemoryClient Integration — Initialization", () => {
|
||||
|
||||
afterAll(() => cleanup());
|
||||
|
||||
test("client pings successfully and resolves org/project", async () => {
|
||||
test("client pings successfully", async () => {
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBeTruthy();
|
||||
expect(client.projectId).toBeTruthy();
|
||||
// org/project are now resolved internally from the API key
|
||||
expect(client.telemetryId).toBeTruthy();
|
||||
});
|
||||
|
||||
test("get with invalid ID throws ValidationError", async () => {
|
||||
|
||||
@@ -67,16 +67,16 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
expect(project).toBeDefined();
|
||||
expect(typeof project).toBe("object");
|
||||
expect("custom_instructions" in project).toBe(true);
|
||||
expect("customInstructions" in project).toBe(true);
|
||||
|
||||
originalInstructions = project.custom_instructions;
|
||||
originalInstructions = project.customInstructions;
|
||||
});
|
||||
|
||||
test("updates project custom_instructions via updateProject()", async () => {
|
||||
const testInstruction = `integration-test-${randomUUID().slice(0, 8)}`;
|
||||
|
||||
const result = await client.updateProject({
|
||||
custom_instructions: testInstruction,
|
||||
customInstructions: testInstruction,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -85,11 +85,11 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
const project = await client.getProject({
|
||||
fields: ["custom_instructions"],
|
||||
});
|
||||
expect(project.custom_instructions).toBe(testInstruction);
|
||||
expect(project.customInstructions).toBe(testInstruction);
|
||||
|
||||
// Restore original
|
||||
await client.updateProject({
|
||||
custom_instructions: originalInstructions || "",
|
||||
customInstructions: originalInstructions || "",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -120,26 +120,26 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED],
|
||||
}),
|
||||
);
|
||||
createdWebhookId = result.webhook_id!;
|
||||
expect(result.webhook_id).toBeDefined();
|
||||
createdWebhookId = result.webhookId!;
|
||||
expect(result.webhookId).toBeDefined();
|
||||
});
|
||||
|
||||
test("createWebhook returns the correct name", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(wh!.name).toBe(hookName);
|
||||
});
|
||||
|
||||
test("createWebhook returns the correct url", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(wh!.url).toBe(hookUrl);
|
||||
});
|
||||
|
||||
test("createWebhook returns the correct event_types", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
expect(wh!.event_types?.sort()).toStrictEqual(
|
||||
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(wh!.eventTypes?.sort()).toStrictEqual(
|
||||
[WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED].sort(),
|
||||
);
|
||||
});
|
||||
@@ -152,14 +152,14 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
test("getWebhooks includes the created webhook", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(found).toBeDefined();
|
||||
});
|
||||
|
||||
test("getWebhooks shows the webhook as active", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
expect(found!.is_active).toBe(true);
|
||||
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(found!.isActive).toBe(true);
|
||||
});
|
||||
|
||||
// ─── Update ────────────────────────────────────────────
|
||||
@@ -177,14 +177,14 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
test("updateWebhook persists the new name", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const updated = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(updated!.name).toBe(updatedName);
|
||||
});
|
||||
|
||||
test("updateWebhook persists the new event_types", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const updated = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
expect(updated!.event_types?.sort()).toStrictEqual(
|
||||
const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(updated!.eventTypes?.sort()).toStrictEqual(
|
||||
[WebhookEvent.MEMORY_DELETED].sort(),
|
||||
);
|
||||
});
|
||||
@@ -199,7 +199,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
test("deleteWebhook removes the webhook from the list", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(found).toBeUndefined();
|
||||
createdWebhookId = "";
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Integration tests: Search and history operations.
|
||||
*
|
||||
* Tests search v1, search v2, and memory history against the real API.
|
||||
* Tests search, filtered search, and memory history against the real API.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
|
||||
*/
|
||||
@@ -36,14 +36,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// ─── Search v1 ────────────────────────────────────────────
|
||||
describe("search v1", () => {
|
||||
// ─── Search ─────────────────────────────────────────────
|
||||
describe("search", () => {
|
||||
test("searches memories by user_id and returns results with scores", async () => {
|
||||
// Search index may lag behind listing index — poll until ready
|
||||
const results = await waitForSearchResults(
|
||||
client,
|
||||
"What is my favorite color?",
|
||||
{ user_id: TEST_USER_ID },
|
||||
{ filters: { userId: TEST_USER_ID } },
|
||||
);
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
@@ -57,15 +57,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Search v2 ────────────────────────────────────────────
|
||||
describe("search v2", () => {
|
||||
// ─── Search with filters ─────────────────────────────────
|
||||
describe("search with filters", () => {
|
||||
test("searches with OR filters and returns results", async () => {
|
||||
const results = await waitForSearchResults(
|
||||
client,
|
||||
"What do you know about me?",
|
||||
{
|
||||
filters: { OR: [{ user_id: TEST_USER_ID }] },
|
||||
api_version: "v2",
|
||||
filters: { OR: [{ userId: TEST_USER_ID }] },
|
||||
},
|
||||
);
|
||||
|
||||
@@ -90,15 +89,15 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
|
||||
const entry = history[0];
|
||||
expect(typeof entry.id).toBe("string");
|
||||
expect(typeof entry.memory_id).toBe("string");
|
||||
expect(typeof entry.memoryId).toBe("string");
|
||||
expect(["ADD", "UPDATE", "DELETE", "NOOP"]).toContain(entry.event);
|
||||
expect(new Date(entry.created_at).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.updated_at).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.createdAt).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.updatedAt).toString()).not.toBe("Invalid Date");
|
||||
expect(
|
||||
entry.new_memory === null || typeof entry.new_memory === "string",
|
||||
entry.newMemory === null || typeof entry.newMemory === "string",
|
||||
).toBe(true);
|
||||
expect(
|
||||
entry.old_memory === null || typeof entry.old_memory === "string",
|
||||
entry.oldMemory === null || typeof entry.oldMemory === "string",
|
||||
).toBe(true);
|
||||
|
||||
const events = history.map((h) => h.event);
|
||||
@@ -110,19 +109,19 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
describe("edge cases", () => {
|
||||
test("search for non-existent user returns empty results", async () => {
|
||||
const results = await client.search("anything", {
|
||||
user_id: `nonexistent-user-${randomUUID()}`,
|
||||
filters: { userId: `nonexistent-user-${randomUUID()}` },
|
||||
});
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(results.length).toBe(0);
|
||||
});
|
||||
|
||||
test("search with limit param does not throw", async () => {
|
||||
test("search with top_k param does not throw", async () => {
|
||||
const results = await client.search(
|
||||
"Tell me about integration test user",
|
||||
{
|
||||
user_id: TEST_USER_ID,
|
||||
limit: 1,
|
||||
filters: { userId: TEST_USER_ID },
|
||||
topK: 1,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
/**
|
||||
* MemoryClient unit tests — add, get, getAll, update, delete, deleteAll, history.
|
||||
* MemoryClient unit tests — add, get, update, delete, deleteAll, history.
|
||||
* Tests verify request construction, not mock response echo.
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import type { Memory, MemoryHistory } from "../mem0.types";
|
||||
import type { MemoryHistory } from "../mem0.types";
|
||||
import {
|
||||
createMockMemory,
|
||||
createMockMemoryHistory,
|
||||
TEST_API_KEY,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
@@ -29,7 +27,7 @@ describe("MemoryClient - add()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add([{ role: "user", content: "Hello" }], { user_id: "u1" });
|
||||
await client.add([{ role: "user", content: "Hello" }], { userId: "u1" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/memories/", "POST")).toBeDefined();
|
||||
});
|
||||
@@ -41,7 +39,7 @@ describe("MemoryClient - add()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add(messages, { user_id: "u1" });
|
||||
await client.add(messages, { userId: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
expect(getFetchBody(call!).messages).toEqual(messages);
|
||||
@@ -61,47 +59,13 @@ describe("MemoryClient - add()", () => {
|
||||
expect(getFetchBody(call!).user_id).toBe("user_1");
|
||||
});
|
||||
|
||||
test("attaches org_id from constructor to payload", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
const body = getFetchBody(call!);
|
||||
expect(body.org_id).toBe(TEST_ORG_ID);
|
||||
});
|
||||
|
||||
test("attaches project_id from constructor to payload", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
const body = getFetchBody(call!);
|
||||
expect(body.project_id).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("sends empty messages array without crashing", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add([], { user_id: "u1" });
|
||||
await client.add([], { userId: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
expect(getFetchBody(call!).messages).toEqual([]);
|
||||
@@ -142,67 +106,6 @@ describe("MemoryClient - get()", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getAll() ────────────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - getAll()", () => {
|
||||
test("uses v2 POST endpoint when api_version=v2", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ user_id: "u1", api_version: "v2" });
|
||||
|
||||
expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("uses v1 GET endpoint by default with user_id as query param", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ user_id: "u1" });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
(c: [string, RequestInit]) =>
|
||||
c[0].includes("/v1/memories/?") && !c[1]?.method,
|
||||
);
|
||||
expect(call).toBeDefined();
|
||||
expect(call![0]).toContain("user_id=u1");
|
||||
});
|
||||
|
||||
test("appends page and page_size to URL as query params", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({
|
||||
user_id: "u1",
|
||||
api_version: "v2",
|
||||
page: 2,
|
||||
page_size: 25,
|
||||
});
|
||||
|
||||
const call = mock.mock.calls.find((c: [string, RequestInit]) =>
|
||||
c[0].includes("page="),
|
||||
);
|
||||
expect(call![0]).toContain("page=2");
|
||||
expect(call![0]).toContain("page_size=25");
|
||||
});
|
||||
|
||||
test("does not crash when called without options", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/", { status: 200, body: [] });
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
const result: Memory[] = await client.getAll();
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── update() ────────────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - update()", () => {
|
||||
@@ -312,7 +215,7 @@ describe("MemoryClient - deleteAll()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.deleteAll({ user_id: "u1" });
|
||||
await client.deleteAll({ userId: "u1" });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
(c: [string, RequestInit]) =>
|
||||
@@ -328,7 +231,7 @@ describe("MemoryClient - deleteAll()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.deleteAll({ user_id: "user@email.com" });
|
||||
await client.deleteAll({ userId: "user@email.com" });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
(c: [string, RequestInit]) =>
|
||||
|
||||
@@ -7,13 +7,7 @@ import {
|
||||
ValidationError,
|
||||
MemoryError,
|
||||
} from "../../common/exceptions";
|
||||
import {
|
||||
createMockFetch,
|
||||
TEST_API_KEY,
|
||||
TEST_HOST,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
installConsoleSuppression,
|
||||
@@ -55,24 +49,6 @@ describe("MemoryClient - Initialization", () => {
|
||||
expect(client.host).toBe(TEST_HOST);
|
||||
});
|
||||
|
||||
test("sets organizationId from constructor", () => {
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
expect(client.organizationId).toBe(TEST_ORG_ID);
|
||||
});
|
||||
|
||||
test("sets projectId from constructor", () => {
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
expect(client.projectId).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("sets Authorization header with Token prefix", () => {
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
|
||||
@@ -87,20 +63,6 @@ describe("MemoryClient - Initialization", () => {
|
||||
// ─── Ping ────────────────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - ping()", () => {
|
||||
test("sets organizationId from ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBe(TEST_ORG_ID);
|
||||
});
|
||||
|
||||
test("sets projectId from ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
expect(client.projectId).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("sets telemetryId from user_email in response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
@@ -108,28 +70,6 @@ describe("MemoryClient - ping()", () => {
|
||||
expect(client.telemetryId).toBe("test@example.com");
|
||||
});
|
||||
|
||||
test("preserves constructor organizationId over ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: "my_org",
|
||||
projectId: "my_proj",
|
||||
});
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBe("my_org");
|
||||
});
|
||||
|
||||
test("preserves constructor projectId over ping response", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: "my_org",
|
||||
projectId: "my_proj",
|
||||
});
|
||||
await client.ping();
|
||||
expect(client.projectId).toBe("my_proj");
|
||||
});
|
||||
|
||||
test("throws AuthenticationError on 401 response", async () => {
|
||||
const { AuthenticationError } = await import("../../common/exceptions");
|
||||
const responses = new Map<string, { status: number; body: unknown }>();
|
||||
|
||||
@@ -4,12 +4,7 @@
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import { Feedback } from "../mem0.types";
|
||||
import {
|
||||
createMockFetch,
|
||||
TEST_API_KEY,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import { createMockFetch, TEST_API_KEY } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
findFetchCall,
|
||||
@@ -22,7 +17,7 @@ installConsoleSuppression();
|
||||
// ─── getProject() ───────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - getProject()", () => {
|
||||
test("throws when organizationId and projectId not set", async () => {
|
||||
test("throws when organizationId and projectId not set (ping returns no org)", async () => {
|
||||
const responses = new Map<string, { status: number; body: unknown }>();
|
||||
responses.set("/v1/ping/", { status: 200, body: { status: "ok" } });
|
||||
global.fetch = createMockFetch(responses);
|
||||
@@ -47,11 +42,9 @@ describe("MemoryClient - getProject()", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
// org/project come from ping mock response
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
await client.getProject({ fields: ["custom_instructions"] });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
@@ -74,11 +67,8 @@ describe("MemoryClient - updateProject()", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
await client.updateProject({
|
||||
custom_instructions: "Updated instructions",
|
||||
});
|
||||
@@ -95,11 +85,8 @@ describe("MemoryClient - updateProject()", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.ping();
|
||||
await client.updateProject({
|
||||
custom_instructions: "Updated instructions",
|
||||
});
|
||||
@@ -124,9 +111,9 @@ describe("MemoryClient - feedback()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.feedback({
|
||||
memory_id: "mem_123",
|
||||
memoryId: "mem_123",
|
||||
feedback: Feedback.POSITIVE,
|
||||
feedback_reason: "Very helpful",
|
||||
feedbackReason: "Very helpful",
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v1/feedback/", "POST");
|
||||
@@ -143,9 +130,9 @@ describe("MemoryClient - feedback()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.feedback({
|
||||
memory_id: "mem_123",
|
||||
memoryId: "mem_123",
|
||||
feedback: Feedback.POSITIVE,
|
||||
feedback_reason: "Very helpful",
|
||||
feedbackReason: "Very helpful",
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v1/feedback/", "POST");
|
||||
@@ -161,11 +148,7 @@ describe("MemoryClient - feedback()", () => {
|
||||
describe("MemoryClient - Memory Exports", () => {
|
||||
test("createMemoryExport throws when missing filters or schema", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await expect(
|
||||
client.createMemoryExport({
|
||||
filters: null as never,
|
||||
@@ -182,11 +165,7 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.createMemoryExport({
|
||||
schema: { fields: ["memory", "user_id"] },
|
||||
filters: { user_id: "u1" },
|
||||
@@ -195,39 +174,11 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
expect(findFetchCall(mock, "/v1/exports/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("createMemoryExport attaches org_id and project_id to body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/exports/", {
|
||||
status: 200,
|
||||
body: { message: "Created", id: "exp_1" },
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.createMemoryExport({
|
||||
schema: { fields: ["memory"] },
|
||||
filters: { user_id: "u1" },
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v1/exports/", "POST");
|
||||
const body = getFetchBody(call!);
|
||||
expect(body.org_id).toBe(TEST_ORG_ID);
|
||||
expect(body.project_id).toBe(TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
test("getMemoryExport throws when missing both id and filters", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
|
||||
"Missing memory_export_id or filters",
|
||||
"Missing memoryExportId or filters",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -239,12 +190,8 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
await client.getMemoryExport({ memory_export_id: "exp_123" });
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getMemoryExport({ memoryExportId: "exp_123" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* MemoryClient unit tests — search (v1/v2 routing, filters).
|
||||
* MemoryClient unit tests — search (v2 default, filters).
|
||||
* Tests verify request construction, not mock response echo.
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
@@ -15,60 +15,52 @@ import {
|
||||
installConsoleSuppression();
|
||||
|
||||
describe("MemoryClient - search()", () => {
|
||||
test("sends POST to /v1/memories/search/ by default", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", { user_id: "u1" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/memories/search/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("includes query in request body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).query).toBe("What is my name?");
|
||||
});
|
||||
|
||||
test("includes user_id in request body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { user_id: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).user_id).toBe("u1");
|
||||
});
|
||||
|
||||
test("uses /v2/memories/search/ when api_version=v2", async () => {
|
||||
test("sends POST to /v2/memories/search/ by default", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { user_id: "u1", api_version: "v2" });
|
||||
await client.search("What is my name?", {
|
||||
filters: { userId: "u1" },
|
||||
});
|
||||
|
||||
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
test("passes filters through to the v2 API body", async () => {
|
||||
test("includes query in request body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", {
|
||||
filters: { userId: "u1" },
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).query).toBe("What is my name?");
|
||||
});
|
||||
|
||||
test("passes filters through to the API body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { filters: { userId: "u1" } });
|
||||
|
||||
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
|
||||
});
|
||||
|
||||
test("passes complex OR filters through to the API body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("query", {
|
||||
api_version: "v2",
|
||||
filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] },
|
||||
});
|
||||
|
||||
@@ -81,7 +73,7 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
test("does not crash when called without options", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
@@ -91,12 +83,12 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
test("handles empty results array", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/search/", { status: 200, body: [] });
|
||||
extra.set("/v2/memories/search/", { status: 200, body: [] });
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
const result: Memory[] = await client.search("nonexistent query", {
|
||||
user_id: "u1",
|
||||
filters: { userId: "u1" },
|
||||
});
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
/**
|
||||
* MemoryClient unit tests — users, deleteUser, deleteUsers.
|
||||
* MemoryClient unit tests — users, deleteUser.
|
||||
* Tests verify entity type routing and request construction.
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import {
|
||||
createMockUser,
|
||||
createMockAllUsers,
|
||||
TEST_API_KEY,
|
||||
TEST_ORG_ID,
|
||||
TEST_PROJECT_ID,
|
||||
} from "./helpers";
|
||||
import { createMockUser, createMockAllUsers, TEST_API_KEY } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
findFetchCall,
|
||||
@@ -40,111 +34,6 @@ describe("MemoryClient - users()", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── deleteUsers() ──────────────────────────────────────
|
||||
|
||||
describe("MemoryClient - deleteUsers()", () => {
|
||||
function createClientWithMockedAxios() {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
const axiosDeleteMock = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { message: "Deleted" } });
|
||||
client.client.delete = axiosDeleteMock;
|
||||
return { client, axiosDeleteMock };
|
||||
}
|
||||
|
||||
test("routes user_id to DELETE /v2/entities/user/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ user_id: "u1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith("/v2/entities/user/u1/", {
|
||||
params: expect.objectContaining({
|
||||
org_id: TEST_ORG_ID,
|
||||
project_id: TEST_PROJECT_ID,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
test("routes agent_id to DELETE /v2/entities/agent/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ agent_id: "agent_1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith(
|
||||
"/v2/entities/agent/agent_1/",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
test("routes app_id to DELETE /v2/entities/app/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ app_id: "app_1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith(
|
||||
"/v2/entities/app/app_1/",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
test("routes run_id to DELETE /v2/entities/run/:name/", async () => {
|
||||
const { client, axiosDeleteMock } = createClientWithMockedAxios();
|
||||
await client.deleteUsers({ run_id: "run_1" });
|
||||
|
||||
expect(axiosDeleteMock).toHaveBeenCalledWith(
|
||||
"/v2/entities/run/run_1/",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
test("returns 'Entity deleted successfully.' for single entity", async () => {
|
||||
const { client } = createClientWithMockedAxios();
|
||||
const result = await client.deleteUsers({ user_id: "u1" });
|
||||
expect(result.message).toBe("Entity deleted successfully.");
|
||||
});
|
||||
|
||||
test("returns 'All users, agents, apps and runs deleted.' when no params given", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/entities/", {
|
||||
status: 200,
|
||||
body: createMockAllUsers([createMockUser({ name: "u1", type: "user" })]),
|
||||
});
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
client.client.delete = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { message: "Deleted" } });
|
||||
|
||||
const result = await client.deleteUsers();
|
||||
expect(result.message).toBe("All users, agents, apps and runs deleted.");
|
||||
});
|
||||
|
||||
test("throws when no entities exist to delete", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/entities/", {
|
||||
status: 200,
|
||||
body: createMockAllUsers([]),
|
||||
});
|
||||
setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
client.client.delete = jest.fn();
|
||||
|
||||
await expect(client.deleteUsers()).rejects.toThrow("No entities to delete");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── deleteUser() (deprecated) ──────────────────────────
|
||||
|
||||
describe("MemoryClient - deleteUser() (deprecated)", () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import { MemoryClient } from "../mem0";
|
||||
import { WebhookEvent } from "../mem0.types";
|
||||
import { TEST_API_KEY, TEST_ORG_ID, TEST_PROJECT_ID } from "./helpers";
|
||||
import { TEST_API_KEY } from "./helpers";
|
||||
import {
|
||||
setupMockFetch,
|
||||
findFetchCall,
|
||||
@@ -23,8 +23,6 @@ function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
|
||||
function createClient() {
|
||||
return new MemoryClient({
|
||||
apiKey: TEST_API_KEY,
|
||||
organizationId: TEST_ORG_ID,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,14 +98,6 @@ describe("MemoryClient - createWebhook", () => {
|
||||
expect(body.eventTypes).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain projectId", async () => {
|
||||
const mock = await callCreate();
|
||||
const body = getFetchBody(
|
||||
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
|
||||
);
|
||||
expect(body.projectId).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain webhookId", async () => {
|
||||
const mock = await callCreate();
|
||||
const body = getFetchBody(
|
||||
@@ -173,22 +163,6 @@ describe("MemoryClient - updateWebhook", () => {
|
||||
expect(body.eventTypes).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain project_id", async () => {
|
||||
const mock = await callUpdate();
|
||||
const body = getFetchBody(
|
||||
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
|
||||
);
|
||||
expect(body.project_id).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain projectId", async () => {
|
||||
const mock = await callUpdate();
|
||||
const body = getFetchBody(
|
||||
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
|
||||
);
|
||||
expect(body.projectId).toBeUndefined();
|
||||
});
|
||||
|
||||
test("body does not contain webhookId", async () => {
|
||||
const mock = await callUpdate();
|
||||
const body = getFetchBody(
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Converts a camelCase string to snake_case.
|
||||
*/
|
||||
export function camelToSnake(str: string): string {
|
||||
// Skip all-uppercase keys (e.g. OR, AND, NOT — logical operators)
|
||||
if (str === str.toUpperCase()) return str;
|
||||
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a snake_case string to camelCase.
|
||||
*/
|
||||
function snakeToCamel(str: string): string {
|
||||
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts all keys of an object from camelCase to snake_case.
|
||||
* Used for converting user-facing camelCase params to API snake_case payloads.
|
||||
*/
|
||||
export function camelToSnakeKeys(obj: any): any {
|
||||
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
|
||||
if (Array.isArray(obj)) return obj.map(camelToSnakeKeys);
|
||||
if (obj instanceof Date) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
camelToSnake(key),
|
||||
camelToSnakeKeys(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts all keys of an object from snake_case to camelCase.
|
||||
* Used for converting API snake_case responses to user-facing camelCase.
|
||||
*/
|
||||
export function snakeToCamelKeys(obj: any): any {
|
||||
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
|
||||
if (Array.isArray(obj)) return obj.map(snakeToCamelKeys);
|
||||
if (obj instanceof Date) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
snakeToCamel(key),
|
||||
snakeToCamelKeys(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import { MemoryClient } from "mem0ai";
|
||||
import type { Memory, MemoryOptions, SearchOptions } from "mem0ai";
|
||||
import type {
|
||||
Memory,
|
||||
AddMemoryOptions,
|
||||
SearchMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
} from "mem0ai";
|
||||
|
||||
import {
|
||||
InputValues,
|
||||
@@ -102,10 +107,6 @@ export const mem0MemoryToMessages = (memories: Memory[]): BaseMessage[] => {
|
||||
export interface ClientOptions {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
organizationName?: string;
|
||||
projectName?: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +118,7 @@ export interface Mem0MemoryInput extends BaseChatMemoryInput {
|
||||
apiKey: string;
|
||||
humanPrefix?: string;
|
||||
aiPrefix?: string;
|
||||
memoryOptions?: MemoryOptions | SearchOptions;
|
||||
memoryOptions?: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
|
||||
mem0Options?: ClientOptions;
|
||||
separateMessages?: boolean;
|
||||
}
|
||||
@@ -160,7 +161,7 @@ export class Mem0Memory extends BaseChatMemory implements Mem0MemoryInput {
|
||||
|
||||
mem0Client: InstanceType<typeof MemoryClient>;
|
||||
|
||||
memoryOptions: MemoryOptions | SearchOptions;
|
||||
memoryOptions: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
|
||||
|
||||
mem0Options: ClientOptions;
|
||||
|
||||
|
||||
@@ -26,15 +26,6 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
|
||||
modelProperties: undefined,
|
||||
},
|
||||
},
|
||||
enableGraph: false,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
url: process.env.NEO4J_URL || "neo4j://localhost:7687",
|
||||
username: process.env.NEO4J_USERNAME || "neo4j",
|
||||
password: process.env.NEO4J_PASSWORD || "password",
|
||||
},
|
||||
},
|
||||
historyStore: {
|
||||
provider: "sqlite",
|
||||
config: {
|
||||
|
||||
@@ -131,11 +131,10 @@ export class ConfigManager {
|
||||
userConfig.historyDbPath ||
|
||||
userConfig.historyStore?.config?.historyDbPath ||
|
||||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
|
||||
customPrompt: userConfig.customPrompt,
|
||||
graphStore: {
|
||||
...DEFAULT_MEMORY_CONFIG.graphStore,
|
||||
...userConfig.graphStore,
|
||||
},
|
||||
customInstructions: userConfig.customInstructions,
|
||||
graphStore: userConfig.graphStore
|
||||
? { ...userConfig.graphStore }
|
||||
: undefined,
|
||||
historyStore: (() => {
|
||||
const defaultHistoryStore = DEFAULT_MEMORY_CONFIG.historyStore!;
|
||||
const historyProvider =
|
||||
@@ -158,7 +157,6 @@ export class ConfigManager {
|
||||
})(),
|
||||
disableHistory:
|
||||
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
|
||||
enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
|
||||
};
|
||||
|
||||
// Validate the merged config
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface GraphStoreConfig {
|
||||
provider: string;
|
||||
config: Neo4jConfig;
|
||||
llm?: LLMConfig;
|
||||
customPrompt?: string;
|
||||
customInstructions?: string;
|
||||
}
|
||||
|
||||
export function validateNeo4jConfig(config: Neo4jConfig): void {
|
||||
|
||||
@@ -136,7 +136,7 @@ export class MemoryGraph {
|
||||
};
|
||||
}
|
||||
|
||||
async search(query: string, filters: Record<string, any>, limit = 100) {
|
||||
async search(query: string, filters: Record<string, any>, topK = 100) {
|
||||
const entityTypeMap = await this._retrieveNodesFromData(query, filters);
|
||||
const searchOutput = await this._searchGraphDb(
|
||||
Object.keys(entityTypeMap),
|
||||
@@ -178,7 +178,7 @@ export class MemoryGraph {
|
||||
}
|
||||
}
|
||||
|
||||
async getAll(filters: Record<string, any>, limit = 100) {
|
||||
async getAll(filters: Record<string, any>, topK = 100) {
|
||||
const session = this.graph.session();
|
||||
try {
|
||||
const result = await session.run(
|
||||
@@ -187,7 +187,7 @@ export class MemoryGraph {
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT toInteger($limit)
|
||||
`,
|
||||
{ user_id: filters["userId"], limit: Math.floor(Number(limit)) },
|
||||
{ user_id: filters["userId"], limit: Math.floor(Number(topK)) },
|
||||
);
|
||||
|
||||
const finalResults = result.records.map((record) => ({
|
||||
@@ -253,7 +253,7 @@ export class MemoryGraph {
|
||||
entityTypeMap: Record<string, string>,
|
||||
) {
|
||||
let messages;
|
||||
if (this.config.graphStore?.customPrompt) {
|
||||
if (this.config.graphStore?.customInstructions) {
|
||||
messages = [
|
||||
{
|
||||
role: "system",
|
||||
@@ -263,7 +263,7 @@ export class MemoryGraph {
|
||||
filters["userId"],
|
||||
).replace(
|
||||
"CUSTOM_PROMPT",
|
||||
`4. ${this.config.graphStore.customPrompt}`,
|
||||
`4. ${this.config.graphStore.customInstructions}`,
|
||||
) + "\nPlease provide your response in JSON format.",
|
||||
},
|
||||
{ role: "user", content: data },
|
||||
@@ -307,7 +307,7 @@ export class MemoryGraph {
|
||||
private async _searchGraphDb(
|
||||
nodeList: string[],
|
||||
filters: Record<string, any>,
|
||||
limit = 100,
|
||||
topK = 100,
|
||||
): Promise<SearchOutput[]> {
|
||||
const resultRelations: SearchOutput[] = [];
|
||||
const session = this.graph.session();
|
||||
@@ -344,7 +344,7 @@ export class MemoryGraph {
|
||||
n_embedding: nEmbedding,
|
||||
threshold: this.threshold,
|
||||
user_id: filters["userId"],
|
||||
limit: Math.floor(Number(limit)),
|
||||
limit: Math.floor(Number(topK)),
|
||||
});
|
||||
|
||||
resultRelations.push(
|
||||
|
||||
@@ -39,7 +39,7 @@ import { captureClientEvent } from "../utils/telemetry";
|
||||
|
||||
export class Memory {
|
||||
private config: MemoryConfig;
|
||||
private customPrompt: string | undefined;
|
||||
private customInstructions: string | undefined;
|
||||
private embedder: Embedder;
|
||||
private vectorStore!: VectorStore;
|
||||
private llm: LLM;
|
||||
@@ -47,7 +47,6 @@ export class Memory {
|
||||
private collectionName: string | undefined;
|
||||
private apiVersion: string;
|
||||
private graphMemory?: MemoryGraph;
|
||||
private enableGraph: boolean;
|
||||
telemetryId: string;
|
||||
private _initPromise: Promise<void>;
|
||||
private _initError?: Error;
|
||||
@@ -56,7 +55,7 @@ export class Memory {
|
||||
// Merge and validate config
|
||||
this.config = ConfigManager.mergeConfig(config);
|
||||
|
||||
this.customPrompt = this.config.customPrompt;
|
||||
this.customInstructions = this.config.customInstructions;
|
||||
this.embedder = EmbedderFactory.create(
|
||||
this.config.embedder.provider,
|
||||
this.config.embedder.config,
|
||||
@@ -79,11 +78,10 @@ export class Memory {
|
||||
|
||||
this.collectionName = this.config.vectorStore.config.collectionName;
|
||||
this.apiVersion = this.config.version || "v1.0";
|
||||
this.enableGraph = this.config.enableGraph || false;
|
||||
this.telemetryId = "anonymous";
|
||||
|
||||
// Initialize graph memory if configured
|
||||
if (this.enableGraph && this.config.graphStore) {
|
||||
// Initialize graph memory if graphStore is configured
|
||||
if (this.config.graphStore) {
|
||||
this.graphMemory = new MemoryGraph(this.config);
|
||||
}
|
||||
|
||||
@@ -159,7 +157,6 @@ export class Memory {
|
||||
api_version: this.apiVersion,
|
||||
client_type: "Memory",
|
||||
collection_name: this.collectionName,
|
||||
enable_graph: this.enableGraph,
|
||||
});
|
||||
} catch (error) {}
|
||||
}
|
||||
@@ -293,11 +290,11 @@ export class Memory {
|
||||
}
|
||||
const parsedMessages = messages.map((m) => m.content).join("\n");
|
||||
|
||||
const [systemPrompt, userPrompt] = this.customPrompt
|
||||
const [systemPrompt, userPrompt] = this.customInstructions
|
||||
? [
|
||||
this.customPrompt.toLowerCase().includes("json")
|
||||
? this.customPrompt
|
||||
: `${this.customPrompt}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
|
||||
this.customInstructions.toLowerCase().includes("json")
|
||||
? this.customInstructions
|
||||
: `${this.customInstructions}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
|
||||
`Input:\n${parsedMessages}`,
|
||||
]
|
||||
: getFactRetrievalMessages(parsedMessages);
|
||||
@@ -478,10 +475,10 @@ export class Memory {
|
||||
await this._ensureInitialized();
|
||||
await this._captureEvent("search", {
|
||||
query_length: query.length,
|
||||
limit: config.limit,
|
||||
topK: config.topK,
|
||||
has_filters: !!config.filters,
|
||||
});
|
||||
const { userId, agentId, runId, limit = 100, filters = {} } = config;
|
||||
const { userId, agentId, runId, topK = 100, filters = {} } = config;
|
||||
|
||||
if (userId) filters.userId = userId;
|
||||
if (agentId) filters.agentId = agentId;
|
||||
@@ -497,7 +494,7 @@ export class Memory {
|
||||
const queryEmbedding = await this.embedder.embed(query);
|
||||
const memories = await this.vectorStore.search(
|
||||
queryEmbedding,
|
||||
limit,
|
||||
topK,
|
||||
filters,
|
||||
);
|
||||
|
||||
@@ -642,19 +639,19 @@ export class Memory {
|
||||
async getAll(config: GetAllMemoryOptions): Promise<SearchResult> {
|
||||
await this._ensureInitialized();
|
||||
await this._captureEvent("get_all", {
|
||||
limit: config.limit,
|
||||
topK: config.topK,
|
||||
has_user_id: !!config.userId,
|
||||
has_agent_id: !!config.agentId,
|
||||
has_run_id: !!config.runId,
|
||||
});
|
||||
const { userId, agentId, runId, limit = 100 } = config;
|
||||
const { userId, agentId, runId, topK = 100 } = config;
|
||||
|
||||
const filters: SearchFilters = {};
|
||||
if (userId) filters.userId = userId;
|
||||
if (agentId) filters.agentId = agentId;
|
||||
if (runId) filters.runId = runId;
|
||||
|
||||
const [memories] = await this.vectorStore.list(filters, limit);
|
||||
const [memories] = await this.vectorStore.list(filters, topK);
|
||||
|
||||
const excludedKeys = new Set([
|
||||
"userId",
|
||||
|
||||
@@ -14,12 +14,12 @@ export interface AddMemoryOptions extends Entity {
|
||||
}
|
||||
|
||||
export interface SearchMemoryOptions extends Entity {
|
||||
limit?: number;
|
||||
topK?: number;
|
||||
filters?: SearchFilters;
|
||||
}
|
||||
|
||||
export interface GetAllMemoryOptions extends Entity {
|
||||
limit?: number;
|
||||
topK?: number;
|
||||
}
|
||||
|
||||
export interface DeleteAllMemoryOptions extends Entity {}
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
expect(cfg.historyStore!.provider).toBe("sqlite");
|
||||
expect(cfg.historyStore!.config.historyDbPath).toBe("memory.db");
|
||||
expect(cfg.disableHistory).toBe(false);
|
||||
expect(cfg.enableGraph).toBe(false);
|
||||
expect(cfg.graphStore).toBeUndefined();
|
||||
});
|
||||
|
||||
it("workaround: explicit historyStore still works (existing user pattern)", () => {
|
||||
@@ -106,7 +106,6 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
|
||||
it("graphStore config passes through unchanged", () => {
|
||||
const cfg = ConfigManager.mergeConfig({
|
||||
enableGraph: true,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
@@ -116,15 +115,14 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(cfg.enableGraph).toBe(true);
|
||||
expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687");
|
||||
});
|
||||
|
||||
it("customPrompt passes through unchanged", () => {
|
||||
it("customInstructions passes through unchanged", () => {
|
||||
const cfg = ConfigManager.mergeConfig({
|
||||
customPrompt: "You are a helpful assistant",
|
||||
customInstructions: "You are a helpful assistant",
|
||||
});
|
||||
expect(cfg.customPrompt).toBe("You are a helpful assistant");
|
||||
expect(cfg.customInstructions).toBe("You are a helpful assistant");
|
||||
});
|
||||
|
||||
it("version override passes through unchanged", () => {
|
||||
|
||||
@@ -60,7 +60,7 @@ export interface GraphStoreConfig {
|
||||
provider: string;
|
||||
config: Neo4jConfig;
|
||||
llm?: LLMConfig;
|
||||
customPrompt?: string;
|
||||
customInstructions?: string;
|
||||
}
|
||||
|
||||
export interface MemoryConfig {
|
||||
@@ -80,9 +80,8 @@ export interface MemoryConfig {
|
||||
historyStore?: HistoryStoreConfig;
|
||||
disableHistory?: boolean;
|
||||
historyDbPath?: string;
|
||||
customPrompt?: string;
|
||||
customInstructions?: string;
|
||||
graphStore?: GraphStoreConfig;
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface MemoryItem {
|
||||
@@ -148,8 +147,7 @@ export const MemoryConfigSchema = z.object({
|
||||
}),
|
||||
}),
|
||||
historyDbPath: z.string().optional(),
|
||||
customPrompt: z.string().optional(),
|
||||
enableGraph: z.boolean().optional(),
|
||||
customInstructions: z.string().optional(),
|
||||
graphStore: z
|
||||
.object({
|
||||
provider: z.string(),
|
||||
@@ -164,7 +162,7 @@ export const MemoryConfigSchema = z.object({
|
||||
config: z.record(z.string(), z.any()),
|
||||
})
|
||||
.optional(),
|
||||
customPrompt: z.string().optional(),
|
||||
customInstructions: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
historyStore: z
|
||||
|
||||
@@ -331,7 +331,7 @@ export class AzureAISearch implements VectorStore {
|
||||
*/
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const filterExpression = filters
|
||||
@@ -341,7 +341,7 @@ export class AzureAISearch implements VectorStore {
|
||||
const vectorQuery: VectorizedQuery<any> = {
|
||||
kind: "vector",
|
||||
vector: query,
|
||||
kNearestNeighborsCount: limit,
|
||||
kNearestNeighborsCount: topK,
|
||||
fields: ["vector"],
|
||||
};
|
||||
|
||||
@@ -355,7 +355,7 @@ export class AzureAISearch implements VectorStore {
|
||||
filterMode: this.vectorFilterMode as any,
|
||||
},
|
||||
filter: filterExpression,
|
||||
top: limit,
|
||||
top: topK,
|
||||
searchFields: ["payload"],
|
||||
});
|
||||
} else {
|
||||
@@ -366,7 +366,7 @@ export class AzureAISearch implements VectorStore {
|
||||
filterMode: this.vectorFilterMode as any,
|
||||
},
|
||||
filter: filterExpression,
|
||||
top: limit,
|
||||
top: topK,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ export class AzureAISearch implements VectorStore {
|
||||
*/
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const filterExpression = filters
|
||||
? this.buildFilterExpression(filters)
|
||||
@@ -509,7 +509,7 @@ export class AzureAISearch implements VectorStore {
|
||||
|
||||
const searchResults = await this.searchClient.search("*", {
|
||||
filter: filterExpression,
|
||||
top: limit,
|
||||
top: topK,
|
||||
});
|
||||
|
||||
const results: VectorStoreResult[] = [];
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface VectorStore {
|
||||
): Promise<void>;
|
||||
search(
|
||||
query: number[],
|
||||
limit?: number,
|
||||
topK?: number,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]>;
|
||||
get(vectorId: string): Promise<VectorStoreResult | null>;
|
||||
@@ -21,7 +21,7 @@ export interface VectorStore {
|
||||
deleteCol(): Promise<void>;
|
||||
list(
|
||||
filters?: SearchFilters,
|
||||
limit?: number,
|
||||
topK?: number,
|
||||
): Promise<[VectorStoreResult[], number]>;
|
||||
getUserId(): Promise<string>;
|
||||
setUserId(userId: string): Promise<void>;
|
||||
|
||||
@@ -101,7 +101,7 @@ export class LangchainVectorStore implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters, // filters parameter is received but will be ignored
|
||||
): Promise<VectorStoreResult[]> {
|
||||
if (this.dimension && query.length !== this.dimension) {
|
||||
@@ -119,7 +119,7 @@ export class LangchainVectorStore implements VectorStore {
|
||||
// Call similaritySearchVectorWithScore WITHOUT the filter argument
|
||||
const results = await this.lcStore.similaritySearchVectorWithScore(
|
||||
query,
|
||||
limit,
|
||||
topK,
|
||||
// Do not pass lcFilter here
|
||||
);
|
||||
|
||||
@@ -192,7 +192,7 @@ export class LangchainVectorStore implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
// No standard list method in Langchain core interface.
|
||||
console.error(
|
||||
|
||||
@@ -100,7 +100,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 10,
|
||||
topK: number = 10,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
if (query.length !== this.dimension) {
|
||||
@@ -136,7 +136,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
}
|
||||
|
||||
results.sort((a, b) => (b.score || 0) - (a.score || 0));
|
||||
return results.slice(0, limit);
|
||||
return results.slice(0, topK);
|
||||
}
|
||||
|
||||
async get(vectorId: string): Promise<VectorStoreResult | null> {
|
||||
@@ -179,7 +179,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const rows = this.db.prepare(`SELECT * FROM vectors`).all() as any[];
|
||||
const results: VectorStoreResult[] = [];
|
||||
@@ -206,7 +206,7 @@ export class MemoryVectorStore implements VectorStore {
|
||||
}
|
||||
}
|
||||
|
||||
return [results.slice(0, limit), results.length];
|
||||
return [results.slice(0, topK), results.length];
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
|
||||
@@ -164,12 +164,12 @@ export class PGVector implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const filterConditions: string[] = [];
|
||||
const queryVector = `[${query.join(",")}]`; // Format query vector as string with square brackets
|
||||
const filterValues: any[] = [queryVector, limit];
|
||||
const filterValues: any[] = [queryVector, topK];
|
||||
let filterIndex = 3;
|
||||
|
||||
if (filters) {
|
||||
@@ -254,7 +254,7 @@ export class PGVector implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const filterConditions: string[] = [];
|
||||
const filterValues: any[] = [];
|
||||
@@ -286,7 +286,7 @@ export class PGVector implements VectorStore {
|
||||
${filterClause}
|
||||
`;
|
||||
|
||||
filterValues.push(limit); // Add limit as the last parameter
|
||||
filterValues.push(topK); // Add limit as the last parameter
|
||||
|
||||
const [listResult, countResult] = await Promise.all([
|
||||
this.client.query(listQuery, filterValues),
|
||||
|
||||
@@ -139,14 +139,14 @@ export class Qdrant implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const queryFilter = this.createFilter(filters);
|
||||
const results = await this.client.search(this.collectionName, {
|
||||
vector: query,
|
||||
filter: queryFilter,
|
||||
limit,
|
||||
limit: topK,
|
||||
});
|
||||
|
||||
return results.map((hit) => ({
|
||||
@@ -198,10 +198,10 @@ export class Qdrant implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const scrollRequest = {
|
||||
limit,
|
||||
limit: topK,
|
||||
filter: this.createFilter(filters),
|
||||
with_payload: true,
|
||||
with_vectors: false,
|
||||
|
||||
@@ -359,7 +359,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
|
||||
@@ -391,14 +391,14 @@ export class RedisDB implements VectorStore {
|
||||
DIALECT: 2,
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: limit,
|
||||
size: topK,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const results = (await this.client.ft.search(
|
||||
this.indexName,
|
||||
`${filterExpr} =>[KNN ${limit} @embedding $vec AS __vector_score]`,
|
||||
`${filterExpr} =>[KNN ${topK} @embedding $vec AS __vector_score]`,
|
||||
searchOptions,
|
||||
)) as unknown as RedisSearchResult;
|
||||
|
||||
@@ -598,7 +598,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
|
||||
const filterExpr = snakeFilters
|
||||
@@ -613,7 +613,7 @@ export class RedisDB implements VectorStore {
|
||||
SORTDIR: "DESC",
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: limit,
|
||||
size: topK,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -231,13 +231,13 @@ See the SQL migration instructions in the code comments.`,
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
try {
|
||||
const rpcQuery: VectorQueryParams = {
|
||||
query_embedding: query,
|
||||
match_count: limit,
|
||||
match_count: topK,
|
||||
};
|
||||
|
||||
if (filters) {
|
||||
@@ -336,13 +336,13 @@ See the SQL migration instructions in the code comments.`,
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
try {
|
||||
let query = this.client
|
||||
.from(this.tableName)
|
||||
.select("*", { count: "exact" })
|
||||
.limit(limit);
|
||||
.limit(topK);
|
||||
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
|
||||
@@ -76,7 +76,7 @@ export class VectorizeDB implements VectorStore {
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
try {
|
||||
@@ -87,7 +87,7 @@ export class VectorizeDB implements VectorStore {
|
||||
vector: query,
|
||||
filter: filters,
|
||||
returnMetadata: "all",
|
||||
topK: limit,
|
||||
topK: topK,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -197,7 +197,7 @@ export class VectorizeDB implements VectorStore {
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 20,
|
||||
topK: number = 20,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
try {
|
||||
const result = await this.client?.vectorize.indexes.query(
|
||||
@@ -206,7 +206,7 @@ export class VectorizeDB implements VectorStore {
|
||||
account_id: this.accountId,
|
||||
vector: Array(this.dimensions).fill(0), // Dummy vector for listing
|
||||
filter: filters,
|
||||
topK: limit,
|
||||
topK: topK,
|
||||
returnMetadata: "all",
|
||||
},
|
||||
);
|
||||
|
||||
@@ -305,13 +305,15 @@ describe("_establishNodesRelationsFromData", () => {
|
||||
expect(systemContent).toContain("test-user");
|
||||
expect(systemContent).not.toContain("USER_ID");
|
||||
// CUSTOM_PROMPT placeholder stays when no custom prompt is configured
|
||||
// (only replaced when config.graphStore.customPrompt is set)
|
||||
// (only replaced when config.graphStore.customInstructions is set)
|
||||
});
|
||||
|
||||
it("appends JSON format suffix and custom prompt when configured", async () => {
|
||||
mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] });
|
||||
|
||||
const mg = graph({ customPrompt: "Focus on food relationships only." });
|
||||
const mg = graph({
|
||||
customInstructions: "Focus on food relationships only.",
|
||||
});
|
||||
await mg._establishNodesRelationsFromData("data", FILTERS, {});
|
||||
|
||||
const [messages] = mockGenerateResponse.mock.calls[0];
|
||||
|
||||
+206
-227
@@ -2,13 +2,22 @@ import hashlib
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
|
||||
from mem0.client.project import AsyncProject, Project
|
||||
from mem0.client.types import (
|
||||
AddMemoryOptions,
|
||||
DeleteAllMemoryOptions,
|
||||
GetAllMemoryOptions,
|
||||
ProjectUpdateOptions,
|
||||
SearchMemoryOptions,
|
||||
UpdateMemoryOptions,
|
||||
)
|
||||
from mem0.client.utils import api_error_handler
|
||||
|
||||
# Exception classes are referenced in docstrings only
|
||||
from mem0.memory.setup import get_user_id, setup_config
|
||||
from mem0.memory.telemetry import capture_client_event
|
||||
@@ -31,8 +40,6 @@ class MemoryClient:
|
||||
api_key (str): The API key for authenticating with the Mem0 API.
|
||||
host (str): The base URL for the Mem0 API.
|
||||
client (httpx.Client): The HTTP client used for making API requests.
|
||||
org_id (str, optional): Organization ID.
|
||||
project_id (str, optional): Project ID.
|
||||
user_id (str): Unique identifier for the user.
|
||||
"""
|
||||
|
||||
@@ -40,8 +47,6 @@ class MemoryClient:
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
host: Optional[str] = None,
|
||||
org_id: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
client: Optional[httpx.Client] = None,
|
||||
):
|
||||
"""Initialize the MemoryClient.
|
||||
@@ -52,8 +57,6 @@ class MemoryClient:
|
||||
environment variable.
|
||||
host: The base URL for the Mem0 API. Defaults to
|
||||
"https://api.mem0.ai".
|
||||
org_id: The ID of the organization.
|
||||
project_id: The ID of the project.
|
||||
client: A custom httpx.Client instance. If provided, it will be
|
||||
used instead of creating a new one. Note that base_url and
|
||||
headers will be set/overridden as needed.
|
||||
@@ -63,8 +66,8 @@ class MemoryClient:
|
||||
"""
|
||||
self.api_key = api_key or os.getenv("MEM0_API_KEY")
|
||||
self.host = host or "https://api.mem0.ai"
|
||||
self.org_id = org_id
|
||||
self.project_id = project_id
|
||||
self.org_id = None
|
||||
self.project_id = None
|
||||
self.user_id = get_user_id()
|
||||
|
||||
if not self.api_key:
|
||||
@@ -128,15 +131,16 @@ class MemoryClient:
|
||||
raise ValueError(f"Error: {error_message}")
|
||||
|
||||
@api_error_handler
|
||||
def add(self, messages, **kwargs) -> Dict[str, Any]:
|
||||
def add(self, messages, options: Optional[AddMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||
"""Add a new memory.
|
||||
|
||||
Args:
|
||||
messages: A list of message dictionaries, a single message dictionary,
|
||||
or a string. If a string is provided, it will be converted to
|
||||
a user message.
|
||||
options: Typed options for the add operation (AddMemoryOptions).
|
||||
**kwargs: Additional parameters such as user_id, agent_id, app_id,
|
||||
metadata, filters, async_mode.
|
||||
metadata, filters.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the API response in v1.1 format.
|
||||
@@ -149,24 +153,19 @@ class MemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
# Handle different message input formats (align with OSS behavior)
|
||||
if isinstance(messages, str):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
elif isinstance(messages, dict):
|
||||
messages = [messages]
|
||||
elif not isinstance(messages, list):
|
||||
raise ValueError(
|
||||
f"messages must be str, dict, or list[dict], got {type(messages).__name__}"
|
||||
)
|
||||
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
|
||||
# Set async_mode to True by default, but allow user override
|
||||
if "async_mode" not in kwargs:
|
||||
kwargs["async_mode"] = True
|
||||
raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
|
||||
|
||||
# Force v1.1 format for all add operations
|
||||
kwargs["output_format"] = "v1.1"
|
||||
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
payload = self._prepare_payload(messages, kwargs)
|
||||
response = self.client.post("/v1/memories/", json=payload)
|
||||
response.raise_for_status()
|
||||
@@ -200,10 +199,11 @@ class MemoryClient:
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
def get_all(self, **kwargs) -> Dict[str, Any]:
|
||||
def get_all(self, options: Optional[GetAllMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||
"""Retrieve all memories, with optional filtering.
|
||||
|
||||
Args:
|
||||
options: Typed options for the get_all operation (GetAllMemoryOptions).
|
||||
**kwargs: Optional parameters for filtering (user_id, agent_id,
|
||||
app_id, top_k, page, page_size).
|
||||
|
||||
@@ -218,8 +218,8 @@ class MemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
params.pop("async_mode", None)
|
||||
|
||||
if "page" in params and "page_size" in params:
|
||||
query_params = {
|
||||
@@ -236,7 +236,6 @@ class MemoryClient:
|
||||
"client.get_all",
|
||||
self,
|
||||
{
|
||||
"api_version": "v2",
|
||||
"keys": list(kwargs.keys()),
|
||||
"sync_type": "sync",
|
||||
},
|
||||
@@ -249,13 +248,13 @@ class MemoryClient:
|
||||
return result
|
||||
|
||||
@api_error_handler
|
||||
def search(self, query: str, **kwargs) -> Dict[str, Any]:
|
||||
def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||
"""Search memories based on a query.
|
||||
|
||||
Args:
|
||||
query: The search query string.
|
||||
**kwargs: Additional parameters such as user_id, agent_id, app_id,
|
||||
top_k, filters.
|
||||
options: Typed options for the search operation (SearchMemoryOptions).
|
||||
**kwargs: Additional parameters such as filters, top_k, rerank.
|
||||
|
||||
Returns:
|
||||
A dictionary containing search results in v1.1 format: {"results": [...]}
|
||||
@@ -268,11 +267,9 @@ class MemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
payload = {"query": query}
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
params.pop("async_mode", None)
|
||||
|
||||
payload.update(params)
|
||||
payload = {"query": query, **params}
|
||||
|
||||
response = self.client.post("/v2/memories/search/", json=payload)
|
||||
response.raise_for_status()
|
||||
@@ -282,7 +279,6 @@ class MemoryClient:
|
||||
"client.search",
|
||||
self,
|
||||
{
|
||||
"api_version": "v2",
|
||||
"keys": list(kwargs.keys()),
|
||||
"sync_type": "sync",
|
||||
},
|
||||
@@ -298,36 +294,32 @@ class MemoryClient:
|
||||
def update(
|
||||
self,
|
||||
memory_id: str,
|
||||
text: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
timestamp: Optional[Union[int, float, str]] = None,
|
||||
options: Optional[UpdateMemoryOptions] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update a memory by ID.
|
||||
"""Update a memory by ID.
|
||||
|
||||
Args:
|
||||
memory_id (str): Memory ID.
|
||||
text (str, optional): New content to update the memory with.
|
||||
metadata (dict, optional): Metadata to update in the memory.
|
||||
timestamp (int, float, or str, optional): Unix epoch timestamp or ISO 8601 string.
|
||||
memory_id: The ID of the memory to update.
|
||||
options: Typed options (UpdateMemoryOptions) with text, metadata,
|
||||
and/or timestamp fields.
|
||||
**kwargs: Alternatively pass text, metadata, timestamp as keyword args.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: The response from the server.
|
||||
|
||||
Example:
|
||||
>>> client.update(memory_id="mem_123", text="Likes to play tennis on weekends")
|
||||
>>> client.update(memory_id="mem_123", timestamp="2025-01-15T12:00:00Z")
|
||||
"""
|
||||
if text is None and metadata is None and timestamp is None:
|
||||
raise ValueError("At least one of text, metadata, or timestamp must be provided for update.")
|
||||
Raises:
|
||||
ValueError: If none of text, metadata, or timestamp are provided.
|
||||
|
||||
payload = {}
|
||||
if text is not None:
|
||||
payload["text"] = text
|
||||
if metadata is not None:
|
||||
payload["metadata"] = metadata
|
||||
if timestamp is not None:
|
||||
payload["timestamp"] = timestamp
|
||||
Example:
|
||||
>>> client.update("mem_123", UpdateMemoryOptions(text="Updated text"))
|
||||
>>> client.update("mem_123", text="Updated text")
|
||||
"""
|
||||
payload = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
|
||||
if not payload:
|
||||
raise ValueError("At least one of text, metadata, or timestamp must be provided for update.")
|
||||
|
||||
capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
params = self._prepare_params()
|
||||
@@ -360,10 +352,11 @@ class MemoryClient:
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
def delete_all(self, **kwargs) -> Dict[str, str]:
|
||||
def delete_all(self, options: Optional[DeleteAllMemoryOptions] = None, **kwargs) -> Dict[str, str]:
|
||||
"""Delete all memories, with optional filtering.
|
||||
|
||||
Args:
|
||||
options: Typed options for the delete_all operation (DeleteAllMemoryOptions).
|
||||
**kwargs: Optional parameters for filtering (user_id, agent_id,
|
||||
app_id).
|
||||
|
||||
@@ -378,6 +371,7 @@ class MemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
response = self.client.delete("/v1/memories/", params=params)
|
||||
response.raise_for_status()
|
||||
@@ -667,13 +661,10 @@ class MemoryClient:
|
||||
@api_error_handler
|
||||
def update_project(
|
||||
self,
|
||||
options: Optional[ProjectUpdateOptions] = None,
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
inclusion_prompt: Optional[str] = None,
|
||||
exclusion_prompt: Optional[str] = None,
|
||||
memory_depth: Optional[str] = None,
|
||||
usecase_setting: Optional[str] = None,
|
||||
multilingual: Optional[bool] = None,
|
||||
@@ -681,67 +672,51 @@ class MemoryClient:
|
||||
"""Update the project settings.
|
||||
|
||||
Args:
|
||||
custom_instructions: New instructions for the project
|
||||
custom_categories: New categories for the project
|
||||
retrieval_criteria: New retrieval criteria for the project
|
||||
enable_graph: Enable or disable the graph for the project
|
||||
version: Version of the project
|
||||
inclusion_prompt: Inclusion prompt for the project
|
||||
exclusion_prompt: Exclusion prompt for the project
|
||||
memory_depth: Memory depth for the project
|
||||
usecase_setting: Usecase setting for the project
|
||||
multilingual: Whether to use the input language for memory storage and retrieval
|
||||
options: Typed options for the update operation (ProjectUpdateOptions).
|
||||
custom_instructions: New instructions for the project.
|
||||
custom_categories: New categories for the project.
|
||||
retrieval_criteria: New retrieval criteria for the project.
|
||||
memory_depth: Memory depth for the project.
|
||||
usecase_setting: Usecase setting for the project.
|
||||
multilingual: Whether to use the input language for memory storage and retrieval.
|
||||
|
||||
Returns:
|
||||
Dictionary containing the API response.
|
||||
|
||||
Raises:
|
||||
ValidationError: If the input data is invalid.
|
||||
AuthenticationError: If authentication fails.
|
||||
RateLimitError: If rate limits are exceeded.
|
||||
MemoryQuotaExceededError: If memory quota is exceeded.
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
ValueError: If org_id or project_id are not set.
|
||||
ValueError: If org_id or project_id are not set, or no update fields provided.
|
||||
"""
|
||||
logger.warning(
|
||||
"update_project() method is going to be deprecated in version v1.0 of the package. Please use the client.project.update() method instead."
|
||||
"update_project() method is going to be deprecated in version v1.0 of the package. "
|
||||
"Please use the client.project.update() method instead."
|
||||
)
|
||||
if not (self.org_id and self.project_id):
|
||||
raise ValueError("org_id and project_id must be set to update instructions or categories")
|
||||
|
||||
if (
|
||||
custom_instructions is None
|
||||
and custom_categories is None
|
||||
and retrieval_criteria is None
|
||||
and enable_graph is None
|
||||
and version is None
|
||||
and inclusion_prompt is None
|
||||
and exclusion_prompt is None
|
||||
and memory_depth is None
|
||||
and usecase_setting is None
|
||||
and multilingual is None
|
||||
):
|
||||
kwargs = {
|
||||
**(options.model_dump(exclude_unset=True) if options else {}),
|
||||
**{
|
||||
k: v
|
||||
for k, v in {
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"memory_depth": memory_depth,
|
||||
"usecase_setting": usecase_setting,
|
||||
"multilingual": multilingual,
|
||||
}.items()
|
||||
if v is not None
|
||||
},
|
||||
}
|
||||
|
||||
if not kwargs:
|
||||
raise ValueError(
|
||||
"Currently we only support updating custom_instructions or "
|
||||
"custom_categories or retrieval_criteria, so you must "
|
||||
"provide at least one of them"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
"inclusion_prompt": inclusion_prompt,
|
||||
"exclusion_prompt": exclusion_prompt,
|
||||
"memory_depth": memory_depth,
|
||||
"usecase_setting": usecase_setting,
|
||||
"multilingual": multilingual,
|
||||
}
|
||||
)
|
||||
payload = self._prepare_params(kwargs)
|
||||
response = self.client.patch(
|
||||
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
|
||||
json=payload,
|
||||
@@ -750,19 +725,7 @@ class MemoryClient:
|
||||
capture_client_event(
|
||||
"client.update_project",
|
||||
self,
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
"inclusion_prompt": inclusion_prompt,
|
||||
"exclusion_prompt": exclusion_prompt,
|
||||
"memory_depth": memory_depth,
|
||||
"usecase_setting": usecase_setting,
|
||||
"multilingual": multilingual,
|
||||
"sync_type": "sync",
|
||||
},
|
||||
{**kwargs, "sync_type": "sync"},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -910,7 +873,7 @@ class MemoryClient:
|
||||
|
||||
response = self.client.post("/v1/feedback/", json=data)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.feedback", self, data, {"sync_type": "sync"})
|
||||
capture_client_event("client.feedback", self, {**data, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
def _prepare_payload(self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -937,20 +900,12 @@ class MemoryClient:
|
||||
|
||||
Returns:
|
||||
A dictionary containing the prepared parameters.
|
||||
|
||||
Raises:
|
||||
ValueError: If either org_id or project_id is provided but not both.
|
||||
"""
|
||||
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
# Add org_id and project_id if both are available
|
||||
if self.org_id and self.project_id:
|
||||
kwargs["org_id"] = self.org_id
|
||||
kwargs["project_id"] = self.project_id
|
||||
elif self.org_id or self.project_id:
|
||||
raise ValueError("Please provide both org_id and project_id")
|
||||
# org_id and project_id are resolved from API key — not injected into params
|
||||
|
||||
return {k: v for k, v in kwargs.items() if v is not None}
|
||||
|
||||
@@ -966,8 +921,6 @@ class AsyncMemoryClient:
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
host: Optional[str] = None,
|
||||
org_id: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
client: Optional[httpx.AsyncClient] = None,
|
||||
):
|
||||
"""Initialize the AsyncMemoryClient.
|
||||
@@ -978,8 +931,6 @@ class AsyncMemoryClient:
|
||||
environment variable.
|
||||
host: The base URL for the Mem0 API. Defaults to
|
||||
"https://api.mem0.ai".
|
||||
org_id: The ID of the organization.
|
||||
project_id: The ID of the project.
|
||||
client: A custom httpx.AsyncClient instance. If provided, it will
|
||||
be used instead of creating a new one. Note that base_url
|
||||
and headers will be set/overridden as needed.
|
||||
@@ -989,8 +940,8 @@ class AsyncMemoryClient:
|
||||
"""
|
||||
self.api_key = api_key or os.getenv("MEM0_API_KEY")
|
||||
self.host = host or "https://api.mem0.ai"
|
||||
self.org_id = org_id
|
||||
self.project_id = project_id
|
||||
self.org_id = None
|
||||
self.project_id = None
|
||||
self.user_id = get_user_id()
|
||||
|
||||
if not self.api_key:
|
||||
@@ -1085,20 +1036,12 @@ class AsyncMemoryClient:
|
||||
|
||||
Returns:
|
||||
A dictionary containing the prepared parameters.
|
||||
|
||||
Raises:
|
||||
ValueError: If either org_id or project_id is provided but not both.
|
||||
"""
|
||||
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
# Add org_id and project_id if both are available
|
||||
if self.org_id and self.project_id:
|
||||
kwargs["org_id"] = self.org_id
|
||||
kwargs["project_id"] = self.project_id
|
||||
elif self.org_id or self.project_id:
|
||||
raise ValueError("Please provide both org_id and project_id")
|
||||
# org_id and project_id are resolved from API key — not injected into params
|
||||
|
||||
return {k: v for k, v in kwargs.items() if v is not None}
|
||||
|
||||
@@ -1109,25 +1052,41 @@ class AsyncMemoryClient:
|
||||
await self.async_client.aclose()
|
||||
|
||||
@api_error_handler
|
||||
async def add(self, messages, **kwargs) -> Dict[str, Any]:
|
||||
async def add(self, messages, options: Optional[AddMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||
"""Add a new memory.
|
||||
|
||||
Args:
|
||||
messages: A list of message dictionaries, a single message dictionary,
|
||||
or a string. If a string is provided, it will be converted to
|
||||
a user message.
|
||||
options: Typed options for the add operation (AddMemoryOptions).
|
||||
**kwargs: Additional parameters such as user_id, agent_id, app_id,
|
||||
metadata, filters.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the API response in v1.1 format.
|
||||
|
||||
Raises:
|
||||
ValidationError: If the input data is invalid.
|
||||
AuthenticationError: If authentication fails.
|
||||
RateLimitError: If rate limits are exceeded.
|
||||
MemoryQuotaExceededError: If memory quota is exceeded.
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
# Handle different message input formats (align with OSS behavior)
|
||||
if isinstance(messages, str):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
elif isinstance(messages, dict):
|
||||
messages = [messages]
|
||||
elif not isinstance(messages, list):
|
||||
raise ValueError(
|
||||
f"messages must be str, dict, or list[dict], got {type(messages).__name__}"
|
||||
)
|
||||
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
|
||||
# Set async_mode to True by default, but allow user override
|
||||
if "async_mode" not in kwargs:
|
||||
kwargs["async_mode"] = True
|
||||
raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
|
||||
|
||||
# Force v1.1 format for all add operations
|
||||
kwargs["output_format"] = "v1.1"
|
||||
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
payload = self._prepare_payload(messages, kwargs)
|
||||
response = await self.async_client.post("/v1/memories/", json=payload)
|
||||
response.raise_for_status()
|
||||
@@ -1145,9 +1104,26 @@ class AsyncMemoryClient:
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
async def get_all(self, **kwargs) -> Dict[str, Any]:
|
||||
async def get_all(self, options: Optional[GetAllMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||
"""Retrieve all memories, with optional filtering.
|
||||
|
||||
Args:
|
||||
options: Typed options for the get_all operation (GetAllMemoryOptions).
|
||||
**kwargs: Optional parameters for filtering (filters, page, page_size).
|
||||
|
||||
Returns:
|
||||
A dictionary containing memories in v1.1 format: {"results": [...]}
|
||||
|
||||
Raises:
|
||||
ValidationError: If the input data is invalid.
|
||||
AuthenticationError: If authentication fails.
|
||||
RateLimitError: If rate limits are exceeded.
|
||||
MemoryQuotaExceededError: If memory quota is exceeded.
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
params.pop("async_mode", None)
|
||||
|
||||
if "page" in params and "page_size" in params:
|
||||
query_params = {
|
||||
@@ -1164,7 +1140,6 @@ class AsyncMemoryClient:
|
||||
"client.get_all",
|
||||
self,
|
||||
{
|
||||
"api_version": "v2",
|
||||
"keys": list(kwargs.keys()),
|
||||
"sync_type": "async",
|
||||
},
|
||||
@@ -1177,12 +1152,28 @@ class AsyncMemoryClient:
|
||||
return result
|
||||
|
||||
@api_error_handler
|
||||
async def search(self, query: str, **kwargs) -> Dict[str, Any]:
|
||||
payload = {"query": query}
|
||||
params = self._prepare_params(kwargs)
|
||||
params.pop("async_mode", None)
|
||||
async def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||
"""Search memories based on a query.
|
||||
|
||||
payload.update(params)
|
||||
Args:
|
||||
query: The search query string.
|
||||
options: Typed options for the search operation (SearchMemoryOptions).
|
||||
**kwargs: Additional parameters such as filters, top_k, rerank.
|
||||
|
||||
Returns:
|
||||
A dictionary containing search results in v1.1 format: {"results": [...]}
|
||||
|
||||
Raises:
|
||||
ValidationError: If the input data is invalid.
|
||||
AuthenticationError: If authentication fails.
|
||||
RateLimitError: If rate limits are exceeded.
|
||||
MemoryQuotaExceededError: If memory quota is exceeded.
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
payload = {"query": query, **params}
|
||||
|
||||
response = await self.async_client.post("/v2/memories/search/", json=payload)
|
||||
response.raise_for_status()
|
||||
@@ -1192,7 +1183,6 @@ class AsyncMemoryClient:
|
||||
"client.search",
|
||||
self,
|
||||
{
|
||||
"api_version": "v2",
|
||||
"keys": list(kwargs.keys()),
|
||||
"sync_type": "async",
|
||||
},
|
||||
@@ -1208,36 +1198,32 @@ class AsyncMemoryClient:
|
||||
async def update(
|
||||
self,
|
||||
memory_id: str,
|
||||
text: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
timestamp: Optional[Union[int, float, str]] = None,
|
||||
options: Optional[UpdateMemoryOptions] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update a memory by ID asynchronously.
|
||||
"""Update a memory by ID asynchronously.
|
||||
|
||||
Args:
|
||||
memory_id (str): Memory ID.
|
||||
text (str, optional): New content to update the memory with.
|
||||
metadata (dict, optional): Metadata to update in the memory.
|
||||
timestamp (int, float, or str, optional): Unix epoch timestamp or ISO 8601 string.
|
||||
memory_id: The ID of the memory to update.
|
||||
options: Typed options (UpdateMemoryOptions) with text, metadata,
|
||||
and/or timestamp fields.
|
||||
**kwargs: Alternatively pass text, metadata, timestamp as keyword args.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: The response from the server.
|
||||
|
||||
Example:
|
||||
>>> await client.update(memory_id="mem_123", text="Likes to play tennis on weekends")
|
||||
>>> await client.update(memory_id="mem_123", timestamp="2025-01-15T12:00:00Z")
|
||||
"""
|
||||
if text is None and metadata is None and timestamp is None:
|
||||
raise ValueError("At least one of text, metadata, or timestamp must be provided for update.")
|
||||
Raises:
|
||||
ValueError: If none of text, metadata, or timestamp are provided.
|
||||
|
||||
payload = {}
|
||||
if text is not None:
|
||||
payload["text"] = text
|
||||
if metadata is not None:
|
||||
payload["metadata"] = metadata
|
||||
if timestamp is not None:
|
||||
payload["timestamp"] = timestamp
|
||||
Example:
|
||||
>>> await client.update("mem_123", UpdateMemoryOptions(text="Updated text"))
|
||||
>>> await client.update("mem_123", text="Updated text")
|
||||
"""
|
||||
payload = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
|
||||
if not payload:
|
||||
raise ValueError("At least one of text, metadata, or timestamp must be provided for update.")
|
||||
|
||||
capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
params = self._prepare_params()
|
||||
@@ -1270,10 +1256,11 @@ class AsyncMemoryClient:
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
async def delete_all(self, **kwargs) -> Dict[str, str]:
|
||||
async def delete_all(self, options: Optional[DeleteAllMemoryOptions] = None, **kwargs) -> Dict[str, str]:
|
||||
"""Delete all memories, with optional filtering.
|
||||
|
||||
Args:
|
||||
options: Typed options for the delete_all operation (DeleteAllMemoryOptions).
|
||||
**kwargs: Optional parameters for filtering (user_id, agent_id, app_id).
|
||||
|
||||
Returns:
|
||||
@@ -1287,6 +1274,7 @@ class AsyncMemoryClient:
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
"""
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
response = await self.async_client.delete("/v1/memories/", params=params)
|
||||
response.raise_for_status()
|
||||
@@ -1554,63 +1542,62 @@ class AsyncMemoryClient:
|
||||
@api_error_handler
|
||||
async def update_project(
|
||||
self,
|
||||
options: Optional[ProjectUpdateOptions] = None,
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
memory_depth: Optional[str] = None,
|
||||
usecase_setting: Optional[str] = None,
|
||||
multilingual: Optional[bool] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Update the project settings.
|
||||
|
||||
Args:
|
||||
custom_instructions: New instructions for the project
|
||||
custom_categories: New categories for the project
|
||||
retrieval_criteria: New retrieval criteria for the project
|
||||
enable_graph: Enable or disable the graph for the project
|
||||
version: Version of the project
|
||||
multilingual: Whether to use the input language for memory storage and retrieval
|
||||
options: Typed options for the update operation (ProjectUpdateOptions).
|
||||
custom_instructions: New instructions for the project.
|
||||
custom_categories: New categories for the project.
|
||||
retrieval_criteria: New retrieval criteria for the project.
|
||||
memory_depth: Memory depth for the project.
|
||||
usecase_setting: Usecase setting for the project.
|
||||
multilingual: Whether to use the input language for memory storage and retrieval.
|
||||
|
||||
Returns:
|
||||
Dictionary containing the API response.
|
||||
|
||||
Raises:
|
||||
ValidationError: If the input data is invalid.
|
||||
AuthenticationError: If authentication fails.
|
||||
RateLimitError: If rate limits are exceeded.
|
||||
MemoryQuotaExceededError: If memory quota is exceeded.
|
||||
NetworkError: If network connectivity issues occur.
|
||||
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
|
||||
ValueError: If org_id or project_id are not set.
|
||||
ValueError: If org_id or project_id are not set, or no update fields provided.
|
||||
"""
|
||||
logger.warning(
|
||||
"update_project() method is going to be deprecated in version v1.0 of the package. Please use the client.project.update() method instead."
|
||||
"update_project() method is going to be deprecated in version v1.0 of the package. "
|
||||
"Please use the client.project.update() method instead."
|
||||
)
|
||||
if not (self.org_id and self.project_id):
|
||||
raise ValueError("org_id and project_id must be set to update instructions or categories")
|
||||
|
||||
if (
|
||||
custom_instructions is None
|
||||
and custom_categories is None
|
||||
and retrieval_criteria is None
|
||||
and enable_graph is None
|
||||
and version is None
|
||||
and multilingual is None
|
||||
):
|
||||
kwargs = {
|
||||
**(options.model_dump(exclude_unset=True) if options else {}),
|
||||
**{
|
||||
k: v
|
||||
for k, v in {
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"memory_depth": memory_depth,
|
||||
"usecase_setting": usecase_setting,
|
||||
"multilingual": multilingual,
|
||||
}.items()
|
||||
if v is not None
|
||||
},
|
||||
}
|
||||
|
||||
if not kwargs:
|
||||
raise ValueError(
|
||||
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
|
||||
"Currently we only support updating custom_instructions or "
|
||||
"custom_categories or retrieval_criteria, so you must "
|
||||
"provide at least one of them"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
"multilingual": multilingual,
|
||||
}
|
||||
)
|
||||
payload = self._prepare_params(kwargs)
|
||||
response = await self.async_client.patch(
|
||||
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
|
||||
json=payload,
|
||||
@@ -1619,15 +1606,7 @@ class AsyncMemoryClient:
|
||||
capture_client_event(
|
||||
"client.update_project",
|
||||
self,
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
"multilingual": multilingual,
|
||||
"sync_type": "async",
|
||||
},
|
||||
{**kwargs, "sync_type": "async"},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -1764,5 +1743,5 @@ class AsyncMemoryClient:
|
||||
|
||||
response = await self.async_client.post("/v1/feedback/", json=data)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.feedback", self, data, {"sync_type": "async"})
|
||||
capture_client_event("client.feedback", self, {**data, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
+3
-14
@@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from mem0.client.utils import api_error_handler
|
||||
from mem0.memory.telemetry import capture_client_event
|
||||
|
||||
# Exception classes are referenced in docstrings only
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -177,7 +178,6 @@ class BaseProject(ABC):
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update project settings.
|
||||
@@ -186,7 +186,6 @@ class BaseProject(ABC):
|
||||
custom_instructions: New instructions for the project
|
||||
custom_categories: New categories for the project
|
||||
retrieval_criteria: New retrieval criteria for the project
|
||||
enable_graph: Enable or disable the graph for the project
|
||||
|
||||
Returns:
|
||||
Dictionary containing the API response.
|
||||
@@ -398,7 +397,6 @@ class Project(BaseProject):
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
multilingual: Optional[bool] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -408,7 +406,6 @@ class Project(BaseProject):
|
||||
custom_instructions: New instructions for the project
|
||||
custom_categories: New categories for the project
|
||||
retrieval_criteria: New retrieval criteria for the project
|
||||
enable_graph: Enable or disable the graph for the project
|
||||
multilingual: Whether to use the input language for memory storage and retrieval
|
||||
|
||||
Returns:
|
||||
@@ -425,13 +422,12 @@ class Project(BaseProject):
|
||||
custom_instructions is None
|
||||
and custom_categories is None
|
||||
and retrieval_criteria is None
|
||||
and enable_graph is None
|
||||
and multilingual is None
|
||||
):
|
||||
raise ValueError(
|
||||
"At least one parameter must be provided for update: "
|
||||
"custom_instructions, custom_categories, retrieval_criteria, "
|
||||
"enable_graph, multilingual"
|
||||
"multilingual"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
@@ -439,7 +435,6 @@ class Project(BaseProject):
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"multilingual": multilingual,
|
||||
}
|
||||
)
|
||||
@@ -455,7 +450,6 @@ class Project(BaseProject):
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"multilingual": multilingual,
|
||||
"sync_type": "sync",
|
||||
},
|
||||
@@ -720,7 +714,6 @@ class AsyncProject(BaseProject):
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
multilingual: Optional[bool] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -730,7 +723,6 @@ class AsyncProject(BaseProject):
|
||||
custom_instructions: New instructions for the project
|
||||
custom_categories: New categories for the project
|
||||
retrieval_criteria: New retrieval criteria for the project
|
||||
enable_graph: Enable or disable the graph for the project
|
||||
multilingual: Whether to use the input language for memory storage and retrieval
|
||||
|
||||
Returns:
|
||||
@@ -747,13 +739,12 @@ class AsyncProject(BaseProject):
|
||||
custom_instructions is None
|
||||
and custom_categories is None
|
||||
and retrieval_criteria is None
|
||||
and enable_graph is None
|
||||
and multilingual is None
|
||||
):
|
||||
raise ValueError(
|
||||
"At least one parameter must be provided for update: "
|
||||
"custom_instructions, custom_categories, retrieval_criteria, "
|
||||
"enable_graph, multilingual"
|
||||
"multilingual"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
@@ -761,7 +752,6 @@ class AsyncProject(BaseProject):
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"multilingual": multilingual,
|
||||
}
|
||||
)
|
||||
@@ -777,7 +767,6 @@ class AsyncProject(BaseProject):
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"multilingual": multilingual,
|
||||
"sync_type": "async",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Pydantic option models for MemoryClient methods.
|
||||
|
||||
These models provide IDE autocompletion, runtime validation, and type safety.
|
||||
Methods accept both typed options and **kwargs for backward compatibility.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class EntityOptions(BaseModel):
|
||||
"""Identity options for add/delete operations (top-level entity IDs)."""
|
||||
|
||||
user_id: Optional[str] = Field(default=None, description="The user ID to associate with the memory")
|
||||
agent_id: Optional[str] = Field(default=None, description="The agent ID to associate with the memory")
|
||||
app_id: Optional[str] = Field(default=None, description="The app ID to associate with the memory")
|
||||
run_id: Optional[str] = Field(default=None, description="The run ID to associate with the memory")
|
||||
|
||||
|
||||
class AddMemoryOptions(EntityOptions):
|
||||
"""Options for the add() method."""
|
||||
|
||||
metadata: Optional[Dict[str, Any]] = Field(default=None, description="Additional metadata for the memory")
|
||||
infer: Optional[bool] = Field(default=None, description="Whether to infer memories from the input")
|
||||
custom_categories: Optional[List[Dict[str, Any]]] = Field(
|
||||
default=None, description="Custom categories for memory classification"
|
||||
)
|
||||
custom_instructions: Optional[str] = Field(default=None, description="Custom instructions for fact extraction")
|
||||
timestamp: Optional[int] = Field(default=None, description="Unix timestamp for the memory")
|
||||
structured_data_schema: Optional[Dict[str, Any]] = Field(
|
||||
default=None, description="Schema for structured data extraction"
|
||||
)
|
||||
|
||||
|
||||
class SearchMemoryOptions(BaseModel):
|
||||
"""Options for the search() method.
|
||||
|
||||
Identity fields (user_id, agent_id, etc.) must be passed inside the
|
||||
``filters`` dict — the v2 API does not accept them at the top level.
|
||||
"""
|
||||
|
||||
filters: Optional[Dict[str, Any]] = Field(
|
||||
default=None, description="Filters for the search (e.g. {'user_id': '...'})"
|
||||
)
|
||||
metadata: Optional[Dict[str, Any]] = Field(default=None, description="Additional metadata for the search")
|
||||
top_k: Optional[int] = Field(default=None, description="Number of results to return")
|
||||
rerank: Optional[bool] = Field(default=None, description="Whether to rerank results")
|
||||
threshold: Optional[float] = Field(default=None, description="Minimum similarity score threshold")
|
||||
fields: Optional[List[str]] = Field(default=None, description="Fields to include in the response")
|
||||
categories: Optional[List[str]] = Field(default=None, description="Categories to filter by")
|
||||
|
||||
|
||||
class GetAllMemoryOptions(BaseModel):
|
||||
"""Options for the get_all() method.
|
||||
|
||||
Identity fields (user_id, agent_id, etc.) must be passed inside the
|
||||
``filters`` dict — the v2 API does not accept them at the top level.
|
||||
"""
|
||||
|
||||
filters: Optional[Dict[str, Any]] = Field(
|
||||
default=None, description="Filters for retrieval (e.g. {'user_id': '...'})"
|
||||
)
|
||||
page: Optional[int] = Field(default=None, description="Page number for pagination")
|
||||
page_size: Optional[int] = Field(default=None, description="Number of items per page")
|
||||
start_date: Optional[str] = Field(
|
||||
default=None, description="Filter memories created on or after this date (ISO 8601)"
|
||||
)
|
||||
end_date: Optional[str] = Field(
|
||||
default=None, description="Filter memories created on or before this date (ISO 8601)"
|
||||
)
|
||||
categories: Optional[List[str]] = Field(default=None, description="Categories to filter by")
|
||||
|
||||
|
||||
class DeleteAllMemoryOptions(EntityOptions):
|
||||
"""Options for the delete_all() method."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UpdateMemoryOptions(BaseModel):
|
||||
"""Options for the update() method."""
|
||||
|
||||
text: Optional[str] = Field(default=None, description="New text content for the memory")
|
||||
metadata: Optional[Dict[str, Any]] = Field(default=None, description="Updated metadata")
|
||||
timestamp: Optional[Union[int, float, str]] = Field(default=None, description="Updated timestamp")
|
||||
|
||||
|
||||
class ProjectUpdateOptions(BaseModel):
|
||||
"""Options for project update operations."""
|
||||
|
||||
custom_instructions: Optional[str] = Field(default=None, description="Custom instructions for fact extraction")
|
||||
custom_categories: Optional[List[Dict[str, Any]]] = Field(
|
||||
default=None, description="Custom categories for classification"
|
||||
)
|
||||
memory_depth: Optional[str] = Field(default=None, description="Memory depth configuration")
|
||||
usecase_setting: Optional[Any] = Field(default=None, description="Use case specific settings")
|
||||
multilingual: Optional[bool] = Field(default=None, description="Whether to enable multilingual support")
|
||||
retrieval_criteria: Optional[List[Any]] = Field(default=None, description="Criteria for memory retrieval")
|
||||
@@ -3,11 +3,11 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from mem0.configs.rerankers.config import RerankerConfig
|
||||
from mem0.embeddings.configs import EmbedderConfig
|
||||
from mem0.graphs.configs import GraphStoreConfig
|
||||
from mem0.llms.configs import LlmConfig
|
||||
from mem0.vector_stores.configs import VectorStoreConfig
|
||||
from mem0.configs.rerankers.config import RerankerConfig
|
||||
|
||||
# Set up the directory path
|
||||
home_dir = os.path.expanduser("~")
|
||||
@@ -56,8 +56,8 @@ class MemoryConfig(BaseModel):
|
||||
description="The version of the API",
|
||||
default="v1.1",
|
||||
)
|
||||
custom_fact_extraction_prompt: Optional[str] = Field(
|
||||
description="Custom prompt for the fact extraction",
|
||||
custom_instructions: Optional[str] = Field(
|
||||
description="Custom instructions for fact extraction",
|
||||
default=None,
|
||||
)
|
||||
custom_update_memory_prompt: Optional[str] = Field(
|
||||
|
||||
@@ -17,7 +17,7 @@ class ElasticsearchConfig(BaseModel):
|
||||
use_ssl: bool = Field(True, description="Use SSL for connection")
|
||||
auto_create_index: bool = Field(True, description="Automatically create index during initialization")
|
||||
custom_search_query: Optional[Callable[[List[float], int, Optional[Dict]], Dict]] = Field(
|
||||
None, description="Custom search query function. Parameters: (query, limit, filters) -> Dict"
|
||||
None, description="Custom search query function. Parameters: (query, top_k, filters) -> Dict"
|
||||
)
|
||||
headers: Optional[Dict[str, str]] = Field(None, description="Custom headers to include in requests")
|
||||
|
||||
|
||||
@@ -346,14 +346,14 @@ class NeptuneBase(ABC):
|
||||
):
|
||||
pass
|
||||
|
||||
def search(self, query, filters, limit=100):
|
||||
def search(self, query, filters, top_k=100):
|
||||
"""
|
||||
Search for memories and related graph data.
|
||||
|
||||
Args:
|
||||
query (str): Query to search for.
|
||||
filters (dict): A dictionary containing filters to be applied during the search.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing:
|
||||
@@ -438,13 +438,13 @@ class NeptuneBase(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_all(self, filters, limit=100):
|
||||
def get_all(self, filters, top_k=100):
|
||||
"""
|
||||
Retrieves all nodes and relationships from the graph database based on filtering criteria.
|
||||
|
||||
Args:
|
||||
filters (dict): A dictionary containing filters to be applied during the retrieval.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
Returns:
|
||||
list: A list of dictionaries, each containing:
|
||||
- 'contexts': The base data store response for each memory.
|
||||
@@ -452,7 +452,7 @@ class NeptuneBase(ABC):
|
||||
"""
|
||||
|
||||
# return all nodes and relationships
|
||||
query, params = self._get_all_cypher(filters, limit)
|
||||
query, params = self._get_all_cypher(filters, top_k)
|
||||
results = self.graph.query(query, params=params)
|
||||
|
||||
final_results = []
|
||||
@@ -470,13 +470,13 @@ class NeptuneBase(ABC):
|
||||
return final_results
|
||||
|
||||
@abstractmethod
|
||||
def _get_all_cypher(self, filters, limit):
|
||||
def _get_all_cypher(self, filters, top_k):
|
||||
"""
|
||||
Returns the OpenCypher query and parameters to get all edges/nodes in the memory store
|
||||
"""
|
||||
pass
|
||||
|
||||
def _search_graph_db(self, node_list, filters, limit=100):
|
||||
def _search_graph_db(self, node_list, filters, top_k=100):
|
||||
"""
|
||||
Search similar nodes among and their respective incoming and outgoing relations.
|
||||
"""
|
||||
@@ -484,14 +484,14 @@ class NeptuneBase(ABC):
|
||||
|
||||
for node in node_list:
|
||||
n_embedding = self.embedding_model.embed(node)
|
||||
cypher_query, params = self._search_graph_db_cypher(n_embedding, filters, limit)
|
||||
cypher_query, params = self._search_graph_db_cypher(n_embedding, filters, top_k)
|
||||
ans = self.graph.query(cypher_query, params=params)
|
||||
result_relations.extend(ans)
|
||||
|
||||
return result_relations
|
||||
|
||||
@abstractmethod
|
||||
def _search_graph_db_cypher(self, n_embedding, filters, limit):
|
||||
def _search_graph_db_cypher(self, n_embedding, filters, top_k):
|
||||
"""
|
||||
Returns the OpenCypher query and parameters to search for similar nodes in the memory store
|
||||
"""
|
||||
|
||||
@@ -380,7 +380,7 @@ class MemoryGraph(NeptuneBase):
|
||||
source_nodes = self.vector_store.search(
|
||||
query="",
|
||||
vectors=source_embedding,
|
||||
limit=self.vector_store_limit,
|
||||
top_k=self.vector_store_limit,
|
||||
filters={"user_id": user_id},
|
||||
)
|
||||
|
||||
@@ -413,7 +413,7 @@ class MemoryGraph(NeptuneBase):
|
||||
destination_nodes = self.vector_store.search(
|
||||
query="",
|
||||
vectors=destination_embedding,
|
||||
limit=self.vector_store_limit,
|
||||
top_k=self.vector_store_limit,
|
||||
filters={"user_id": user_id},
|
||||
)
|
||||
|
||||
@@ -455,12 +455,12 @@ class MemoryGraph(NeptuneBase):
|
||||
logger.debug(f"delete_all query={cypher}")
|
||||
return cypher, params
|
||||
|
||||
def _get_all_cypher(self, filters, limit):
|
||||
def _get_all_cypher(self, filters, top_k):
|
||||
"""
|
||||
Returns the OpenCypher query and parameters to get all edges/nodes in the memory store
|
||||
|
||||
:param filters: search filters
|
||||
:param limit: return limit
|
||||
:param top_k: return limit
|
||||
:return: str, dict
|
||||
"""
|
||||
|
||||
@@ -469,16 +469,16 @@ class MemoryGraph(NeptuneBase):
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT $limit
|
||||
"""
|
||||
params = {"user_id": filters["user_id"], "limit": limit}
|
||||
params = {"user_id": filters["user_id"], "limit": top_k}
|
||||
return cypher, params
|
||||
|
||||
def _search_graph_db_cypher(self, n_embedding, filters, limit):
|
||||
def _search_graph_db_cypher(self, n_embedding, filters, top_k):
|
||||
"""
|
||||
Returns the OpenCypher query and parameters to search for similar nodes in the memory store
|
||||
|
||||
:param n_embedding: node vector
|
||||
:param filters: search filters
|
||||
:param limit: return limit
|
||||
:param top_k: return limit
|
||||
:return: str, dict
|
||||
"""
|
||||
|
||||
@@ -486,7 +486,7 @@ class MemoryGraph(NeptuneBase):
|
||||
search_nodes = self.vector_store.search(
|
||||
query="",
|
||||
vectors=n_embedding,
|
||||
limit=self.vector_store_limit,
|
||||
top_k=self.vector_store_limit,
|
||||
filters=filters,
|
||||
)
|
||||
|
||||
@@ -504,7 +504,7 @@ class MemoryGraph(NeptuneBase):
|
||||
params = {
|
||||
"n_ids": ids,
|
||||
"user_id": filters["user_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
logger.debug(f"_search_graph_db\n query={cypher_query}")
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import logging
|
||||
from .base import NeptuneBase
|
||||
|
||||
try:
|
||||
from langchain_aws import NeptuneAnalyticsGraph
|
||||
from botocore.config import Config
|
||||
from langchain_aws import NeptuneAnalyticsGraph
|
||||
except ImportError:
|
||||
raise ImportError("langchain_aws is not installed. Please install it using 'make install_all'.")
|
||||
|
||||
@@ -412,12 +412,12 @@ class MemoryGraph(NeptuneBase):
|
||||
logger.debug(f"delete_all query={cypher}")
|
||||
return cypher, params
|
||||
|
||||
def _get_all_cypher(self, filters, limit):
|
||||
def _get_all_cypher(self, filters, top_k):
|
||||
"""
|
||||
Returns the OpenCypher query and parameters to get all edges/nodes in the memory store
|
||||
|
||||
:param filters: search filters
|
||||
:param limit: return limit
|
||||
:param top_k: return limit
|
||||
:return: str, dict
|
||||
"""
|
||||
|
||||
@@ -426,16 +426,16 @@ class MemoryGraph(NeptuneBase):
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT $limit
|
||||
"""
|
||||
params = {"user_id": filters["user_id"], "limit": limit}
|
||||
params = {"user_id": filters["user_id"], "limit": top_k}
|
||||
return cypher, params
|
||||
|
||||
def _search_graph_db_cypher(self, n_embedding, filters, limit):
|
||||
def _search_graph_db_cypher(self, n_embedding, filters, top_k):
|
||||
"""
|
||||
Returns the OpenCypher query and parameters to search for similar nodes in the memory store
|
||||
|
||||
:param n_embedding: node vector
|
||||
:param filters: search filters
|
||||
:param limit: return limit
|
||||
:param top_k: return limit
|
||||
:return: str, dict
|
||||
"""
|
||||
|
||||
@@ -468,7 +468,7 @@ class MemoryGraph(NeptuneBase):
|
||||
"n_embedding": n_embedding,
|
||||
"threshold": self.threshold,
|
||||
"user_id": filters["user_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
logger.debug(f"_search_graph_db\n query={cypher_query}")
|
||||
|
||||
|
||||
@@ -213,14 +213,14 @@ class MemoryGraph:
|
||||
|
||||
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
|
||||
|
||||
def search(self, query, filters, limit=100):
|
||||
def search(self, query, filters, top_k=100):
|
||||
"""
|
||||
Search for memories and related graph data.
|
||||
|
||||
Args:
|
||||
query (str): Query to search for.
|
||||
filters (dict): A dictionary containing filters to be applied during the search.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
list: A list of dicts with keys "source", "relationship", "destination".
|
||||
@@ -286,13 +286,13 @@ class MemoryGraph:
|
||||
)
|
||||
self.ag.commit()
|
||||
|
||||
def get_all(self, filters, limit=100):
|
||||
def get_all(self, filters, top_k=100):
|
||||
"""
|
||||
Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
|
||||
|
||||
Args:
|
||||
filters (dict): A dictionary containing filters to be applied during the retrieval.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
Returns:
|
||||
list: A list of dictionaries, each containing:
|
||||
- 'source': The source node name.
|
||||
@@ -308,7 +308,7 @@ class MemoryGraph:
|
||||
where_parts.extend(["n.run_id = %s", "m.run_id = %s"])
|
||||
params.extend([filters["run_id"], filters["run_id"]])
|
||||
where_clause = " AND ".join(where_parts)
|
||||
params.append(limit)
|
||||
params.append(top_k)
|
||||
|
||||
results = self._exec_cypher(
|
||||
f"MATCH (n)-[r]->(m) WHERE {where_clause} "
|
||||
@@ -408,7 +408,7 @@ class MemoryGraph:
|
||||
|
||||
# -- graph DB operations ---------------------------------------------------
|
||||
|
||||
def _search_graph_db(self, node_list, filters, limit=100):
|
||||
def _search_graph_db(self, node_list, filters, top_k=100):
|
||||
"""Search similar nodes and their respective incoming and outgoing relations."""
|
||||
result_relations = []
|
||||
|
||||
@@ -430,7 +430,7 @@ class MemoryGraph:
|
||||
rel_where = " AND ".join(rel_where_parts)
|
||||
|
||||
# For each similar node, fetch its relationships
|
||||
for sn in similar_nodes[:limit]:
|
||||
for sn in similar_nodes[:top_k]:
|
||||
node_name = sn["name"]
|
||||
similarity = sn["similarity"]
|
||||
|
||||
|
||||
@@ -93,14 +93,14 @@ class MemoryGraph:
|
||||
|
||||
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
|
||||
|
||||
def search(self, query, filters, limit=100):
|
||||
def search(self, query, filters, top_k=100):
|
||||
"""
|
||||
Search for memories and related graph data.
|
||||
|
||||
Args:
|
||||
query (str): Query to search for.
|
||||
filters (dict): A dictionary containing filters to be applied during the search.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing:
|
||||
@@ -171,18 +171,18 @@ class MemoryGraph:
|
||||
params["run_id"] = filters["run_id"]
|
||||
self.graph.query(cypher, params=params)
|
||||
|
||||
def get_all(self, filters, limit=100):
|
||||
def get_all(self, filters, top_k=100):
|
||||
"""
|
||||
Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
|
||||
Args:
|
||||
filters (dict): A dictionary containing filters to be applied during the retrieval.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
Returns:
|
||||
list: A list of dictionaries, each containing:
|
||||
- 'contexts': The base data store response for each memory.
|
||||
- 'entities': A list of strings representing the nodes and relationships
|
||||
"""
|
||||
params = {"user_id": filters["user_id"], "limit": limit}
|
||||
params = {"user_id": filters["user_id"], "limit": top_k}
|
||||
|
||||
# Build node properties based on filters
|
||||
node_props = ["user_id: $user_id"]
|
||||
@@ -291,7 +291,7 @@ class MemoryGraph:
|
||||
logger.debug(f"Extracted entities: {entities}")
|
||||
return entities
|
||||
|
||||
def _search_graph_db(self, node_list, filters, limit=100):
|
||||
def _search_graph_db(self, node_list, filters, top_k=100):
|
||||
"""Search similar nodes among and their respective incoming and outgoing relations."""
|
||||
result_relations = []
|
||||
|
||||
@@ -332,7 +332,7 @@ class MemoryGraph:
|
||||
"n_embedding": n_embedding,
|
||||
"threshold": self.threshold,
|
||||
"user_id": filters["user_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
if filters.get("agent_id"):
|
||||
params["agent_id"] = filters["agent_id"]
|
||||
|
||||
@@ -113,14 +113,14 @@ class MemoryGraph:
|
||||
|
||||
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
|
||||
|
||||
def search(self, query, filters, limit=5):
|
||||
def search(self, query, filters, top_k=5):
|
||||
"""
|
||||
Search for memories and related graph data.
|
||||
|
||||
Args:
|
||||
query (str): Query to search for.
|
||||
filters (dict): A dictionary containing filters to be applied during the search.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing:
|
||||
@@ -139,7 +139,7 @@ class MemoryGraph:
|
||||
bm25 = BM25Okapi(search_outputs_sequence)
|
||||
|
||||
tokenized_query = query.split(" ")
|
||||
reranked_results = bm25.get_top_n(tokenized_query, search_outputs_sequence, n=limit)
|
||||
reranked_results = bm25.get_top_n(tokenized_query, search_outputs_sequence, n=top_k)
|
||||
|
||||
search_results = []
|
||||
for item in reranked_results:
|
||||
@@ -191,12 +191,12 @@ class MemoryGraph:
|
||||
params["run_id"] = filters["run_id"]
|
||||
self.kuzu_execute(cypher, parameters=params)
|
||||
|
||||
def get_all(self, filters, limit=100):
|
||||
def get_all(self, filters, top_k=100):
|
||||
"""
|
||||
Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
|
||||
Args:
|
||||
filters (dict): A dictionary containing filters to be applied during the retrieval.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
Returns:
|
||||
list: A list of dictionaries, each containing:
|
||||
- 'contexts': The base data store response for each memory.
|
||||
@@ -205,7 +205,7 @@ class MemoryGraph:
|
||||
|
||||
params = {
|
||||
"user_id": filters["user_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
# Build node properties based on filters
|
||||
node_props = ["user_id: $user_id"]
|
||||
@@ -316,14 +316,14 @@ class MemoryGraph:
|
||||
logger.debug(f"Extracted entities: {entities}")
|
||||
return entities
|
||||
|
||||
def _search_graph_db(self, node_list, filters, limit=100, threshold=None):
|
||||
def _search_graph_db(self, node_list, filters, top_k=100, threshold=None):
|
||||
"""Search similar nodes among and their respective incoming and outgoing relations."""
|
||||
result_relations = []
|
||||
|
||||
params = {
|
||||
"threshold": threshold if threshold else self.threshold,
|
||||
"user_id": filters["user_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
# Build node properties for filtering
|
||||
node_props = ["user_id: $user_id"]
|
||||
@@ -364,7 +364,7 @@ class MemoryGraph:
|
||||
parameters=params))
|
||||
|
||||
# Kuzu does not support sort/limit over unions. Do it manually for now.
|
||||
result_relations.extend(sorted(results, key=lambda x: x["similarity"], reverse=True)[:limit])
|
||||
result_relations.extend(sorted(results, key=lambda x: x["similarity"], reverse=True)[:top_k])
|
||||
|
||||
return result_relations
|
||||
|
||||
|
||||
+54
-60
@@ -258,7 +258,7 @@ class Memory(MemoryBase):
|
||||
def __init__(self, config: MemoryConfig = MemoryConfig()):
|
||||
self.config = config
|
||||
|
||||
self.custom_fact_extraction_prompt = self.config.custom_fact_extraction_prompt
|
||||
self.custom_instructions = self.config.custom_instructions
|
||||
self.custom_update_memory_prompt = self.config.custom_update_memory_prompt
|
||||
self.embedding_model = EmbedderFactory.create(
|
||||
self.config.embedder.provider,
|
||||
@@ -281,12 +281,9 @@ class Memory(MemoryBase):
|
||||
config.reranker.config
|
||||
)
|
||||
|
||||
self.enable_graph = False
|
||||
|
||||
if self.config.graph_store.config:
|
||||
provider = self.config.graph_store.provider
|
||||
self.graph = GraphStoreFactory.create(provider, self.config)
|
||||
self.enable_graph = True
|
||||
else:
|
||||
self.graph = None
|
||||
if MEM0_TELEMETRY:
|
||||
@@ -477,7 +474,7 @@ class Memory(MemoryBase):
|
||||
vector_store_result = future1.result()
|
||||
graph_result = future2.result()
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
return {
|
||||
"results": vector_store_result,
|
||||
"relations": graph_result,
|
||||
@@ -525,8 +522,8 @@ class Memory(MemoryBase):
|
||||
|
||||
parsed_messages = parse_messages(messages)
|
||||
|
||||
if self.config.custom_fact_extraction_prompt:
|
||||
system_prompt = self.config.custom_fact_extraction_prompt
|
||||
if self.config.custom_instructions:
|
||||
system_prompt = self.config.custom_instructions
|
||||
user_prompt = f"Input:\n{parsed_messages}"
|
||||
else:
|
||||
# Determine if this should use agent memory extraction based on agent_id presence
|
||||
@@ -582,7 +579,7 @@ class Memory(MemoryBase):
|
||||
existing_memories = self.vector_store.search(
|
||||
query=new_mem,
|
||||
vectors=messages_embeddings,
|
||||
limit=5,
|
||||
top_k=5,
|
||||
filters=search_filters,
|
||||
)
|
||||
for mem in existing_memories:
|
||||
@@ -726,7 +723,7 @@ class Memory(MemoryBase):
|
||||
|
||||
def _add_to_graph(self, messages, filters):
|
||||
added_entities = []
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
if filters.get("user_id") is None:
|
||||
filters["user_id"] = "user"
|
||||
|
||||
@@ -785,7 +782,7 @@ class Memory(MemoryBase):
|
||||
agent_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
limit: int = 100,
|
||||
top_k: int = 100,
|
||||
):
|
||||
"""
|
||||
List all memories.
|
||||
@@ -797,7 +794,7 @@ class Memory(MemoryBase):
|
||||
filters (dict, optional): Additional custom key-value filters to apply to the search.
|
||||
These are merged with the ID-based scoping filters. For example,
|
||||
`filters={"actor_id": "some_user"}`.
|
||||
limit (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
top_k (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing a list of memories under the "results" key,
|
||||
@@ -815,13 +812,13 @@ class Memory(MemoryBase):
|
||||
|
||||
keys, encoded_ids = process_telemetry_filters(effective_filters)
|
||||
capture_event(
|
||||
"mem0.get_all", self, {"limit": limit, "keys": keys, "encoded_ids": encoded_ids, "sync_type": "sync"}
|
||||
"mem0.get_all", self, {"top_k": top_k, "keys": keys, "encoded_ids": encoded_ids, "sync_type": "sync"}
|
||||
)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future_memories = executor.submit(self._get_all_from_vector_store, effective_filters, limit)
|
||||
future_memories = executor.submit(self._get_all_from_vector_store, effective_filters, top_k)
|
||||
future_graph_entities = (
|
||||
executor.submit(self.graph.get_all, effective_filters, limit) if self.enable_graph else None
|
||||
executor.submit(self.graph.get_all, effective_filters, top_k) if self.graph else None
|
||||
)
|
||||
|
||||
concurrent.futures.wait(
|
||||
@@ -831,13 +828,13 @@ class Memory(MemoryBase):
|
||||
all_memories_result = future_memories.result()
|
||||
graph_entities_result = future_graph_entities.result() if future_graph_entities else None
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
return {"results": all_memories_result, "relations": graph_entities_result}
|
||||
|
||||
return {"results": all_memories_result}
|
||||
|
||||
def _get_all_from_vector_store(self, filters, limit):
|
||||
memories_result = self.vector_store.list(filters=filters, limit=limit)
|
||||
def _get_all_from_vector_store(self, filters, top_k):
|
||||
memories_result = self.vector_store.list(filters=filters, top_k=top_k)
|
||||
|
||||
# Handle different vector store return formats by inspecting first element
|
||||
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0:
|
||||
@@ -890,7 +887,7 @@ class Memory(MemoryBase):
|
||||
user_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
top_k: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
threshold: Optional[float] = None,
|
||||
rerank: bool = True,
|
||||
@@ -902,7 +899,7 @@ class Memory(MemoryBase):
|
||||
user_id (str, optional): ID of the user to search for. Defaults to None.
|
||||
agent_id (str, optional): ID of the agent to search for. Defaults to None.
|
||||
run_id (str, optional): ID of the run to search for. Defaults to None.
|
||||
limit (int, optional): Limit the number of results. Defaults to 100.
|
||||
top_k (int, optional): Maximum number of results to return. Defaults to 100.
|
||||
filters (dict, optional): Legacy filters to apply to the search. Defaults to None.
|
||||
threshold (float, optional): Minimum score for a memory to be included in the results. Defaults to None.
|
||||
filters (dict, optional): Enhanced metadata filtering with operators:
|
||||
@@ -947,7 +944,7 @@ class Memory(MemoryBase):
|
||||
"mem0.search",
|
||||
self,
|
||||
{
|
||||
"limit": limit,
|
||||
"top_k": top_k,
|
||||
"version": self.api_version,
|
||||
"keys": keys,
|
||||
"encoded_ids": encoded_ids,
|
||||
@@ -958,9 +955,9 @@ class Memory(MemoryBase):
|
||||
)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future_memories = executor.submit(self._search_vector_store, query, effective_filters, limit, threshold)
|
||||
future_memories = executor.submit(self._search_vector_store, query, effective_filters, top_k, threshold)
|
||||
future_graph_entities = (
|
||||
executor.submit(self.graph.search, query, effective_filters, limit) if self.enable_graph else None
|
||||
executor.submit(self.graph.search, query, effective_filters, top_k) if self.graph else None
|
||||
)
|
||||
|
||||
concurrent.futures.wait(
|
||||
@@ -973,12 +970,12 @@ class Memory(MemoryBase):
|
||||
# Apply reranking if enabled and reranker is available
|
||||
if rerank and self.reranker and original_memories:
|
||||
try:
|
||||
reranked_memories = self.reranker.rerank(query, original_memories, limit)
|
||||
reranked_memories = self.reranker.rerank(query, original_memories, top_k)
|
||||
original_memories = reranked_memories
|
||||
except Exception as e:
|
||||
logger.warning(f"Reranking failed, using original results: {e}")
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
return {"results": original_memories, "relations": graph_entities}
|
||||
|
||||
return {"results": original_memories}
|
||||
@@ -1079,9 +1076,9 @@ class Memory(MemoryBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _search_vector_store(self, query, filters, limit, threshold: Optional[float] = None):
|
||||
def _search_vector_store(self, query, filters, top_k, threshold: Optional[float] = None):
|
||||
embeddings = self.embedding_model.embed(query, "search")
|
||||
memories = self.vector_store.search(query=query, vectors=embeddings, limit=limit, filters=filters)
|
||||
memories = self.vector_store.search(query=query, vectors=embeddings, top_k=top_k, filters=filters)
|
||||
|
||||
promoted_payload_keys = [
|
||||
"user_id",
|
||||
@@ -1157,7 +1154,7 @@ class Memory(MemoryBase):
|
||||
raise ValueError(f"Memory with id {memory_id} not found")
|
||||
|
||||
# Clean up graph entities before deleting from vector store
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
try:
|
||||
memory_text = existing_memory.payload.get("data", "")
|
||||
if memory_text:
|
||||
@@ -1205,7 +1202,7 @@ class Memory(MemoryBase):
|
||||
|
||||
logger.info(f"Deleted {len(memories)} memories")
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
self.graph.delete_all(filters)
|
||||
|
||||
return {"message": "Memories deleted successfully!"}
|
||||
@@ -1407,7 +1404,7 @@ class Memory(MemoryBase):
|
||||
)
|
||||
capture_event("mem0.reset", self, {"sync_type": "sync"})
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
try:
|
||||
self.graph.reset()
|
||||
except Exception:
|
||||
@@ -1442,12 +1439,9 @@ class AsyncMemory(MemoryBase):
|
||||
config.reranker.config
|
||||
)
|
||||
|
||||
self.enable_graph = False
|
||||
|
||||
if self.config.graph_store.config:
|
||||
provider = self.config.graph_store.provider
|
||||
self.graph = GraphStoreFactory.create(provider, self.config)
|
||||
self.enable_graph = True
|
||||
else:
|
||||
self.graph = None
|
||||
|
||||
@@ -1590,7 +1584,7 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
vector_store_result, graph_result = await asyncio.gather(vector_store_task, graph_task)
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
return {
|
||||
"results": vector_store_result,
|
||||
"relations": graph_result,
|
||||
@@ -1643,8 +1637,8 @@ class AsyncMemory(MemoryBase):
|
||||
return returned_memories
|
||||
|
||||
parsed_messages = parse_messages(messages)
|
||||
if self.config.custom_fact_extraction_prompt:
|
||||
system_prompt = self.config.custom_fact_extraction_prompt
|
||||
if self.config.custom_instructions:
|
||||
system_prompt = self.config.custom_instructions
|
||||
user_prompt = f"Input:\n{parsed_messages}"
|
||||
else:
|
||||
# Determine if this should use agent memory extraction based on agent_id presence
|
||||
@@ -1699,7 +1693,7 @@ class AsyncMemory(MemoryBase):
|
||||
self.vector_store.search,
|
||||
query=new_mem_content,
|
||||
vectors=embeddings,
|
||||
limit=5,
|
||||
top_k=5,
|
||||
filters=search_filters,
|
||||
)
|
||||
return [{"id": mem.id, "text": mem.payload.get("data", "")} for mem in existing_mems]
|
||||
@@ -1863,7 +1857,7 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
async def _add_to_graph(self, messages, filters):
|
||||
added_entities = []
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
if filters.get("user_id") is None:
|
||||
filters["user_id"] = "user"
|
||||
|
||||
@@ -1922,7 +1916,7 @@ class AsyncMemory(MemoryBase):
|
||||
agent_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
limit: int = 100,
|
||||
top_k: int = 100,
|
||||
):
|
||||
"""
|
||||
List all memories.
|
||||
@@ -1934,7 +1928,7 @@ class AsyncMemory(MemoryBase):
|
||||
filters (dict, optional): Additional custom key-value filters to apply to the search.
|
||||
These are merged with the ID-based scoping filters. For example,
|
||||
`filters={"actor_id": "some_user"}`.
|
||||
limit (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
top_k (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing a list of memories under the "results" key,
|
||||
@@ -1955,19 +1949,19 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
keys, encoded_ids = process_telemetry_filters(effective_filters)
|
||||
capture_event(
|
||||
"mem0.get_all", self, {"limit": limit, "keys": keys, "encoded_ids": encoded_ids, "sync_type": "async"}
|
||||
"mem0.get_all", self, {"top_k": top_k, "keys": keys, "encoded_ids": encoded_ids, "sync_type": "async"}
|
||||
)
|
||||
|
||||
vector_store_task = asyncio.create_task(self._get_all_from_vector_store(effective_filters, limit))
|
||||
vector_store_task = asyncio.create_task(self._get_all_from_vector_store(effective_filters, top_k))
|
||||
|
||||
graph_task = None
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
graph_get_all = getattr(self.graph, "get_all", None)
|
||||
if callable(graph_get_all):
|
||||
if asyncio.iscoroutinefunction(graph_get_all):
|
||||
graph_task = asyncio.create_task(graph_get_all(effective_filters, limit))
|
||||
graph_task = asyncio.create_task(graph_get_all(effective_filters, top_k))
|
||||
else:
|
||||
graph_task = asyncio.create_task(asyncio.to_thread(graph_get_all, effective_filters, limit))
|
||||
graph_task = asyncio.create_task(asyncio.to_thread(graph_get_all, effective_filters, top_k))
|
||||
|
||||
results_dict = {}
|
||||
if graph_task:
|
||||
@@ -1978,8 +1972,8 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
return results_dict
|
||||
|
||||
async def _get_all_from_vector_store(self, filters, limit):
|
||||
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit)
|
||||
async def _get_all_from_vector_store(self, filters, top_k):
|
||||
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, top_k=top_k)
|
||||
|
||||
# Handle different vector store return formats by inspecting first element
|
||||
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0:
|
||||
@@ -2032,7 +2026,7 @@ class AsyncMemory(MemoryBase):
|
||||
user_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
top_k: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
threshold: Optional[float] = None,
|
||||
metadata_filters: Optional[Dict[str, Any]] = None,
|
||||
@@ -2045,7 +2039,7 @@ class AsyncMemory(MemoryBase):
|
||||
user_id (str, optional): ID of the user to search for. Defaults to None.
|
||||
agent_id (str, optional): ID of the agent to search for. Defaults to None.
|
||||
run_id (str, optional): ID of the run to search for. Defaults to None.
|
||||
limit (int, optional): Limit the number of results. Defaults to 100.
|
||||
top_k (int, optional): Maximum number of results to return. Defaults to 100.
|
||||
filters (dict, optional): Legacy filters to apply to the search. Defaults to None.
|
||||
threshold (float, optional): Minimum score for a memory to be included in the results. Defaults to None.
|
||||
filters (dict, optional): Enhanced metadata filtering with operators:
|
||||
@@ -2091,7 +2085,7 @@ class AsyncMemory(MemoryBase):
|
||||
"mem0.search",
|
||||
self,
|
||||
{
|
||||
"limit": limit,
|
||||
"top_k": top_k,
|
||||
"version": self.api_version,
|
||||
"keys": keys,
|
||||
"encoded_ids": encoded_ids,
|
||||
@@ -2101,14 +2095,14 @@ class AsyncMemory(MemoryBase):
|
||||
},
|
||||
)
|
||||
|
||||
vector_store_task = asyncio.create_task(self._search_vector_store(query, effective_filters, limit, threshold))
|
||||
vector_store_task = asyncio.create_task(self._search_vector_store(query, effective_filters, top_k, threshold))
|
||||
|
||||
graph_task = None
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
if hasattr(self.graph.search, "__await__"): # Check if graph search is async
|
||||
graph_task = asyncio.create_task(self.graph.search(query, effective_filters, limit))
|
||||
graph_task = asyncio.create_task(self.graph.search(query, effective_filters, top_k))
|
||||
else:
|
||||
graph_task = asyncio.create_task(asyncio.to_thread(self.graph.search, query, effective_filters, limit))
|
||||
graph_task = asyncio.create_task(asyncio.to_thread(self.graph.search, query, effective_filters, top_k))
|
||||
|
||||
if graph_task:
|
||||
original_memories, graph_entities = await asyncio.gather(vector_store_task, graph_task)
|
||||
@@ -2121,13 +2115,13 @@ class AsyncMemory(MemoryBase):
|
||||
try:
|
||||
# Run reranking in thread pool to avoid blocking async loop
|
||||
reranked_memories = await asyncio.to_thread(
|
||||
self.reranker.rerank, query, original_memories, limit
|
||||
self.reranker.rerank, query, original_memories, top_k
|
||||
)
|
||||
original_memories = reranked_memories
|
||||
except Exception as e:
|
||||
logger.warning(f"Reranking failed, using original results: {e}")
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
return {"results": original_memories, "relations": graph_entities}
|
||||
|
||||
return {"results": original_memories}
|
||||
@@ -2228,10 +2222,10 @@ class AsyncMemory(MemoryBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _search_vector_store(self, query, filters, limit, threshold: Optional[float] = None):
|
||||
async def _search_vector_store(self, query, filters, top_k, threshold: Optional[float] = None):
|
||||
embeddings = await asyncio.to_thread(self.embedding_model.embed, query, "search")
|
||||
memories = await asyncio.to_thread(
|
||||
self.vector_store.search, query=query, vectors=embeddings, limit=limit, filters=filters
|
||||
self.vector_store.search, query=query, vectors=embeddings, top_k=top_k, filters=filters
|
||||
)
|
||||
|
||||
promoted_payload_keys = [
|
||||
@@ -2309,7 +2303,7 @@ class AsyncMemory(MemoryBase):
|
||||
raise ValueError(f"Memory with id {memory_id} not found")
|
||||
|
||||
# Clean up graph entities before deleting from vector store
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
try:
|
||||
memory_text = existing_memory.payload.get("data", "")
|
||||
if memory_text:
|
||||
@@ -2360,7 +2354,7 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
logger.info(f"Deleted {len(memories[0])} memories")
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
await asyncio.to_thread(self.graph.delete_all, filters)
|
||||
|
||||
return {"message": "Memories deleted successfully!"}
|
||||
@@ -2587,7 +2581,7 @@ class AsyncMemory(MemoryBase):
|
||||
)
|
||||
capture_event("mem0.reset", self, {"sync_type": "async"})
|
||||
|
||||
if self.enable_graph:
|
||||
if self.graph:
|
||||
try:
|
||||
await asyncio.to_thread(self.graph.reset)
|
||||
except Exception:
|
||||
|
||||
@@ -98,14 +98,14 @@ class MemoryGraph:
|
||||
|
||||
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
|
||||
|
||||
def search(self, query, filters, limit=100):
|
||||
def search(self, query, filters, top_k=100):
|
||||
"""
|
||||
Search for memories and related graph data.
|
||||
|
||||
Args:
|
||||
query (str): Query to search for.
|
||||
filters (dict): A dictionary containing filters to be applied during the search.
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing:
|
||||
@@ -172,14 +172,14 @@ class MemoryGraph:
|
||||
params = {"user_id": filters["user_id"]}
|
||||
self.graph.query(cypher, params=params)
|
||||
|
||||
def get_all(self, filters, limit=100):
|
||||
def get_all(self, filters, top_k=100):
|
||||
"""
|
||||
Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
|
||||
|
||||
Args:
|
||||
filters (dict): A dictionary containing filters to be applied during the retrieval.
|
||||
Supports 'user_id' (required) and 'agent_id' (optional).
|
||||
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
top_k (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
|
||||
Returns:
|
||||
list: A list of dictionaries, each containing:
|
||||
- 'source': The source node name.
|
||||
@@ -193,14 +193,14 @@ class MemoryGraph:
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT $limit
|
||||
"""
|
||||
params = {"user_id": filters["user_id"], "agent_id": filters["agent_id"], "limit": limit}
|
||||
params = {"user_id": filters["user_id"], "agent_id": filters["agent_id"], "limit": top_k}
|
||||
else:
|
||||
query = """
|
||||
MATCH (n:Entity {user_id: $user_id})-[r]->(m:Entity {user_id: $user_id})
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT $limit
|
||||
"""
|
||||
params = {"user_id": filters["user_id"], "limit": limit}
|
||||
params = {"user_id": filters["user_id"], "limit": top_k}
|
||||
|
||||
results = self.graph.query(query, params=params)
|
||||
|
||||
@@ -293,7 +293,7 @@ class MemoryGraph:
|
||||
logger.debug(f"Extracted entities: {entities}")
|
||||
return entities
|
||||
|
||||
def _search_graph_db(self, node_list, filters, limit=100):
|
||||
def _search_graph_db(self, node_list, filters, top_k=100):
|
||||
"""Search similar nodes among and their respective incoming and outgoing relations."""
|
||||
result_relations = []
|
||||
|
||||
@@ -324,7 +324,7 @@ class MemoryGraph:
|
||||
"threshold": self.threshold,
|
||||
"user_id": filters["user_id"],
|
||||
"agent_id": filters["agent_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
else:
|
||||
cypher_query = """
|
||||
@@ -348,7 +348,7 @@ class MemoryGraph:
|
||||
"n_embedding": n_embedding,
|
||||
"threshold": self.threshold,
|
||||
"user_id": filters["user_id"],
|
||||
"limit": limit,
|
||||
"limit": top_k,
|
||||
}
|
||||
|
||||
ans = self.graph.query(cypher_query, params=params)
|
||||
|
||||
@@ -38,7 +38,7 @@ def ensure_json_instruction(system_prompt, user_prompt):
|
||||
|
||||
OpenAI's API requires the word 'json' to appear in the messages when
|
||||
response_format is set to {"type": "json_object"}. When users provide a
|
||||
custom_fact_extraction_prompt that doesn't include 'json', this causes a
|
||||
custom_instructions that doesn't include 'json', this causes a
|
||||
400 error. This function appends a JSON format instruction to the system
|
||||
prompt if 'json' is not already present in either prompt.
|
||||
|
||||
|
||||
+4
-4
@@ -59,7 +59,7 @@ class Completions:
|
||||
run_id: Optional[str] = None,
|
||||
metadata: Optional[dict] = None,
|
||||
filters: Optional[dict] = None,
|
||||
limit: Optional[int] = 10,
|
||||
top_k: Optional[int] = 10,
|
||||
# LLM arguments
|
||||
timeout: Optional[Union[float, str, httpx.Timeout]] = None,
|
||||
temperature: Optional[float] = None,
|
||||
@@ -103,7 +103,7 @@ class Completions:
|
||||
prepared_messages = self._prepare_messages(messages)
|
||||
if prepared_messages[-1]["role"] == "user":
|
||||
self._async_add_to_memory(messages, user_id, agent_id, run_id, metadata, filters)
|
||||
relevant_memories = self._fetch_relevant_memories(messages, user_id, agent_id, run_id, filters, limit)
|
||||
relevant_memories = self._fetch_relevant_memories(messages, user_id, agent_id, run_id, filters, top_k)
|
||||
logger.debug(f"Retrieved {len(relevant_memories)} relevant memories")
|
||||
prepared_messages[-1]["content"] = self._format_query_with_memories(messages, relevant_memories)
|
||||
|
||||
@@ -163,7 +163,7 @@ class Completions:
|
||||
|
||||
threading.Thread(target=add_task, daemon=True).start()
|
||||
|
||||
def _fetch_relevant_memories(self, messages, user_id, agent_id, run_id, filters, limit):
|
||||
def _fetch_relevant_memories(self, messages, user_id, agent_id, run_id, filters, top_k):
|
||||
# Currently, only pass the last 6 messages to the search API to prevent long query
|
||||
message_input = [f"{message['role']}: {message['content']}" for message in messages][-6:]
|
||||
# TODO: Make it better by summarizing the past conversation
|
||||
@@ -173,7 +173,7 @@ class Completions:
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
top_k=top_k,
|
||||
)
|
||||
|
||||
def _format_query_with_memories(self, messages, relevant_memories):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user