fix(sdk): removing deprecating param from our sdk and docs changes with it (#4740)

This commit is contained in:
Kartik
2026-04-12 00:34:58 +05:30
committed by GitHub
parent 3882af7450
commit e44b46ef2e
138 changed files with 1186 additions and 2840 deletions
+2 -2
View File
@@ -266,7 +266,7 @@ config = MemoryConfig(
graph_store=GraphStoreConfig(provider="neo4j", config={...}), # optional graph_store=GraphStoreConfig(provider="neo4j", config={...}), # optional
history_db_path="~/.mem0/history.db", history_db_path="~/.mem0/history.db",
version="v1.1", version="v1.1",
custom_fact_extraction_prompt="Custom prompt...", custom_instructions="Custom prompt...",
custom_update_memory_prompt="Custom prompt..." custom_update_memory_prompt="Custom prompt..."
) )
``` ```
@@ -684,7 +684,7 @@ Conversation: {messages}
""" """
config = MemoryConfig( config = MemoryConfig(
custom_fact_extraction_prompt=custom_extraction_prompt custom_instructions=custom_extraction_prompt
) )
memory = Memory(config) memory = Memory(config)
``` ```
+1 -2
View File
@@ -47,8 +47,7 @@ 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`. | | `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"}`). | | `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. | | `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. | | `enable_graph` | boolean (default `false`) | Optional | Set to `true` to create entity nodes and relationships for knowledge graph. |
| `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. > \* 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.
+1 -2
View File
@@ -62,8 +62,7 @@ To retrieve graph memory relationships between entities, pass `output_format="v1
memories = client.get_all( memories = client.get_all(
filters={ filters={
"user_id": "alex" "user_id": "alex"
}, }
output_format="v1.1"
) )
``` ```
@@ -32,7 +32,7 @@ Example with the mem0 Python package:
```python ```python
from mem0 import MemoryClient from mem0 import MemoryClient
client = MemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID') client = MemoryClient(api_key="your-api-key")
``` ```
</Tab> </Tab>
@@ -41,10 +41,7 @@ client = MemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID')
```javascript ```javascript
import { MemoryClient } from "mem0ai"; import { MemoryClient } from "mem0ai";
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: "your-api-key" });
organizationId: "YOUR_ORG_ID",
projectId: "YOUR_PROJECT_ID"
});
``` ```
</Tab> </Tab>
@@ -172,7 +169,7 @@ All project methods are available in async mode:
from mem0 import AsyncMemoryClient from mem0 import AsyncMemoryClient
async def manage_project(): 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 # All methods support async/await
project_info = await client.project.get() project_info = await client.project.get()
+1 -1
View File
@@ -15,7 +15,7 @@ Mem0 supports LangChain as a provider for vector store integration. LangChain pr
```python Python ```python Python
import os import os
from mem0 import Memory from mem0 import Memory
from langchain_community.vectorstores import Chroma from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings from langchain_openai import OpenAIEmbeddings
# Initialize a LangChain vector store # Initialize a LangChain vector store
@@ -129,13 +129,13 @@ async def search_memories(
user_id=USER_ID, user_id=USER_ID,
limit=5, limit=5,
threshold=0.7, # Higher threshold for more relevant results threshold=0.7, # Higher threshold for more relevant results
) )
# Format and return the results # Format and return the results
if not results.get('results', []): if not results.get('results', []):
return "I don't have any relevant memories about this topic." return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results.get('results', [])] memories = [f"• {result['memory']}" for result in results.get('results', [])]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories) 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, user_id=USER_ID,
limit=5, limit=5,
threshold=0.7, # Higher threshold for more relevant results threshold=0.7, # Higher threshold for more relevant results
) )
# Format and return the results # Format and return the results
if not results.get('results', []): if not results.get('results', []):
return "I don't have any relevant memories about this topic." return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results.get('results', [])] memories = [f"• {result['memory']}" for result in results.get('results', [])]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories) return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
@@ -85,7 +85,6 @@ const messages = [
await client.add(messages, { await client.add(messages, {
user_id: "alice", user_id: "alice",
version: "v2",
}); });
``` ```
</CodeGroup> </CodeGroup>
+37 -16
View File
@@ -70,7 +70,6 @@
"platform/features/v2-memory-filters", "platform/features/v2-memory-filters",
"platform/features/entity-scoped-memory", "platform/features/entity-scoped-memory",
"platform/features/async-client", "platform/features/async-client",
"platform/features/async-mode-default-change",
"platform/features/multimodal-support", "platform/features/multimodal-support",
"platform/features/custom-categories" "platform/features/custom-categories"
] ]
@@ -94,8 +93,7 @@
"pages": [ "pages": [
"platform/features/direct-import", "platform/features/direct-import",
"platform/features/memory-export", "platform/features/memory-export",
"platform/features/timestamp", "platform/features/timestamp"
"platform/features/expiration-date"
] ]
}, },
{ {
@@ -122,8 +120,6 @@
"icon": "arrow-right", "icon": "arrow-right",
"pages": [ "pages": [
"migration/oss-to-platform", "migration/oss-to-platform",
"migration/v0-to-v1",
"migration/breaking-changes",
"migration/api-changes" "migration/api-changes"
] ]
}, },
@@ -133,16 +129,6 @@
"pages": [ "pages": [
"platform/contribute" "platform/contribute"
] ]
},
{
"group": "Release Notes",
"icon": "rocket",
"pages": [
"changelog/highlights",
"changelog/sdk",
"changelog/platform",
"changelog/openclaw"
]
} }
] ]
}, },
@@ -182,7 +168,7 @@
"open-source/features/reranker-search", "open-source/features/reranker-search",
"open-source/features/async-memory", "open-source/features/async-memory",
"open-source/features/multimodal-support", "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/custom-update-memory-prompt",
"open-source/features/rest-api", "open-source/features/rest-api",
"open-source/features/openai_compatibility" "open-source/features/openai_compatibility"
@@ -562,6 +548,21 @@
] ]
} }
] ]
},
{
"tab": "Release Notes",
"groups": [
{
"group": "Release Notes",
"icon": "rocket",
"pages": [
"changelog/highlights",
"changelog/sdk",
"changelog/platform",
"changelog/openclaw"
]
}
]
} }
] ]
} }
@@ -612,6 +613,26 @@
] ]
}, },
"redirects": [ "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": "/changelog", "source": "/changelog",
"destination": "/changelog/highlights" "destination": "/changelog/highlights"
+1 -1
View File
@@ -268,7 +268,7 @@ memories = mem0.search(
{"categories": {"contains": "travel"}} {"categories": {"contains": "travel"}}
] ]
}, },
limit=5 top_k=5
) )
# Configure agent with custom model settings # Configure agent with custom model settings
+1 -2
View File
@@ -16,7 +16,7 @@ Combining Mem0 with Keywords AI allows you to:
4. Optimize token usage and reduce costs 4. Optimize token usage and reduce costs
<Note> <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> </Note>
## Setup and Configuration ## Setup and Configuration
@@ -107,7 +107,6 @@ response = client.chat.completions.create(
extra_body={ extra_body={
"mem0_params": { "mem0_params": {
"user_id": "test_user", "user_id": "test_user",
"org_id": "org_1",
"api_key": os.environ.get("MEM0_API_KEY"), "api_key": os.environ.get("MEM0_API_KEY"),
"add_memories": { "add_memories": {
"messages": messages, "messages": messages,
+1 -4
View File
@@ -29,10 +29,7 @@ import os
os.environ["MEM0_API_KEY"] = "your-api-key" os.environ["MEM0_API_KEY"] = "your-api-key"
client = MemoryClient( client = MemoryClient()
org_id=your_org_id,
project_id=your_project_id
)
``` ```
## Available Tools ## Available Tools
+2 -2
View File
@@ -45,7 +45,7 @@ mem0 = MemoryClient()
@function_tool @function_tool
def search_memory(query: str, user_id: str) -> str: def search_memory(query: str, user_id: str) -> str:
"""Search through past conversations and memories""" """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'): if memories and memories.get('results'):
return "\n".join([f"- {mem['memory']}" for mem in memories['results']]) return "\n".join([f"- {mem['memory']}" for mem in memories['results']])
return "No relevant memories found." return "No relevant memories found."
@@ -215,7 +215,7 @@ Customize memory behavior:
memories = mem0.search( memories = mem0.search(
query="travel preferences", query="travel preferences",
user_id="alex", user_id="alex",
limit=5 # Number of memories to retrieve top_k=5 # Number of memories to retrieve
) )
# Add metadata to memories # Add metadata to memories
+1 -1
View File
@@ -155,7 +155,7 @@ openclaw mem0 stats
| Key | Type | Default | Description | | 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.provider` | `string` | `"openai"` | Embedding provider (`"openai"`, `"ollama"`, etc.) |
| `oss.embedder.config` | `object` | — | Provider config: `apiKey`, `model`, `baseURL` | | `oss.embedder.config` | `object` | — | Provider config: `apiKey`, `model`, `baseURL` |
| `oss.vectorStore.provider` | `string` | `"memory"` | Vector store (`"memory"`, `"qdrant"`, `"chroma"`, etc.) | | `oss.vectorStore.provider` | `string` | `"memory"` | Vector store (`"memory"`, `"qdrant"`, `"chroma"`, etc.) |
+1 -1
View File
@@ -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 `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: 3. Add Memories to Enhance Context:
+1 -1
View File
@@ -86,7 +86,7 @@ Key differentiators:
- [Reranker Search](https://docs.mem0.ai/open-source/features/reranker-search): Enhanced search results with reranking models - [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 - [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 - [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 - [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 - [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 - [OpenAI Compatibility](https://docs.mem0.ai/open-source/features/openai_compatibility): Seamless integration with OpenAI-compatible APIs
+2 -2
View File
@@ -319,7 +319,7 @@ config = {
"graph_store": {...}, "graph_store": {...},
"version": "v1.0", # ❌ v1.0 no longer supported "version": "v1.0", # ❌ v1.0 no longer supported
"history_db_path": "...", "history_db_path": "...",
"custom_fact_extraction_prompt": "..." "custom_instructions": "..."
} }
``` ```
@@ -336,7 +336,7 @@ config = {
}, },
"version": "v1.1", # ✅ v1.1+ only "version": "v1.1", # ✅ v1.1+ only
"history_db_path": "...", "history_db_path": "...",
"custom_fact_extraction_prompt": "...", "custom_instructions": "...",
"custom_update_memory_prompt": "..." # ✅ NEW: Custom update prompt "custom_update_memory_prompt": "..." # ✅ NEW: Custom update prompt
} }
``` ```
-383
View File
@@ -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>
+10 -6
View File
@@ -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. **Critical Change**: Platform uses v2 endpoints that require filtering parameters to be nested inside a `filters` dictionary.
</Warning> </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 | | Method | Open Source | Platform |
| ------ | ----------- | -------- | | ------ | ----------- | -------- |
| `search()` | `m.search(query, user_id="alex")` | `client.search(query, filters={"user_id": "alex"})` | | `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> <CodeGroup>
```python Open Source (Old) ```python Open Source (Old)
# Get all memories for a user # 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 # 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) ```python Platform (New)
# Get all memories for a user # 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 # 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> </CodeGroup>
</Accordion> </Accordion>
@@ -283,7 +287,7 @@ The Platform introduces powerful capabilities not available in OSS:
"user preferences", "user preferences",
filters={"user_id": "alex"}, filters={"user_id": "alex"},
rerank=True, # Platform exclusive rerank=True, # Platform exclusive
limit=5 top_k=5
) )
# Search with keyword expansion # Search with keyword expansion
@@ -335,7 +339,7 @@ The Platform introduces powerful capabilities not available in OSS:
{"timestamp": {"gte": "2024-01-01"}} {"timestamp": {"gte": "2024-01-01"}}
] ]
}, },
limit=100 top_k=100
) )
# Monitor usage patterns # Monitor usage patterns
-481
View File
@@ -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>
+2 -2
View File
@@ -240,7 +240,7 @@ async_openai_client = AsyncOpenAI()
async_memory = AsyncMemory() async_memory = AsyncMemory()
async def chat_with_memories(message: str, user_id: str = "default_user") -> str: 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"] relevant_memories = search_result["results"]
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories) 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") @app.get("/memories/search")
async def search_memories(query: str, user_id: str, limit: int = 10): async def search_memories(query: str, user_id: str, limit: int = 10):
try: 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} return {"status": "success", "data": result}
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) raise HTTPException(status_code=500, detail=str(exc))
@@ -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. description: Tailor fact extraction so Mem0 stores only the details you care about.
icon: "wand-magic-sparkles" 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> <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. - 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 already have a clear schema for memories and want the LLM to follow it.
- You must prevent irrelevant details from entering long-term storage. - 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. Prompts that are too broad cause unrelated facts to slip through. Keep instructions tight and test them with real transcripts.
</Warning> </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 ## 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. - **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. - **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. - **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> <AccordionGroup>
<Accordion title="Prompt blueprint"> <Accordion title="Prompt blueprint">
1. State the allowed fact types. 1. State the allowed fact types.
2. Include short examples that mirror production messages. 2. Include short examples that mirror production messages.
3. Show both empty (`[]`) and populated outputs. 3. Show both empty (`[]`) and populated outputs.
4. Remind the model to return JSON with a `facts` key only. 4. Remind the model to return JSON with a `facts` key only.
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
@@ -43,8 +47,8 @@ Custom fact extraction prompts let you decide exactly which facts Mem0 records f
<CodeGroup> <CodeGroup>
```python Python ```python Python
custom_fact_extraction_prompt = """ custom_instructions = """
Please only extract entities containing customer support information, order details, and user information. Please only extract entities containing customer support information, order details, and user information.
Here are some few shot examples: Here are some few shot examples:
Input: Hi. Input: Hi.
@@ -67,8 +71,8 @@ Return the facts and customer information in a json format as shown above.
``` ```
```ts TypeScript ```ts TypeScript
const customPrompt = ` const customInstructions = `
Please only extract entities containing customer support information, order details, and user information. Please only extract entities containing customer support information, order details, and user information.
Here are some few shot examples: Here are some few shot examples:
Input: Hi. Input: Hi.
@@ -110,7 +114,7 @@ config = {
"max_tokens": 2000, "max_tokens": 2000,
} }
}, },
"custom_fact_extraction_prompt": custom_fact_extraction_prompt, "custom_instructions": custom_instructions,
"version": "v1.1" "version": "v1.1"
} }
@@ -131,7 +135,7 @@ const config = {
maxTokens: 1500, maxTokens: 1500,
}, },
}, },
customPrompt: customPrompt, customInstructions: customInstructions,
}; };
const memory = new Memory(config); 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. - Log each decision so product teams can review why a change happened.
<Note> <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> </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 ## 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 | | 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 | | 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}> <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. Coordinate both prompts so fact extraction feeds clean inputs into the update flow.
</Card> </Card>
<Card title="Build Email Automations" icon="inbox" href="/cookbooks/operations/email-automation"> <Card title="Build Email Automations" icon="inbox" href="/cookbooks/operations/email-automation">
+4 -4
View File
@@ -94,7 +94,7 @@ memory.add(conversation, user_id="demo-user")
results = memory.search( results = memory.search(
"Who did Alice meet at GraphConf?", "Who did Alice meet at GraphConf?",
user_id="demo-user", user_id="demo-user",
limit=3, top_k=3,
rerank=True, rerank=True,
) )
@@ -146,7 +146,7 @@ await memory.add(conversation, { userId: "demo-user" });
const results = await memory.search( const results = await memory.search(
"Who did Alice meet at GraphConf?", "Who did Alice meet at GraphConf?",
{ userId: "demo-user", limit: 3, rerank: true } { userId: "demo-user", topK: 3, rerank: true }
); );
results.results.forEach((hit) => { results.results.forEach((hit) => {
@@ -204,7 +204,7 @@ const config = {
username: process.env.NEO4J_USERNAME!, username: process.env.NEO4J_USERNAME!,
password: process.env.NEO4J_PASSWORD!, password: process.env.NEO4J_PASSWORD!,
}, },
customPrompt: "Please only capture people, organisations, and project links.", customInstructions: "Please only capture people, organisations, and project links.",
} }
}; };
@@ -265,7 +265,7 @@ Monitor graph growth, especially on free tiers, by periodically cleaning dormant
## Decision Points ## 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). - 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. - Set a policy for pruning stale relationships so your graph stays fast and affordable.
## Provider setup ## Provider setup
@@ -131,7 +131,7 @@ print(response.choices[0].message.content)
| `run_id` | `str` | Optional session/run identifier for short-lived flows. | | `run_id` | `str` | Optional session/run identifier for short-lived flows. |
| `metadata` | `dict` | Store extra fields alongside each memory entry. | | `metadata` | `dict` | Store extra fields alongside each memory entry. |
| `filters` | `dict` | Restrict retrieval to specific memories while responding. | | `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. Other request fields mirror OpenAI’s chat completion API.
+1 -1
View File
@@ -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"> <Card title="Multimodal Support" icon="image" href="/open-source/features/multimodal-support">
Process images, audio, and video memories. Process images, audio, and video memories.
</Card> </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. Tailor how facts are extracted from text.
</Card> </Card>
</CardGroup> </CardGroup>
@@ -321,7 +321,7 @@ results = m.search(
] ]
}, },
rerank=True, rerank=True,
limit=20 top_k=20
) )
``` ```
@@ -366,7 +366,7 @@ results = m.search(
user_id="reader123", user_id="reader123",
filters={"content_type": "book_recommendation"}, filters={"content_type": "book_recommendation"},
rerank=True, rerank=True,
limit=10 top_k=10
) )
for result in results["results"]: for result in results["results"]:
+2 -2
View File
@@ -230,7 +230,7 @@ Mem0 offers granular configuration across vector stores, LLMs, embedders, and hi
| --- | --- | --- | | --- | --- | --- |
| `historyDbPath` | Path to history database | `"{mem0_dir}/history.db"` | | `historyDbPath` | Path to history database | `"{mem0_dir}/history.db"` |
| `version` | API version | `"v1.0"` | | `version` | API version | `"v1.0"` |
| `customPrompt` | Custom processing prompt | `undefined` | | `customInstructions` | Custom processing prompt | `undefined` |
</Accordion> </Accordion>
<Accordion title="History store"> <Accordion title="History store">
| Parameter | Description | Default | | Parameter | Description | Default |
@@ -273,7 +273,7 @@ const config = {
} }
}, },
disableHistory: false, 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> </Accordion>
+26 -26
View File
@@ -183,7 +183,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -717,7 +717,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -845,7 +845,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -1096,7 +1096,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -1203,11 +1203,11 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "lang": "cURL",
@@ -1232,7 +1232,7 @@
"tags": [ "tags": [
"memories" "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", "operationId": "memories_delete",
"parameters": [ "parameters": [
{ {
@@ -1315,15 +1315,15 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "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", "lang": "Go",
@@ -1441,11 +1441,11 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "lang": "cURL",
@@ -1589,11 +1589,11 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "lang": "cURL",
@@ -1708,11 +1708,11 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "lang": "cURL",
@@ -1864,7 +1864,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -1989,7 +1989,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -2053,7 +2053,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -2199,7 +2199,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -3608,7 +3608,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -4411,7 +4411,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -4490,7 +4490,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -4758,7 +4758,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -4998,7 +4998,7 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "lang": "JavaScript",
@@ -5742,4 +5742,4 @@
} }
}, },
"x-original-swagger-version": "2.0" "x-original-swagger-version": "2.0"
} }
+16 -102
View File
@@ -1,6 +1,6 @@
--- ---
title: Advanced Retrieval 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? ## What is Advanced Retrieval?
@@ -9,40 +9,6 @@ Advanced Retrieval gives you precise control over how memories are found and ran
## Search Enhancement Options ## 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 ### Reranking
Reorders results using deep semantic understanding to put the most relevant memories first. Reorders results using deep semantic understanding to put the most relevant memories first.
@@ -77,41 +43,6 @@ results = client.search(
</Tab> </Tab>
</Tabs> </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 ## Real-World Use Cases
<Tabs> <Tabs>
@@ -120,7 +51,6 @@ results = client.search(
# Smart home assistant finding device preferences # Smart home assistant finding device preferences
results = client.search( results = client.search(
query="How do I like my bedroom temperature?", query="How do I like my bedroom temperature?",
keyword_search=True, # Find specific temperature mentions
rerank=True, # Get most recent preferences first rerank=True, # Get most recent preferences first
user_id="user123" user_id="user123"
) )
@@ -133,8 +63,6 @@ results = client.search(
# Find specific product issues with high precision # Find specific product issues with high precision
results = client.search( results = client.search(
query="Problems with premium subscription billing", query="Problems with premium subscription billing",
keyword_search=True, # Find "premium", "billing", "subscription"
filter_memories=True, # Only billing-related issues
user_id="customer456" user_id="customer456"
) )
@@ -147,11 +75,10 @@ results = client.search(
results = client.search( results = client.search(
query="Patient allergies and contraindications", query="Patient allergies and contraindications",
rerank=True, # Most important info first rerank=True, # Most important info first
filter_memories=True, # Only medical restrictions
user_id="patient789" user_id="patient789"
) )
# Ensures critical allergy info appears first and filters out non-medical data # Ensures critical allergy info appears first
``` ```
</Tab> </Tab>
<Tab title="Learning Platform"> <Tab title="Learning Platform">
@@ -159,7 +86,6 @@ results = client.search(
# Find learning progress for specific topics # Find learning progress for specific topics
results = client.search( results = client.search(
query="Python programming progress and difficulties", query="Python programming progress and difficulties",
keyword_search=True, # Find "Python", "programming", specific concepts
rerank=True, # Recent progress first rerank=True, # Recent progress first
user_id="student123" user_id="student123"
) )
@@ -169,63 +95,57 @@ results = client.search(
</Tab> </Tab>
</Tabs> </Tabs>
## Choosing the Right Combination ## Choosing the Right Configuration
### Recommended Configurations ### Recommended Configurations
<CodeGroup> <CodeGroup>
```python Python ```python Python
# Fast and broad - good for exploration # Basic search - good for exploration
def quick_search(query, user_id): def quick_search(query, user_id):
return client.search( return client.search(
query=query, query=query,
keyword_search=True,
user_id=user_id user_id=user_id
) )
# Balanced - good for most applications # Reranked search - good for most applications
def standard_search(query, user_id): def standard_search(query, user_id):
return client.search( return client.search(
query=query, query=query,
keyword_search=True,
rerank=True, rerank=True,
user_id=user_id user_id=user_id
) )
# High precision - good for critical applications # Reranked search - good for critical applications
def precise_search(query, user_id): def precise_search(query, user_id):
return client.search( return client.search(
query=query, query=query,
rerank=True, rerank=True,
filter_memories=True,
user_id=user_id user_id=user_id
) )
``` ```
```javascript JavaScript ```javascript JavaScript
// Fast and broad - good for exploration // Basic search - good for exploration
function quickSearch(query, userId) { function quickSearch(query, userId) {
return client.search(query, { return client.search(query, {
user_id: userId, user_id: userId
keyword_search: true
}); });
} }
// Balanced - good for most applications // Reranked search - good for most applications
function standardSearch(query, userId) { function standardSearch(query, userId) {
return client.search(query, { return client.search(query, {
user_id: userId, user_id: userId,
keyword_search: true,
rerank: true rerank: true
}); });
} }
// High precision - good for critical applications // Reranked search - good for critical applications
function preciseSearch(query, userId) { function preciseSearch(query, userId) {
return client.search(query, { return client.search(query, {
user_id: userId, user_id: userId,
rerank: true, rerank: true
filter_memories: true
}); });
} }
``` ```
@@ -235,19 +155,15 @@ function preciseSearch(query, userId) {
### Do ### Do
- Start simple with just one enhancement and measure impact - Start simple with basic search and measure impact before enabling reranking
- Use keyword search for entity-heavy queries (names, places, technical terms)
- Use reranking when the top result quality matters most - 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 - Monitor latency and adjust based on your application's needs
- Handle empty results gracefully
### Don't ### Don't
- Enable all options by default without measuring necessity - Enable reranking by default without measuring necessity
- Use filtering for broad exploratory queries
- Ignore latency impact in real-time applications - Ignore latency impact in real-time applications
- Forget to handle cases where filtering returns no results
- Use advanced retrieval for simple, fast lookup scenarios - Use advanced retrieval for simple, fast lookup scenarios
## Performance Guidelines ## Performance Guidelines
@@ -261,20 +177,18 @@ import time
start_time = time.time() start_time = time.time()
results = client.search( results = client.search(
query="user preferences", query="user preferences",
keyword_search=True, # +10ms
rerank=True, # +150ms rerank=True, # +150ms
filter_memories=True, # +250ms
user_id="user123" user_id="user123"
) )
latency = time.time() - start_time 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 ### Optimization Tips
1. **Cache frequent queries** to avoid repeated advanced processing 1. **Cache frequent queries** to avoid repeated advanced processing
2. **Use session-specific search** with `run_id` to reduce search space 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 4. **Monitor and alert** on search latency patterns
<Snippet file="get-help.mdx" /> <Snippet file="get-help.mdx" />
@@ -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>
@@ -38,11 +38,7 @@ Before defining any criteria, make sure to initialize the `MemoryClient` with yo
```python ```python
from mem0 import MemoryClient from mem0 import MemoryClient
client = MemoryClient( client = MemoryClient(api_key="your_mem0_api_key")
api_key="your_mem0_api_key",
org_id="your_organization_id",
project_id="your_project_id"
)
``` ```
### Define Your Criteria ### Define Your Criteria
+2 -2
View File
@@ -98,7 +98,7 @@ messages = [
] ]
# Add memories with project-level custom categories # Add memories with project-level custom categories
client.add(messages, user_id="alice", async_mode=False) client.add(messages, user_id="alice")
``` ```
</CodeGroup> </CodeGroup>
@@ -187,7 +187,7 @@ messages = [
] ]
# Add memories with default categories # Add memories with default categories
client.add(messages, user_id='alice', async_mode=False) client.add(messages, user_id='alice')
``` ```
```python Memories with categories ```python Memories with categories
-109
View File
@@ -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" />
+4 -20
View File
@@ -30,11 +30,7 @@ When adding new memories, enable Graph Memory to automatically build relationshi
```python Python ```python Python
from mem0 import MemoryClient from mem0 import MemoryClient
client = MemoryClient( client = MemoryClient(api_key="your-api-key")
api_key="your-api-key",
org_id="your-org-id",
project_id="your-project-id"
)
messages = [ messages = [
{"role": "user", "content": "My name is Joseph"}, {"role": "user", "content": "My name is Joseph"},
@@ -53,11 +49,7 @@ client.add(
```javascript JavaScript ```javascript JavaScript
import { MemoryClient } from "mem0"; import { MemoryClient } from "mem0";
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: "your-api-key" });
apiKey: "your-api-key",
org_id: "your-org-id",
project_id: "your-project-id"
});
const messages = [ const messages = [
{ role: "user", content: "My name is Joseph" }, { role: "user", content: "My name is Joseph" },
@@ -284,11 +276,7 @@ Instead of passing `enable_graph=True` to every add call, you can enable it once
```python Python ```python Python
from mem0 import MemoryClient from mem0 import MemoryClient
client = MemoryClient( client = MemoryClient(api_key="your-api-key")
api_key="your-api-key",
org_id="your-org-id",
project_id="your-project-id"
)
# Enable graph memory for all operations in this project # Enable graph memory for all operations in this project
client.project.update(enable_graph=True) client.project.update(enable_graph=True)
@@ -309,11 +297,7 @@ client.add(
```javascript JavaScript ```javascript JavaScript
import { MemoryClient } from "mem0"; import { MemoryClient } from "mem0";
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: "your-api-key" });
apiKey: "your-api-key",
org_id: "your-org-id",
project_id: "your-project-id"
});
// Enable graph memory for all operations in this project // Enable graph memory for all operations in this project
await client.project.update({ enable_graph: true }); await client.project.update({ enable_graph: true });
+3 -4
View File
@@ -217,17 +217,16 @@ print(search_response)
## Async Mode Support ## 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> <CodeGroup>
```python Python ```python Python
# Group chat with async mode # Group chat — async processing is the default
response = client.add( response = client.add(
messages, messages,
run_id="groupchat_async", run_id="groupchat_async",
infer=True, infer=True,
async_mode=True
) )
print(response) 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. 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. 6. **Search Context**: Leverage the search functionality to find specific information within group chat contexts.
+7 -2
View File
@@ -3,14 +3,17 @@ import type * as MemoryTypes from "./mem0.types";
// Re-export all types from mem0.types // Re-export all types from mem0.types
export type { export type {
MemoryOptions, EntityOptions,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
ProjectOptions, ProjectOptions,
Memory, Memory,
MemoryHistory, MemoryHistory,
MemoryUpdateBody, MemoryUpdateBody,
ProjectResponse, ProjectResponse,
PromptUpdatePayload, PromptUpdatePayload,
SearchOptions,
Webhook, Webhook,
WebhookCreatePayload, WebhookCreatePayload,
WebhookUpdatePayload, WebhookUpdatePayload,
@@ -19,6 +22,8 @@ export type {
AllUsers, AllUsers,
User, User,
FeedbackPayload, FeedbackPayload,
CreateMemoryExportPayload,
GetMemoryExportPayload,
} from "./mem0.types"; } from "./mem0.types";
// Re-export enums as values (not type-only) // Re-export enums as values (not type-only)
+57 -205
View File
@@ -4,11 +4,13 @@ import {
ProjectOptions, ProjectOptions,
Memory, Memory,
MemoryHistory, MemoryHistory,
MemoryOptions, AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
MemoryUpdateBody, MemoryUpdateBody,
ProjectResponse, ProjectResponse,
PromptUpdatePayload, PromptUpdatePayload,
SearchOptions,
Webhook, Webhook,
WebhookCreatePayload, WebhookCreatePayload,
WebhookUpdatePayload, WebhookUpdatePayload,
@@ -30,19 +32,13 @@ class APIError extends Error {
interface ClientOptions { interface ClientOptions {
apiKey: string; apiKey: string;
host?: string; host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
} }
export default class MemoryClient { export default class MemoryClient {
apiKey: string; apiKey: string;
host: string; host: string;
organizationName: string | null; private organizationId: string | number | null;
projectName: string | null; private projectId: string | number | null;
organizationId: string | number | null;
projectId: string | number | null;
headers: Record<string, string>; headers: Record<string, string>;
client: any; client: any;
telemetryId: string; telemetryId: string;
@@ -59,35 +55,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) { constructor(options: ClientOptions) {
this.apiKey = options.apiKey; this.apiKey = options.apiKey;
this.host = options.host || "https://api.mem0.ai"; this.host = options.host || "https://api.mem0.ai";
this.organizationName = options.organizationName || null; this.organizationId = null;
this.projectName = options.projectName || null; this.projectId = null;
this.organizationId = options.organizationId || null;
this.projectId = options.projectId || null;
this.headers = { this.headers = {
Authorization: `Token ${this.apiKey}`, Authorization: `Token ${this.apiKey}`,
@@ -101,28 +73,19 @@ export default class MemoryClient {
}); });
this._validateApiKey(); this._validateApiKey();
// Initialize with a temporary ID that will be updated
this.telemetryId = ""; this.telemetryId = "";
// Initialize the client
this._initializeClient(); this._initializeClient();
} }
private async _initializeClient() { private async _initializeClient() {
try { try {
// Generate telemetry ID
await this.ping(); await this.ping();
if (!this.telemetryId) { if (!this.telemetryId) {
this.telemetryId = generateHash(this.apiKey); this.telemetryId = generateHash(this.apiKey);
} }
this._validateOrgProject();
// Capture initialization event
captureClientEvent("init", this, { captureClientEvent("init", this, {
api_version: "v1",
client_type: "MemoryClient", client_type: "MemoryClient",
}).catch((error: any) => { }).catch((error: any) => {
console.error("Failed to capture event:", error); console.error("Failed to capture event:", error);
@@ -163,13 +126,16 @@ export default class MemoryClient {
return jsonResponse; return jsonResponse;
} }
_preparePayload(messages: Array<Message>, options: MemoryOptions): object { _preparePayload(
messages: Array<Message>,
options: Record<string, any>,
): object {
const payload: any = {}; const payload: any = {};
payload.messages = messages; payload.messages = messages;
return { ...payload, ...options }; return { ...payload, ...options };
} }
_prepareParams(options: MemoryOptions): object { _prepareParams(options: Record<string, any>): object {
return Object.fromEntries( return Object.fromEntries(
Object.entries(options).filter(([_, v]) => v != null), Object.entries(options).filter(([_, v]) => v != null),
); );
@@ -197,9 +163,8 @@ export default class MemoryClient {
const { org_id, project_id, user_email } = response; const { org_id, project_id, user_email } = response;
// Only update if values are actually present if (org_id) this.organizationId = org_id;
if (org_id && !this.organizationId) this.organizationId = org_id; if (project_id) this.projectId = project_id;
if (project_id && !this.projectId) this.projectId = project_id;
if (user_email) this.telemetryId = user_email; if (user_email) this.telemetryId = user_email;
} catch (error: any) { } catch (error: any) {
// Pass through structured exceptions and APIError // Pass through structured exceptions and APIError
@@ -215,30 +180,11 @@ export default class MemoryClient {
async add( async add(
messages: Array<Message>, messages: Array<Message>,
options: MemoryOptions & Record<string, any> = {}, options: AddMemoryOptions & Record<string, any> = {},
): Promise<Array<Memory>> { ): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping(); 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); const payload = this._preparePayload(messages, options);
// get payload keys whose value is not null or undefined
const payloadKeys = Object.keys(payload); const payloadKeys = Object.keys(payload);
this._captureEvent("add", [payloadKeys]); this._captureEvent("add", [payloadKeys]);
@@ -276,7 +222,6 @@ export default class MemoryClient {
} }
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payload: Record<string, any> = {}; const payload: Record<string, any> = {};
if (text !== undefined) payload.text = text; if (text !== undefined) payload.text = text;
if (metadata !== undefined) payload.metadata = metadata; if (metadata !== undefined) payload.metadata = metadata;
@@ -307,87 +252,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(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {}); const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]); this._captureEvent("get_all", [payloadKeys]);
const { api_version, page, page_size, ...otherOptions } = options ?? {}; const { page, page_size, ...rest } = options ?? {};
if (this.organizationName != null && this.projectName != null) { const body: Record<string, any> = {
otherOptions.org_name = this.organizationName; output_format: "v1.1",
otherOptions.project_name = this.projectName; ...rest,
} };
let appendedParams = "";
let paginated_response = false;
let url = `${this.host}/v2/memories/`;
if (page && page_size) { if (page && page_size) {
appendedParams += `page=${page}&page_size=${page_size}`; url += `?page=${page}&page_size=${page_size}`;
paginated_response = true;
} }
if (this.organizationId != null && this.projectId != null) { const response = await this._fetchWithErrorHandling(url, {
otherOptions.org_id = this.organizationId; method: "POST",
otherOptions.project_id = this.projectId; headers: this.headers,
body: JSON.stringify(body),
if (otherOptions.org_name) delete otherOptions.org_name; });
if (otherOptions.project_name) delete otherOptions.project_name; // Unwrap v1.1 format: { results: [...] } → [...]
} return Array.isArray(response) ? response : (response?.results ?? response);
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,
});
}
} }
async search( async search(
query: string, query: string,
options?: SearchOptions & Record<string, any>, options?: SearchMemoryOptions,
): Promise<Array<Memory>> { ): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {}); const payloadKeys = Object.keys(options || {});
this._captureEvent("search", [payloadKeys]); this._captureEvent("search", [payloadKeys]);
const { api_version, ...otherOptions } = options ?? {}; const payload: Record<string, any> = {
const payload = { query, ...otherOptions }; query,
if (this.organizationName != null && this.projectName != null) { output_format: "v1.1",
payload.org_name = this.organizationName; ...(options ?? {}),
payload.project_name = this.projectName; };
}
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( const response = await this._fetchWithErrorHandling(
`${this.host}${endpoint}`, `${this.host}/v2/memories/search/`,
{ {
method: "POST", method: "POST",
headers: this.headers, headers: this.headers,
body: JSON.stringify(payload), 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 }> { async delete(memoryId: string): Promise<{ message: string }> {
@@ -402,23 +313,12 @@ export default class MemoryClient {
); );
} }
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> { async deleteAll(
options: DeleteAllMemoryOptions = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {}); const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]); 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;
}
// @ts-ignore // @ts-ignore
const params = new URLSearchParams(this._prepareParams(options)); const params = new URLSearchParams(this._prepareParams(options));
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
@@ -443,31 +343,20 @@ export default class MemoryClient {
return response; return response;
} }
async users(): Promise<AllUsers> { async users(options?: {
page?: number;
page_size?: number;
}): Promise<AllUsers> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("users", []); this._captureEvent("users", []);
const options: MemoryOptions = {}; let url = `${this.host}/v1/entities/`;
if (this.organizationName != null && this.projectName != null) { const params: string[] = [];
options.org_name = this.organizationName; if (options?.page) params.push(`page=${options.page}`);
options.project_name = this.projectName; if (options?.page_size) params.push(`page_size=${options.page_size}`);
} if (params.length) url += `?${params.join("&")}`;
const response = await this._fetchWithErrorHandling(url, {
if (this.organizationId != null && this.projectId != null) { headers: this.headers,
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,
},
);
return response; return response;
} }
@@ -502,7 +391,6 @@ export default class MemoryClient {
} = {}, } = {},
): Promise<{ message: string }> { ): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
let to_delete: Array<{ type: string; name: string }> = []; let to_delete: Array<{ type: string; name: string }> = [];
const { user_id, agent_id, app_id, run_id } = params; const { user_id, agent_id, app_id, run_id } = params;
@@ -527,29 +415,9 @@ export default class MemoryClient {
throw new Error("No entities to delete"); 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) { for (const entity of to_delete) {
try { try {
await this.client.delete( await this.client.delete(`/v2/entities/${entity.type}/${entity.name}/`);
`/v2/entities/${entity.type}/${entity.name}/`,
{
params: requestOptions,
},
);
} catch (error: any) { } catch (error: any) {
throw new APIError( throw new APIError(
`Failed to delete ${entity.type} ${entity.name}: ${error.message}`, `Failed to delete ${entity.type} ${entity.name}: ${error.message}`,
@@ -558,13 +426,7 @@ export default class MemoryClient {
} }
this._captureEvent("delete_users", [ this._captureEvent("delete_users", [
{ { user_id, agent_id, app_id, run_id, sync_type: "sync" },
user_id: user_id,
agent_id: agent_id,
app_id: app_id,
run_id: run_id,
sync_type: "sync",
},
]); ]);
return { return {
@@ -612,7 +474,6 @@ export default class MemoryClient {
async getProject(options: ProjectOptions): Promise<ProjectResponse> { async getProject(options: ProjectOptions): Promise<ProjectResponse> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {}); const payloadKeys = Object.keys(options || {});
this._captureEvent("get_project", [payloadKeys]); this._captureEvent("get_project", [payloadKeys]);
const { fields } = options; const { fields } = options;
@@ -639,7 +500,6 @@ export default class MemoryClient {
prompts: PromptUpdatePayload, prompts: PromptUpdatePayload,
): Promise<Record<string, any>> { ): Promise<Record<string, any>> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("update_project", []); this._captureEvent("update_project", []);
if (!(this.organizationId && this.projectId)) { if (!(this.organizationId && this.projectId)) {
throw new Error( throw new Error(
@@ -748,15 +608,10 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
this._captureEvent("create_memory_export", []); this._captureEvent("create_memory_export", []);
// Return if missing filters or schema
if (!data.filters || !data.schema) { if (!data.filters || !data.schema) {
throw new Error("Missing filters or 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( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/`, `${this.host}/v1/exports/`,
{ {
@@ -779,9 +634,6 @@ export default class MemoryClient {
throw new Error("Missing memory_export_id or filters"); throw new Error("Missing memory_export_id or filters");
} }
data.org_id = this.organizationId?.toString() || "";
data.project_id = this.projectId?.toString() || "";
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/get/`, `${this.host}/v1/exports/get/`,
{ {
+63 -76
View File
@@ -1,59 +1,70 @@
interface Common { // ─── Entity Options (for add/delete — top-level identity) ───
project_id?: string | null; export interface EntityOptions {
org_id?: string | null;
}
export interface MemoryOptions {
api_version?: API_VERSION | string;
version?: API_VERSION | string;
user_id?: string; user_id?: string;
agent_id?: string; agent_id?: string;
app_id?: string; app_id?: string;
run_id?: string; run_id?: string;
}
// ─── Per-Method Options ─────────────────────────────────────
export interface AddMemoryOptions extends EntityOptions {
metadata?: Record<string, any>; 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; 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_categories?: custom_categories[];
custom_instructions?: string; custom_instructions?: string;
timestamp?: number; timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
filter_memories?: boolean;
immutable?: boolean;
structured_data_schema?: Record<string, any>; structured_data_schema?: Record<string, any>;
enable_graph?: boolean;
} }
export interface SearchMemoryOptions {
filters?: Record<string, any>;
metadata?: Record<string, any>;
top_k?: number;
threshold?: number;
rerank?: boolean;
fields?: string[];
categories?: string[];
enable_graph?: boolean;
}
export interface GetAllMemoryOptions {
filters?: Record<string, any>;
page?: number;
page_size?: number;
start_date?: string;
end_date?: string;
categories?: string[];
enable_graph?: boolean;
}
export interface DeleteAllMemoryOptions extends EntityOptions {}
// ─── Project Options ────────────────────────────────────────
export interface ProjectOptions { export interface ProjectOptions {
fields?: string[]; fields?: string[];
} }
export enum OutputFormat { export interface PromptUpdatePayload {
V1 = "v1.0", custom_instructions?: string;
V1_1 = "v1.1", custom_categories?: custom_categories[];
} retrieval_criteria?: any[];
enable_graph?: boolean;
export enum API_VERSION { version?: string;
V1 = "v1", memory_depth?: string | null;
V2 = "v2", usecase_setting?: string | number;
multilingual?: boolean;
[key: string]: any;
} }
// ─── Enums ──────────────────────────────────────────────────
export enum Feedback { export enum Feedback {
POSITIVE = "POSITIVE", POSITIVE = "POSITIVE",
NEGATIVE = "NEGATIVE", NEGATIVE = "NEGATIVE",
VERY_NEGATIVE = "VERY_NEGATIVE", VERY_NEGATIVE = "VERY_NEGATIVE",
} }
// ─── Message Types ──────────────────────────────────────────
export interface MultiModalMessages { export interface MultiModalMessages {
type: "image_url"; type: "image_url";
image_url: { image_url: {
@@ -68,30 +79,9 @@ export interface Messages {
export interface Message extends Messages {} export interface Message extends Messages {}
export interface MemoryHistory { // ─── Response Types (reflect API shapes, unchanged) ─────────
id: string; export interface MemoryData {
memory_id: string; memory: 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;
} }
enum Event { enum Event {
@@ -101,10 +91,6 @@ enum Event {
NOOP = "NOOP", NOOP = "NOOP",
} }
export interface MemoryData {
memory: string;
}
export interface Memory { export interface Memory {
id: string; id: string;
messages?: Array<Messages>; messages?: Array<Messages>;
@@ -125,6 +111,19 @@ export interface Memory {
run_id?: string | null; run_id?: string | null;
} }
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 MemoryUpdateBody { export interface MemoryUpdateBody {
memoryId: string; memoryId: string;
text: string; text: string;
@@ -157,20 +156,7 @@ interface custom_categories {
[key: string]: any; [key: string]: any;
} }
export interface PromptUpdatePayload { // ─── Webhook Types ──────────────────────────────────────────
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;
}
export enum WebhookEvent { export enum WebhookEvent {
MEMORY_ADDED = "memory_add", MEMORY_ADDED = "memory_add",
MEMORY_UPDATED = "memory_update", MEMORY_UPDATED = "memory_update",
@@ -202,19 +188,20 @@ export interface WebhookUpdatePayload {
eventTypes?: WebhookEvent[]; eventTypes?: WebhookEvent[];
} }
// ─── Feedback & Export Types ────────────────────────────────
export interface FeedbackPayload { export interface FeedbackPayload {
memory_id: string; memory_id: string;
feedback?: Feedback | null; feedback?: Feedback | null;
feedback_reason?: string | null; feedback_reason?: string | null;
} }
export interface CreateMemoryExportPayload extends Common { export interface CreateMemoryExportPayload {
schema: Record<string, any>; schema: Record<string, any>;
filters: Record<string, any>; filters: Record<string, any>;
export_instructions?: string; export_instructions?: string;
} }
export interface GetMemoryExportPayload extends Common { export interface GetMemoryExportPayload {
filters?: Record<string, any>; filters?: Record<string, any>;
memory_export_id?: string; memory_export_id?: string;
} }
@@ -122,7 +122,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
// ─── Get all ────────────────────────────────────────────── // ─── Get all ──────────────────────────────────────────────
describe("get all memories", () => { describe("get all memories", () => {
test("returns all memories for test user", async () => { test("returns all memories for test user", async () => {
const memories = await client.getAll({ user_id: TEST_USER_ID }); const memories = await client.getAll({
filters: { user_id: TEST_USER_ID },
});
expect(Array.isArray(memories)).toBe(true); expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length); expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
@@ -135,7 +137,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("returns paginated results with page and page_size", async () => { test("returns paginated results with page and page_size", async () => {
const page1 = await client.getAll({ const page1 = await client.getAll({
user_id: TEST_USER_ID, filters: { user_id: TEST_USER_ID },
page: 1, page: 1,
page_size: 1, page_size: 1,
}); });
@@ -200,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("getAll for non-existent user returns empty array", async () => { test("getAll for non-existent user returns empty array", async () => {
const memories = await client.getAll({ const memories = await client.getAll({
user_id: `nonexistent-user-${randomUUID()}`, filters: { user_id: `nonexistent-user-${randomUUID()}` },
}); });
expect(Array.isArray(memories)).toBe(true); expect(Array.isArray(memories)).toBe(true);
@@ -63,7 +63,9 @@ export async function waitForMemories(
maxRetries = 4, maxRetries = 4,
): Promise<Memory[]> { ): Promise<Memory[]> {
for (let attempt = 1; attempt <= maxRetries; attempt++) { for (let attempt = 1; attempt <= maxRetries; attempt++) {
const memories = await withRetry(() => client.getAll({ user_id: userId })); const memories = await withRetry(() =>
client.getAll({ filters: { user_id: userId } }),
);
if (Array.isArray(memories) && memories.length >= minCount) { if (Array.isArray(memories) && memories.length >= minCount) {
return memories; return memories;
} }
@@ -31,10 +31,10 @@ describeIntegration("MemoryClient Integration — Initialization", () => {
afterAll(() => cleanup()); afterAll(() => cleanup());
test("client pings successfully and resolves org/project", async () => { test("client pings successfully", async () => {
await client.ping(); await client.ping();
expect(client.organizationId).toBeTruthy(); // org/project are now resolved internally from the API key
expect(client.projectId).toBeTruthy(); expect(client.telemetryId).toBeTruthy();
}); });
test("get with invalid ID throws ValidationError", async () => { test("get with invalid ID throws ValidationError", async () => {
@@ -1,7 +1,7 @@
/** /**
* Integration tests: Search and history operations. * 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 * Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
*/ */
@@ -36,14 +36,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
cleanup(); cleanup();
}); });
// ─── Search v1 ──────────────────────────────────────────── // ─── Search ─────────────────────────────────────────────
describe("search v1", () => { describe("search", () => {
test("searches memories by user_id and returns results with scores", async () => { test("searches memories by user_id and returns results with scores", async () => {
// Search index may lag behind listing index — poll until ready // Search index may lag behind listing index — poll until ready
const results = await waitForSearchResults( const results = await waitForSearchResults(
client, client,
"What is my favorite color?", "What is my favorite color?",
{ user_id: TEST_USER_ID }, { filters: { user_id: TEST_USER_ID } },
); );
expect(Array.isArray(results)).toBe(true); expect(Array.isArray(results)).toBe(true);
@@ -57,15 +57,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
}); });
}); });
// ─── Search v2 ──────────────────────────────────────────── // ─── Search with filters ─────────────────────────────────
describe("search v2", () => { describe("search with filters", () => {
test("searches with OR filters and returns results", async () => { test("searches with OR filters and returns results", async () => {
const results = await waitForSearchResults( const results = await waitForSearchResults(
client, client,
"What do you know about me?", "What do you know about me?",
{ {
filters: { OR: [{ user_id: TEST_USER_ID }] }, filters: { OR: [{ user_id: TEST_USER_ID }] },
api_version: "v2",
}, },
); );
@@ -110,19 +109,19 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
describe("edge cases", () => { describe("edge cases", () => {
test("search for non-existent user returns empty results", async () => { test("search for non-existent user returns empty results", async () => {
const results = await client.search("anything", { const results = await client.search("anything", {
user_id: `nonexistent-user-${randomUUID()}`, filters: { user_id: `nonexistent-user-${randomUUID()}` },
}); });
expect(Array.isArray(results)).toBe(true); expect(Array.isArray(results)).toBe(true);
expect(results.length).toBe(0); 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( const results = await client.search(
"Tell me about integration test user", "Tell me about integration test user",
{ {
user_id: TEST_USER_ID, filters: { user_id: TEST_USER_ID },
limit: 1, top_k: 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. * Tests verify request construction, not mock response echo.
*/ */
import { MemoryClient } from "../mem0"; import { MemoryClient } from "../mem0";
import type { Memory, MemoryHistory } from "../mem0.types"; import type { MemoryHistory } from "../mem0.types";
import { import {
createMockMemory, createMockMemory,
createMockMemoryHistory, createMockMemoryHistory,
TEST_API_KEY, TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers"; } from "./helpers";
import { import {
setupMockFetch, setupMockFetch,
@@ -61,40 +59,6 @@ describe("MemoryClient - add()", () => {
expect(getFetchBody(call!).user_id).toBe("user_1"); 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 () => { test("sends empty messages array without crashing", async () => {
const extra = new Map<string, { status: number; body: unknown }>(); const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] }); extra.set("/v1/memories/", { status: 200, body: [] });
@@ -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() ──────────────────────────────────────────── // ─── update() ────────────────────────────────────────────
describe("MemoryClient - update()", () => { describe("MemoryClient - update()", () => {
@@ -7,13 +7,7 @@ import {
ValidationError, ValidationError,
MemoryError, MemoryError,
} from "../../common/exceptions"; } from "../../common/exceptions";
import { import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
createMockFetch,
TEST_API_KEY,
TEST_HOST,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { import {
setupMockFetch, setupMockFetch,
installConsoleSuppression, installConsoleSuppression,
@@ -55,24 +49,6 @@ describe("MemoryClient - Initialization", () => {
expect(client.host).toBe(TEST_HOST); 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", () => { test("sets Authorization header with Token prefix", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`); expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
@@ -87,20 +63,6 @@ describe("MemoryClient - Initialization", () => {
// ─── Ping ──────────────────────────────────────────────── // ─── Ping ────────────────────────────────────────────────
describe("MemoryClient - 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 () => { test("sets telemetryId from user_email in response", async () => {
setupMockFetch(); setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -108,28 +70,6 @@ describe("MemoryClient - ping()", () => {
expect(client.telemetryId).toBe("test@example.com"); 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 () => { test("throws AuthenticationError on 401 response", async () => {
const { AuthenticationError } = await import("../../common/exceptions"); const { AuthenticationError } = await import("../../common/exceptions");
const responses = new Map<string, { status: number; body: unknown }>(); const responses = new Map<string, { status: number; body: unknown }>();
@@ -4,12 +4,7 @@
*/ */
import { MemoryClient } from "../mem0"; import { MemoryClient } from "../mem0";
import { Feedback } from "../mem0.types"; import { Feedback } from "../mem0.types";
import { import { createMockFetch, TEST_API_KEY } from "./helpers";
createMockFetch,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { import {
setupMockFetch, setupMockFetch,
findFetchCall, findFetchCall,
@@ -22,7 +17,7 @@ installConsoleSuppression();
// ─── getProject() ─────────────────────────────────────── // ─── getProject() ───────────────────────────────────────
describe("MemoryClient - 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 }>(); const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", { status: 200, body: { status: "ok" } }); responses.set("/v1/ping/", { status: 200, body: { status: "ok" } });
global.fetch = createMockFetch(responses); global.fetch = createMockFetch(responses);
@@ -47,11 +42,9 @@ describe("MemoryClient - getProject()", () => {
}); });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ // org/project come from ping mock response
apiKey: TEST_API_KEY, const client = new MemoryClient({ apiKey: TEST_API_KEY });
organizationId: TEST_ORG_ID, await client.ping();
projectId: TEST_PROJECT_ID,
});
await client.getProject({ fields: ["custom_instructions"] }); await client.getProject({ fields: ["custom_instructions"] });
const call = mock.mock.calls.find( const call = mock.mock.calls.find(
@@ -74,11 +67,8 @@ describe("MemoryClient - updateProject()", () => {
}); });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: TEST_API_KEY });
apiKey: TEST_API_KEY, await client.ping();
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.updateProject({ await client.updateProject({
custom_instructions: "Updated instructions", custom_instructions: "Updated instructions",
}); });
@@ -95,11 +85,8 @@ describe("MemoryClient - updateProject()", () => {
}); });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: TEST_API_KEY });
apiKey: TEST_API_KEY, await client.ping();
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.updateProject({ await client.updateProject({
custom_instructions: "Updated instructions", custom_instructions: "Updated instructions",
}); });
@@ -161,11 +148,7 @@ describe("MemoryClient - feedback()", () => {
describe("MemoryClient - Memory Exports", () => { describe("MemoryClient - Memory Exports", () => {
test("createMemoryExport throws when missing filters or schema", async () => { test("createMemoryExport throws when missing filters or schema", async () => {
setupMockFetch(); setupMockFetch();
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: TEST_API_KEY });
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await expect( await expect(
client.createMemoryExport({ client.createMemoryExport({
filters: null as never, filters: null as never,
@@ -182,11 +165,7 @@ describe("MemoryClient - Memory Exports", () => {
}); });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: TEST_API_KEY });
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.createMemoryExport({ await client.createMemoryExport({
schema: { fields: ["memory", "user_id"] }, schema: { fields: ["memory", "user_id"] },
filters: { user_id: "u1" }, filters: { user_id: "u1" },
@@ -195,37 +174,9 @@ describe("MemoryClient - Memory Exports", () => {
expect(findFetchCall(mock, "/v1/exports/", "POST")).toBeDefined(); 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 () => { test("getMemoryExport throws when missing both id and filters", async () => {
setupMockFetch(); setupMockFetch();
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: TEST_API_KEY });
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await expect(client.getMemoryExport({} as never)).rejects.toThrow( await expect(client.getMemoryExport({} as never)).rejects.toThrow(
"Missing memory_export_id or filters", "Missing memory_export_id or filters",
); );
@@ -239,11 +190,7 @@ describe("MemoryClient - Memory Exports", () => {
}); });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ const client = new MemoryClient({ apiKey: TEST_API_KEY });
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.getMemoryExport({ memory_export_id: "exp_123" }); await client.getMemoryExport({ memory_export_id: "exp_123" });
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined(); 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. * Tests verify request construction, not mock response echo.
*/ */
import { MemoryClient } from "../mem0"; import { MemoryClient } from "../mem0";
@@ -15,60 +15,52 @@ import {
installConsoleSuppression(); installConsoleSuppression();
describe("MemoryClient - search()", () => { describe("MemoryClient - search()", () => {
test("sends POST to /v1/memories/search/ by default", async () => { test("sends POST to /v2/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 () => {
const extra = new Map<string, { status: number; body: unknown }>(); const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] }); extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); 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: { user_id: "u1" },
});
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined(); 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: { user_id: "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: { user_id: "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 }>(); const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] }); extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("query", { await client.search("query", {
api_version: "v2",
filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] }, filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] },
}); });
@@ -81,7 +73,7 @@ describe("MemoryClient - search()", () => {
test("does not crash when called without options", async () => { test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>(); 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); setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -91,12 +83,12 @@ describe("MemoryClient - search()", () => {
test("handles empty results array", async () => { test("handles empty results array", async () => {
const extra = new Map<string, { status: number; body: unknown }>(); 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); setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.search("nonexistent query", { const result: Memory[] = await client.search("nonexistent query", {
user_id: "u1", filters: { user_id: "u1" },
}); });
expect(result).toHaveLength(0); 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. * Tests verify entity type routing and request construction.
*/ */
import { MemoryClient } from "../mem0"; import { MemoryClient } from "../mem0";
import { import { createMockUser, createMockAllUsers, TEST_API_KEY } from "./helpers";
createMockUser,
createMockAllUsers,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { import {
setupMockFetch, setupMockFetch,
findFetchCall, 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) ────────────────────────── // ─── deleteUser() (deprecated) ──────────────────────────
describe("MemoryClient - deleteUser() (deprecated)", () => { describe("MemoryClient - deleteUser() (deprecated)", () => {
@@ -5,7 +5,7 @@
*/ */
import { MemoryClient } from "../mem0"; import { MemoryClient } from "../mem0";
import { WebhookEvent } from "../mem0.types"; import { WebhookEvent } from "../mem0.types";
import { TEST_API_KEY, TEST_ORG_ID, TEST_PROJECT_ID } from "./helpers"; import { TEST_API_KEY } from "./helpers";
import { import {
setupMockFetch, setupMockFetch,
findFetchCall, findFetchCall,
@@ -23,8 +23,6 @@ function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
function createClient() { function createClient() {
return new MemoryClient({ return new MemoryClient({
apiKey: TEST_API_KEY, apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
}); });
} }
@@ -100,14 +98,6 @@ describe("MemoryClient - createWebhook", () => {
expect(body.eventTypes).toBeUndefined(); 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 () => { test("body does not contain webhookId", async () => {
const mock = await callCreate(); const mock = await callCreate();
const body = getFetchBody( const body = getFetchBody(
@@ -173,22 +163,6 @@ describe("MemoryClient - updateWebhook", () => {
expect(body.eventTypes).toBeUndefined(); 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 () => { test("body does not contain webhookId", async () => {
const mock = await callUpdate(); const mock = await callUpdate();
const body = getFetchBody( const body = getFetchBody(
@@ -1,5 +1,10 @@
import { MemoryClient } from "mem0ai"; import { MemoryClient } from "mem0ai";
import type { Memory, MemoryOptions, SearchOptions } from "mem0ai"; import type {
Memory,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
} from "mem0ai";
import { import {
InputValues, InputValues,
@@ -102,10 +107,6 @@ export const mem0MemoryToMessages = (memories: Memory[]): BaseMessage[] => {
export interface ClientOptions { export interface ClientOptions {
apiKey: string; apiKey: string;
host?: string; host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
} }
/** /**
@@ -117,7 +118,7 @@ export interface Mem0MemoryInput extends BaseChatMemoryInput {
apiKey: string; apiKey: string;
humanPrefix?: string; humanPrefix?: string;
aiPrefix?: string; aiPrefix?: string;
memoryOptions?: MemoryOptions | SearchOptions; memoryOptions?: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
mem0Options?: ClientOptions; mem0Options?: ClientOptions;
separateMessages?: boolean; separateMessages?: boolean;
} }
@@ -160,7 +161,7 @@ export class Mem0Memory extends BaseChatMemory implements Mem0MemoryInput {
mem0Client: InstanceType<typeof MemoryClient>; mem0Client: InstanceType<typeof MemoryClient>;
memoryOptions: MemoryOptions | SearchOptions; memoryOptions: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
mem0Options: ClientOptions; mem0Options: ClientOptions;
+1 -1
View File
@@ -131,7 +131,7 @@ export class ConfigManager {
userConfig.historyDbPath || userConfig.historyDbPath ||
userConfig.historyStore?.config?.historyDbPath || userConfig.historyStore?.config?.historyDbPath ||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath, DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
customPrompt: userConfig.customPrompt, customInstructions: userConfig.customInstructions,
graphStore: { graphStore: {
...DEFAULT_MEMORY_CONFIG.graphStore, ...DEFAULT_MEMORY_CONFIG.graphStore,
...userConfig.graphStore, ...userConfig.graphStore,
+1 -1
View File
@@ -10,7 +10,7 @@ export interface GraphStoreConfig {
provider: string; provider: string;
config: Neo4jConfig; config: Neo4jConfig;
llm?: LLMConfig; llm?: LLMConfig;
customPrompt?: string; customInstructions?: string;
} }
export function validateNeo4jConfig(config: Neo4jConfig): void { export function validateNeo4jConfig(config: Neo4jConfig): void {
+7 -7
View File
@@ -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 entityTypeMap = await this._retrieveNodesFromData(query, filters);
const searchOutput = await this._searchGraphDb( const searchOutput = await this._searchGraphDb(
Object.keys(entityTypeMap), 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(); const session = this.graph.session();
try { try {
const result = await session.run( 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 RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT toInteger($limit) 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) => ({ const finalResults = result.records.map((record) => ({
@@ -253,7 +253,7 @@ export class MemoryGraph {
entityTypeMap: Record<string, string>, entityTypeMap: Record<string, string>,
) { ) {
let messages; let messages;
if (this.config.graphStore?.customPrompt) { if (this.config.graphStore?.customInstructions) {
messages = [ messages = [
{ {
role: "system", role: "system",
@@ -263,7 +263,7 @@ export class MemoryGraph {
filters["userId"], filters["userId"],
).replace( ).replace(
"CUSTOM_PROMPT", "CUSTOM_PROMPT",
`4. ${this.config.graphStore.customPrompt}`, `4. ${this.config.graphStore.customInstructions}`,
) + "\nPlease provide your response in JSON format.", ) + "\nPlease provide your response in JSON format.",
}, },
{ role: "user", content: data }, { role: "user", content: data },
@@ -307,7 +307,7 @@ export class MemoryGraph {
private async _searchGraphDb( private async _searchGraphDb(
nodeList: string[], nodeList: string[],
filters: Record<string, any>, filters: Record<string, any>,
limit = 100, topK = 100,
): Promise<SearchOutput[]> { ): Promise<SearchOutput[]> {
const resultRelations: SearchOutput[] = []; const resultRelations: SearchOutput[] = [];
const session = this.graph.session(); const session = this.graph.session();
@@ -344,7 +344,7 @@ export class MemoryGraph {
n_embedding: nEmbedding, n_embedding: nEmbedding,
threshold: this.threshold, threshold: this.threshold,
user_id: filters["userId"], user_id: filters["userId"],
limit: Math.floor(Number(limit)), limit: Math.floor(Number(topK)),
}); });
resultRelations.push( resultRelations.push(
+12 -12
View File
@@ -39,7 +39,7 @@ import { captureClientEvent } from "../utils/telemetry";
export class Memory { export class Memory {
private config: MemoryConfig; private config: MemoryConfig;
private customPrompt: string | undefined; private customInstructions: string | undefined;
private embedder: Embedder; private embedder: Embedder;
private vectorStore!: VectorStore; private vectorStore!: VectorStore;
private llm: LLM; private llm: LLM;
@@ -56,7 +56,7 @@ export class Memory {
// Merge and validate config // Merge and validate config
this.config = ConfigManager.mergeConfig(config); this.config = ConfigManager.mergeConfig(config);
this.customPrompt = this.config.customPrompt; this.customInstructions = this.config.customInstructions;
this.embedder = EmbedderFactory.create( this.embedder = EmbedderFactory.create(
this.config.embedder.provider, this.config.embedder.provider,
this.config.embedder.config, this.config.embedder.config,
@@ -293,11 +293,11 @@ export class Memory {
} }
const parsedMessages = messages.map((m) => m.content).join("\n"); 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.customInstructions.toLowerCase().includes("json")
? this.customPrompt ? this.customInstructions
: `${this.customPrompt}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`, : `${this.customInstructions}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
`Input:\n${parsedMessages}`, `Input:\n${parsedMessages}`,
] ]
: getFactRetrievalMessages(parsedMessages); : getFactRetrievalMessages(parsedMessages);
@@ -478,10 +478,10 @@ export class Memory {
await this._ensureInitialized(); await this._ensureInitialized();
await this._captureEvent("search", { await this._captureEvent("search", {
query_length: query.length, query_length: query.length,
limit: config.limit, topK: config.topK,
has_filters: !!config.filters, 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 (userId) filters.userId = userId;
if (agentId) filters.agentId = agentId; if (agentId) filters.agentId = agentId;
@@ -497,7 +497,7 @@ export class Memory {
const queryEmbedding = await this.embedder.embed(query); const queryEmbedding = await this.embedder.embed(query);
const memories = await this.vectorStore.search( const memories = await this.vectorStore.search(
queryEmbedding, queryEmbedding,
limit, topK,
filters, filters,
); );
@@ -642,19 +642,19 @@ export class Memory {
async getAll(config: GetAllMemoryOptions): Promise<SearchResult> { async getAll(config: GetAllMemoryOptions): Promise<SearchResult> {
await this._ensureInitialized(); await this._ensureInitialized();
await this._captureEvent("get_all", { await this._captureEvent("get_all", {
limit: config.limit, topK: config.topK,
has_user_id: !!config.userId, has_user_id: !!config.userId,
has_agent_id: !!config.agentId, has_agent_id: !!config.agentId,
has_run_id: !!config.runId, has_run_id: !!config.runId,
}); });
const { userId, agentId, runId, limit = 100 } = config; const { userId, agentId, runId, topK = 100 } = config;
const filters: SearchFilters = {}; const filters: SearchFilters = {};
if (userId) filters.userId = userId; if (userId) filters.userId = userId;
if (agentId) filters.agentId = agentId; if (agentId) filters.agentId = agentId;
if (runId) filters.runId = runId; 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([ const excludedKeys = new Set([
"userId", "userId",
+2 -2
View File
@@ -14,12 +14,12 @@ export interface AddMemoryOptions extends Entity {
} }
export interface SearchMemoryOptions extends Entity { export interface SearchMemoryOptions extends Entity {
limit?: number; topK?: number;
filters?: SearchFilters; filters?: SearchFilters;
} }
export interface GetAllMemoryOptions extends Entity { export interface GetAllMemoryOptions extends Entity {
limit?: number; topK?: number;
} }
export interface DeleteAllMemoryOptions extends Entity {} export interface DeleteAllMemoryOptions extends Entity {}
@@ -120,11 +120,11 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687"); expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687");
}); });
it("customPrompt passes through unchanged", () => { it("customInstructions passes through unchanged", () => {
const cfg = ConfigManager.mergeConfig({ 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", () => { it("version override passes through unchanged", () => {
+4 -4
View File
@@ -60,7 +60,7 @@ export interface GraphStoreConfig {
provider: string; provider: string;
config: Neo4jConfig; config: Neo4jConfig;
llm?: LLMConfig; llm?: LLMConfig;
customPrompt?: string; customInstructions?: string;
} }
export interface MemoryConfig { export interface MemoryConfig {
@@ -80,7 +80,7 @@ export interface MemoryConfig {
historyStore?: HistoryStoreConfig; historyStore?: HistoryStoreConfig;
disableHistory?: boolean; disableHistory?: boolean;
historyDbPath?: string; historyDbPath?: string;
customPrompt?: string; customInstructions?: string;
graphStore?: GraphStoreConfig; graphStore?: GraphStoreConfig;
enableGraph?: boolean; enableGraph?: boolean;
} }
@@ -148,7 +148,7 @@ export const MemoryConfigSchema = z.object({
}), }),
}), }),
historyDbPath: z.string().optional(), historyDbPath: z.string().optional(),
customPrompt: z.string().optional(), customInstructions: z.string().optional(),
enableGraph: z.boolean().optional(), enableGraph: z.boolean().optional(),
graphStore: z graphStore: z
.object({ .object({
@@ -164,7 +164,7 @@ export const MemoryConfigSchema = z.object({
config: z.record(z.string(), z.any()), config: z.record(z.string(), z.any()),
}) })
.optional(), .optional(),
customPrompt: z.string().optional(), customInstructions: z.string().optional(),
}) })
.optional(), .optional(),
historyStore: z historyStore: z
@@ -331,7 +331,7 @@ export class AzureAISearch implements VectorStore {
*/ */
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
const filterExpression = filters const filterExpression = filters
@@ -341,7 +341,7 @@ export class AzureAISearch implements VectorStore {
const vectorQuery: VectorizedQuery<any> = { const vectorQuery: VectorizedQuery<any> = {
kind: "vector", kind: "vector",
vector: query, vector: query,
kNearestNeighborsCount: limit, kNearestNeighborsCount: topK,
fields: ["vector"], fields: ["vector"],
}; };
@@ -355,7 +355,7 @@ export class AzureAISearch implements VectorStore {
filterMode: this.vectorFilterMode as any, filterMode: this.vectorFilterMode as any,
}, },
filter: filterExpression, filter: filterExpression,
top: limit, top: topK,
searchFields: ["payload"], searchFields: ["payload"],
}); });
} else { } else {
@@ -366,7 +366,7 @@ export class AzureAISearch implements VectorStore {
filterMode: this.vectorFilterMode as any, filterMode: this.vectorFilterMode as any,
}, },
filter: filterExpression, filter: filterExpression,
top: limit, top: topK,
}); });
} }
@@ -501,7 +501,7 @@ export class AzureAISearch implements VectorStore {
*/ */
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
const filterExpression = filters const filterExpression = filters
? this.buildFilterExpression(filters) ? this.buildFilterExpression(filters)
@@ -509,7 +509,7 @@ export class AzureAISearch implements VectorStore {
const searchResults = await this.searchClient.search("*", { const searchResults = await this.searchClient.search("*", {
filter: filterExpression, filter: filterExpression,
top: limit, top: topK,
}); });
const results: VectorStoreResult[] = []; const results: VectorStoreResult[] = [];
+2 -2
View File
@@ -8,7 +8,7 @@ export interface VectorStore {
): Promise<void>; ): Promise<void>;
search( search(
query: number[], query: number[],
limit?: number, topK?: number,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]>; ): Promise<VectorStoreResult[]>;
get(vectorId: string): Promise<VectorStoreResult | null>; get(vectorId: string): Promise<VectorStoreResult | null>;
@@ -21,7 +21,7 @@ export interface VectorStore {
deleteCol(): Promise<void>; deleteCol(): Promise<void>;
list( list(
filters?: SearchFilters, filters?: SearchFilters,
limit?: number, topK?: number,
): Promise<[VectorStoreResult[], number]>; ): Promise<[VectorStoreResult[], number]>;
getUserId(): Promise<string>; getUserId(): Promise<string>;
setUserId(userId: string): Promise<void>; setUserId(userId: string): Promise<void>;
@@ -101,7 +101,7 @@ export class LangchainVectorStore implements VectorStore {
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, // filters parameter is received but will be ignored filters?: SearchFilters, // filters parameter is received but will be ignored
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
if (this.dimension && query.length !== this.dimension) { if (this.dimension && query.length !== this.dimension) {
@@ -119,7 +119,7 @@ export class LangchainVectorStore implements VectorStore {
// Call similaritySearchVectorWithScore WITHOUT the filter argument // Call similaritySearchVectorWithScore WITHOUT the filter argument
const results = await this.lcStore.similaritySearchVectorWithScore( const results = await this.lcStore.similaritySearchVectorWithScore(
query, query,
limit, topK,
// Do not pass lcFilter here // Do not pass lcFilter here
); );
@@ -192,7 +192,7 @@ export class LangchainVectorStore implements VectorStore {
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
// No standard list method in Langchain core interface. // No standard list method in Langchain core interface.
console.error( console.error(
+4 -4
View File
@@ -100,7 +100,7 @@ export class MemoryVectorStore implements VectorStore {
async search( async search(
query: number[], query: number[],
limit: number = 10, topK: number = 10,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
if (query.length !== this.dimension) { if (query.length !== this.dimension) {
@@ -136,7 +136,7 @@ export class MemoryVectorStore implements VectorStore {
} }
results.sort((a, b) => (b.score || 0) - (a.score || 0)); 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> { async get(vectorId: string): Promise<VectorStoreResult | null> {
@@ -179,7 +179,7 @@ export class MemoryVectorStore implements VectorStore {
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
const rows = this.db.prepare(`SELECT * FROM vectors`).all() as any[]; const rows = this.db.prepare(`SELECT * FROM vectors`).all() as any[];
const results: VectorStoreResult[] = []; 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> { async getUserId(): Promise<string> {
@@ -164,12 +164,12 @@ export class PGVector implements VectorStore {
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
const filterConditions: string[] = []; const filterConditions: string[] = [];
const queryVector = `[${query.join(",")}]`; // Format query vector as string with square brackets 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; let filterIndex = 3;
if (filters) { if (filters) {
@@ -254,7 +254,7 @@ export class PGVector implements VectorStore {
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
const filterConditions: string[] = []; const filterConditions: string[] = [];
const filterValues: any[] = []; const filterValues: any[] = [];
@@ -286,7 +286,7 @@ export class PGVector implements VectorStore {
${filterClause} ${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([ const [listResult, countResult] = await Promise.all([
this.client.query(listQuery, filterValues), this.client.query(listQuery, filterValues),
+4 -4
View File
@@ -139,14 +139,14 @@ export class Qdrant implements VectorStore {
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
const queryFilter = this.createFilter(filters); const queryFilter = this.createFilter(filters);
const results = await this.client.search(this.collectionName, { const results = await this.client.search(this.collectionName, {
vector: query, vector: query,
filter: queryFilter, filter: queryFilter,
limit, limit: topK,
}); });
return results.map((hit) => ({ return results.map((hit) => ({
@@ -198,10 +198,10 @@ export class Qdrant implements VectorStore {
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
const scrollRequest = { const scrollRequest = {
limit, limit: topK,
filter: this.createFilter(filters), filter: this.createFilter(filters),
with_payload: true, with_payload: true,
with_vectors: false, with_vectors: false,
+5 -5
View File
@@ -359,7 +359,7 @@ export class RedisDB implements VectorStore {
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
const snakeFilters = filters ? toSnakeCase(filters) : undefined; const snakeFilters = filters ? toSnakeCase(filters) : undefined;
@@ -391,14 +391,14 @@ export class RedisDB implements VectorStore {
DIALECT: 2, DIALECT: 2,
LIMIT: { LIMIT: {
from: 0, from: 0,
size: limit, size: topK,
}, },
}; };
try { try {
const results = (await this.client.ft.search( const results = (await this.client.ft.search(
this.indexName, this.indexName,
`${filterExpr} =>[KNN ${limit} @embedding $vec AS __vector_score]`, `${filterExpr} =>[KNN ${topK} @embedding $vec AS __vector_score]`,
searchOptions, searchOptions,
)) as unknown as RedisSearchResult; )) as unknown as RedisSearchResult;
@@ -598,7 +598,7 @@ export class RedisDB implements VectorStore {
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
const snakeFilters = filters ? toSnakeCase(filters) : undefined; const snakeFilters = filters ? toSnakeCase(filters) : undefined;
const filterExpr = snakeFilters const filterExpr = snakeFilters
@@ -613,7 +613,7 @@ export class RedisDB implements VectorStore {
SORTDIR: "DESC", SORTDIR: "DESC",
LIMIT: { LIMIT: {
from: 0, from: 0,
size: limit, size: topK,
}, },
}; };
@@ -231,13 +231,13 @@ See the SQL migration instructions in the code comments.`,
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
try { try {
const rpcQuery: VectorQueryParams = { const rpcQuery: VectorQueryParams = {
query_embedding: query, query_embedding: query,
match_count: limit, match_count: topK,
}; };
if (filters) { if (filters) {
@@ -336,13 +336,13 @@ See the SQL migration instructions in the code comments.`,
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 100, topK: number = 100,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
try { try {
let query = this.client let query = this.client
.from(this.tableName) .from(this.tableName)
.select("*", { count: "exact" }) .select("*", { count: "exact" })
.limit(limit); .limit(topK);
if (filters) { if (filters) {
Object.entries(filters).forEach(([key, value]) => { Object.entries(filters).forEach(([key, value]) => {
@@ -76,7 +76,7 @@ export class VectorizeDB implements VectorStore {
async search( async search(
query: number[], query: number[],
limit: number = 5, topK: number = 5,
filters?: SearchFilters, filters?: SearchFilters,
): Promise<VectorStoreResult[]> { ): Promise<VectorStoreResult[]> {
try { try {
@@ -87,7 +87,7 @@ export class VectorizeDB implements VectorStore {
vector: query, vector: query,
filter: filters, filter: filters,
returnMetadata: "all", returnMetadata: "all",
topK: limit, topK: topK,
}, },
); );
@@ -197,7 +197,7 @@ export class VectorizeDB implements VectorStore {
async list( async list(
filters?: SearchFilters, filters?: SearchFilters,
limit: number = 20, topK: number = 20,
): Promise<[VectorStoreResult[], number]> { ): Promise<[VectorStoreResult[], number]> {
try { try {
const result = await this.client?.vectorize.indexes.query( const result = await this.client?.vectorize.indexes.query(
@@ -206,7 +206,7 @@ export class VectorizeDB implements VectorStore {
account_id: this.accountId, account_id: this.accountId,
vector: Array(this.dimensions).fill(0), // Dummy vector for listing vector: Array(this.dimensions).fill(0), // Dummy vector for listing
filter: filters, filter: filters,
topK: limit, topK: topK,
returnMetadata: "all", returnMetadata: "all",
}, },
); );
@@ -305,13 +305,15 @@ describe("_establishNodesRelationsFromData", () => {
expect(systemContent).toContain("test-user"); expect(systemContent).toContain("test-user");
expect(systemContent).not.toContain("USER_ID"); expect(systemContent).not.toContain("USER_ID");
// CUSTOM_PROMPT placeholder stays when no custom prompt is configured // 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 () => { it("appends JSON format suffix and custom prompt when configured", async () => {
mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); 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, {}); await mg._establishNodesRelationsFromData("data", FILTERS, {});
const [messages] = mockGenerateResponse.mock.calls[0]; const [messages] = mockGenerateResponse.mock.calls[0];
+208 -223
View File
@@ -2,13 +2,22 @@ import hashlib
import logging import logging
import os import os
import warnings import warnings
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional
import httpx import httpx
import requests import requests
from mem0.client.project import AsyncProject, Project 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 from mem0.client.utils import api_error_handler
# Exception classes are referenced in docstrings only # Exception classes are referenced in docstrings only
from mem0.memory.setup import get_user_id, setup_config from mem0.memory.setup import get_user_id, setup_config
from mem0.memory.telemetry import capture_client_event 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. api_key (str): The API key for authenticating with the Mem0 API.
host (str): The base URL for the Mem0 API. host (str): The base URL for the Mem0 API.
client (httpx.Client): The HTTP client used for making API requests. 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. user_id (str): Unique identifier for the user.
""" """
@@ -40,8 +47,6 @@ class MemoryClient:
self, self,
api_key: Optional[str] = None, api_key: Optional[str] = None,
host: Optional[str] = None, host: Optional[str] = None,
org_id: Optional[str] = None,
project_id: Optional[str] = None,
client: Optional[httpx.Client] = None, client: Optional[httpx.Client] = None,
): ):
"""Initialize the MemoryClient. """Initialize the MemoryClient.
@@ -52,8 +57,6 @@ class MemoryClient:
environment variable. environment variable.
host: The base URL for the Mem0 API. Defaults to host: The base URL for the Mem0 API. Defaults to
"https://api.mem0.ai". "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 client: A custom httpx.Client instance. If provided, it will be
used instead of creating a new one. Note that base_url and used instead of creating a new one. Note that base_url and
headers will be set/overridden as needed. 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.api_key = api_key or os.getenv("MEM0_API_KEY")
self.host = host or "https://api.mem0.ai" self.host = host or "https://api.mem0.ai"
self.org_id = org_id self.org_id = None
self.project_id = project_id self.project_id = None
self.user_id = get_user_id() self.user_id = get_user_id()
if not self.api_key: if not self.api_key:
@@ -128,15 +131,16 @@ class MemoryClient:
raise ValueError(f"Error: {error_message}") raise ValueError(f"Error: {error_message}")
@api_error_handler @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. """Add a new memory.
Args: Args:
messages: A list of message dictionaries, a single message dictionary, messages: A list of message dictionaries, a single message dictionary,
or a string. If a string is provided, it will be converted to or a string. If a string is provided, it will be converted to
a user message. a user message.
options: Typed options for the add operation (AddMemoryOptions).
**kwargs: Additional parameters such as user_id, agent_id, app_id, **kwargs: Additional parameters such as user_id, agent_id, app_id,
metadata, filters, async_mode. metadata, filters.
Returns: Returns:
A dictionary containing the API response in v1.1 format. A dictionary containing the API response in v1.1 format.
@@ -149,24 +153,19 @@ class MemoryClient:
NetworkError: If network connectivity issues occur. NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). 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) # Handle different message input formats (align with OSS behavior)
if isinstance(messages, str): if isinstance(messages, str):
messages = [{"role": "user", "content": messages}] messages = [{"role": "user", "content": messages}]
elif isinstance(messages, dict): elif isinstance(messages, dict):
messages = [messages] messages = [messages]
elif not isinstance(messages, list): elif not isinstance(messages, list):
raise ValueError( raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
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
# Force v1.1 format for all add operations # Force v1.1 format for all add operations
kwargs["output_format"] = "v1.1" kwargs["output_format"] = "v1.1"
kwargs = self._prepare_params(kwargs)
payload = self._prepare_payload(messages, kwargs) payload = self._prepare_payload(messages, kwargs)
response = self.client.post("/v1/memories/", json=payload) response = self.client.post("/v1/memories/", json=payload)
response.raise_for_status() response.raise_for_status()
@@ -200,10 +199,11 @@ class MemoryClient:
return response.json() return response.json()
@api_error_handler @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. """Retrieve all memories, with optional filtering.
Args: Args:
options: Typed options for the get_all operation (GetAllMemoryOptions).
**kwargs: Optional parameters for filtering (user_id, agent_id, **kwargs: Optional parameters for filtering (user_id, agent_id,
app_id, top_k, page, page_size). app_id, top_k, page, page_size).
@@ -218,8 +218,8 @@ class MemoryClient:
NetworkError: If network connectivity issues occur. NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). 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 = self._prepare_params(kwargs)
params.pop("async_mode", None)
if "page" in params and "page_size" in params: if "page" in params and "page_size" in params:
query_params = { query_params = {
@@ -236,7 +236,6 @@ class MemoryClient:
"client.get_all", "client.get_all",
self, self,
{ {
"api_version": "v2",
"keys": list(kwargs.keys()), "keys": list(kwargs.keys()),
"sync_type": "sync", "sync_type": "sync",
}, },
@@ -249,13 +248,13 @@ class MemoryClient:
return result return result
@api_error_handler @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. """Search memories based on a query.
Args: Args:
query: The search query string. query: The search query string.
**kwargs: Additional parameters such as user_id, agent_id, app_id, options: Typed options for the search operation (SearchMemoryOptions).
top_k, filters. **kwargs: Additional parameters such as filters, top_k, rerank.
Returns: Returns:
A dictionary containing search results in v1.1 format: {"results": [...]} A dictionary containing search results in v1.1 format: {"results": [...]}
@@ -268,11 +267,9 @@ class MemoryClient:
NetworkError: If network connectivity issues occur. NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). 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 = self._prepare_params(kwargs)
params.pop("async_mode", None) payload = {"query": query, **params}
payload.update(params)
response = self.client.post("/v2/memories/search/", json=payload) response = self.client.post("/v2/memories/search/", json=payload)
response.raise_for_status() response.raise_for_status()
@@ -282,7 +279,6 @@ class MemoryClient:
"client.search", "client.search",
self, self,
{ {
"api_version": "v2",
"keys": list(kwargs.keys()), "keys": list(kwargs.keys()),
"sync_type": "sync", "sync_type": "sync",
}, },
@@ -298,36 +294,32 @@ class MemoryClient:
def update( def update(
self, self,
memory_id: str, memory_id: str,
text: Optional[str] = None, options: Optional[UpdateMemoryOptions] = None,
metadata: Optional[Dict[str, Any]] = None, **kwargs,
timestamp: Optional[Union[int, float, str]] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """Update a memory by ID.
Update a memory by ID.
Args: Args:
memory_id (str): Memory ID. memory_id: The ID of the memory to update.
text (str, optional): New content to update the memory with. options: Typed options (UpdateMemoryOptions) with text, metadata,
metadata (dict, optional): Metadata to update in the memory. and/or timestamp fields.
timestamp (int, float, or str, optional): Unix epoch timestamp or ISO 8601 string. **kwargs: Alternatively pass text, metadata, timestamp as keyword args.
Returns: Returns:
Dict[str, Any]: The response from the server. Dict[str, Any]: The response from the server.
Example: Raises:
>>> client.update(memory_id="mem_123", text="Likes to play tennis on weekends") ValueError: If none of text, metadata, or timestamp are provided.
>>> 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.")
payload = {} Example:
if text is not None: >>> client.update("mem_123", UpdateMemoryOptions(text="Updated text"))
payload["text"] = text >>> client.update("mem_123", text="Updated text")
if metadata is not None: """
payload["metadata"] = metadata payload = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
if timestamp is not None: payload = {k: v for k, v in payload.items() if v is not None}
payload["timestamp"] = timestamp
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"}) capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "sync"})
params = self._prepare_params() params = self._prepare_params()
@@ -360,10 +352,11 @@ class MemoryClient:
return response.json() return response.json()
@api_error_handler @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. """Delete all memories, with optional filtering.
Args: Args:
options: Typed options for the delete_all operation (DeleteAllMemoryOptions).
**kwargs: Optional parameters for filtering (user_id, agent_id, **kwargs: Optional parameters for filtering (user_id, agent_id,
app_id). app_id).
@@ -378,6 +371,7 @@ class MemoryClient:
NetworkError: If network connectivity issues occur. NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). 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 = self._prepare_params(kwargs)
response = self.client.delete("/v1/memories/", params=params) response = self.client.delete("/v1/memories/", params=params)
response.raise_for_status() response.raise_for_status()
@@ -667,13 +661,11 @@ class MemoryClient:
@api_error_handler @api_error_handler
def update_project( def update_project(
self, self,
options: Optional[ProjectUpdateOptions] = None,
custom_instructions: Optional[str] = None, custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None, custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None, retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
enable_graph: Optional[bool] = 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, memory_depth: Optional[str] = None,
usecase_setting: Optional[str] = None, usecase_setting: Optional[str] = None,
multilingual: Optional[bool] = None, multilingual: Optional[bool] = None,
@@ -681,67 +673,53 @@ class MemoryClient:
"""Update the project settings. """Update the project settings.
Args: Args:
custom_instructions: New instructions for the project options: Typed options for the update operation (ProjectUpdateOptions).
custom_categories: New categories for the project custom_instructions: New instructions for the project.
retrieval_criteria: New retrieval criteria for the project custom_categories: New categories for the project.
enable_graph: Enable or disable the graph for the project retrieval_criteria: New retrieval criteria for the project.
version: Version of the project enable_graph: Enable or disable the graph for the project.
inclusion_prompt: Inclusion prompt for the project memory_depth: Memory depth for the project.
exclusion_prompt: Exclusion prompt for the project usecase_setting: Usecase setting for the project.
memory_depth: Memory depth for the project multilingual: Whether to use the input language for memory storage and retrieval.
usecase_setting: Usecase setting for the project
multilingual: Whether to use the input language for memory storage and retrieval
Returns: Returns:
Dictionary containing the API response. Dictionary containing the API response.
Raises: Raises:
ValidationError: If the input data is invalid. ValueError: If org_id or project_id are not set, or no update fields provided.
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.
""" """
logger.warning( 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): if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories") raise ValueError("org_id and project_id must be set to update instructions or categories")
if ( kwargs = {
custom_instructions is None **(options.model_dump(exclude_unset=True) if options else {}),
and custom_categories is None **{
and retrieval_criteria is None k: v
and enable_graph is None for k, v in {
and version is None "custom_instructions": custom_instructions,
and inclusion_prompt is None "custom_categories": custom_categories,
and exclusion_prompt is None "retrieval_criteria": retrieval_criteria,
and memory_depth is None "enable_graph": enable_graph,
and usecase_setting is None "memory_depth": memory_depth,
and multilingual is None "usecase_setting": usecase_setting,
): "multilingual": multilingual,
}.items()
if v is not None
},
}
if not kwargs:
raise ValueError( raise ValueError(
"Currently we only support updating custom_instructions or " "Currently we only support updating custom_instructions or "
"custom_categories or retrieval_criteria, so you must " "custom_categories or retrieval_criteria, so you must "
"provide at least one of them" "provide at least one of them"
) )
payload = self._prepare_params( payload = self._prepare_params(kwargs)
{
"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,
}
)
response = self.client.patch( response = self.client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/", f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
json=payload, json=payload,
@@ -750,19 +728,7 @@ class MemoryClient:
capture_client_event( capture_client_event(
"client.update_project", "client.update_project",
self, self,
{ {**kwargs, "sync_type": "sync"},
"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",
},
) )
return response.json() return response.json()
@@ -937,20 +903,12 @@ class MemoryClient:
Returns: Returns:
A dictionary containing the prepared parameters. A dictionary containing the prepared parameters.
Raises:
ValueError: If either org_id or project_id is provided but not both.
""" """
if kwargs is None: if kwargs is None:
kwargs = {} kwargs = {}
# Add org_id and project_id if both are available # org_id and project_id are resolved from API key — not injected into params
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")
return {k: v for k, v in kwargs.items() if v is not None} return {k: v for k, v in kwargs.items() if v is not None}
@@ -966,8 +924,6 @@ class AsyncMemoryClient:
self, self,
api_key: Optional[str] = None, api_key: Optional[str] = None,
host: Optional[str] = None, host: Optional[str] = None,
org_id: Optional[str] = None,
project_id: Optional[str] = None,
client: Optional[httpx.AsyncClient] = None, client: Optional[httpx.AsyncClient] = None,
): ):
"""Initialize the AsyncMemoryClient. """Initialize the AsyncMemoryClient.
@@ -978,8 +934,6 @@ class AsyncMemoryClient:
environment variable. environment variable.
host: The base URL for the Mem0 API. Defaults to host: The base URL for the Mem0 API. Defaults to
"https://api.mem0.ai". "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 client: A custom httpx.AsyncClient instance. If provided, it will
be used instead of creating a new one. Note that base_url be used instead of creating a new one. Note that base_url
and headers will be set/overridden as needed. and headers will be set/overridden as needed.
@@ -989,8 +943,8 @@ class AsyncMemoryClient:
""" """
self.api_key = api_key or os.getenv("MEM0_API_KEY") self.api_key = api_key or os.getenv("MEM0_API_KEY")
self.host = host or "https://api.mem0.ai" self.host = host or "https://api.mem0.ai"
self.org_id = org_id self.org_id = None
self.project_id = project_id self.project_id = None
self.user_id = get_user_id() self.user_id = get_user_id()
if not self.api_key: if not self.api_key:
@@ -1085,20 +1039,12 @@ class AsyncMemoryClient:
Returns: Returns:
A dictionary containing the prepared parameters. A dictionary containing the prepared parameters.
Raises:
ValueError: If either org_id or project_id is provided but not both.
""" """
if kwargs is None: if kwargs is None:
kwargs = {} kwargs = {}
# Add org_id and project_id if both are available # org_id and project_id are resolved from API key — not injected into params
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")
return {k: v for k, v in kwargs.items() if v is not None} return {k: v for k, v in kwargs.items() if v is not None}
@@ -1109,25 +1055,41 @@ class AsyncMemoryClient:
await self.async_client.aclose() await self.async_client.aclose()
@api_error_handler @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) # Handle different message input formats (align with OSS behavior)
if isinstance(messages, str): if isinstance(messages, str):
messages = [{"role": "user", "content": messages}] messages = [{"role": "user", "content": messages}]
elif isinstance(messages, dict): elif isinstance(messages, dict):
messages = [messages] messages = [messages]
elif not isinstance(messages, list): elif not isinstance(messages, list):
raise ValueError( raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
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
# Force v1.1 format for all add operations # Force v1.1 format for all add operations
kwargs["output_format"] = "v1.1" kwargs["output_format"] = "v1.1"
kwargs = self._prepare_params(kwargs)
payload = self._prepare_payload(messages, kwargs) payload = self._prepare_payload(messages, kwargs)
response = await self.async_client.post("/v1/memories/", json=payload) response = await self.async_client.post("/v1/memories/", json=payload)
response.raise_for_status() response.raise_for_status()
@@ -1145,9 +1107,26 @@ class AsyncMemoryClient:
return response.json() return response.json()
@api_error_handler @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 = self._prepare_params(kwargs)
params.pop("async_mode", None)
if "page" in params and "page_size" in params: if "page" in params and "page_size" in params:
query_params = { query_params = {
@@ -1164,7 +1143,6 @@ class AsyncMemoryClient:
"client.get_all", "client.get_all",
self, self,
{ {
"api_version": "v2",
"keys": list(kwargs.keys()), "keys": list(kwargs.keys()),
"sync_type": "async", "sync_type": "async",
}, },
@@ -1177,12 +1155,28 @@ class AsyncMemoryClient:
return result return result
@api_error_handler @api_error_handler
async def search(self, query: str, **kwargs) -> Dict[str, Any]: async def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
payload = {"query": query} """Search memories based on a query.
params = self._prepare_params(kwargs)
params.pop("async_mode", None)
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 = await self.async_client.post("/v2/memories/search/", json=payload)
response.raise_for_status() response.raise_for_status()
@@ -1192,7 +1186,6 @@ class AsyncMemoryClient:
"client.search", "client.search",
self, self,
{ {
"api_version": "v2",
"keys": list(kwargs.keys()), "keys": list(kwargs.keys()),
"sync_type": "async", "sync_type": "async",
}, },
@@ -1208,36 +1201,32 @@ class AsyncMemoryClient:
async def update( async def update(
self, self,
memory_id: str, memory_id: str,
text: Optional[str] = None, options: Optional[UpdateMemoryOptions] = None,
metadata: Optional[Dict[str, Any]] = None, **kwargs,
timestamp: Optional[Union[int, float, str]] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """Update a memory by ID asynchronously.
Update a memory by ID asynchronously.
Args: Args:
memory_id (str): Memory ID. memory_id: The ID of the memory to update.
text (str, optional): New content to update the memory with. options: Typed options (UpdateMemoryOptions) with text, metadata,
metadata (dict, optional): Metadata to update in the memory. and/or timestamp fields.
timestamp (int, float, or str, optional): Unix epoch timestamp or ISO 8601 string. **kwargs: Alternatively pass text, metadata, timestamp as keyword args.
Returns: Returns:
Dict[str, Any]: The response from the server. Dict[str, Any]: The response from the server.
Example: Raises:
>>> await client.update(memory_id="mem_123", text="Likes to play tennis on weekends") ValueError: If none of text, metadata, or timestamp are provided.
>>> 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.")
payload = {} Example:
if text is not None: >>> await client.update("mem_123", UpdateMemoryOptions(text="Updated text"))
payload["text"] = text >>> await client.update("mem_123", text="Updated text")
if metadata is not None: """
payload["metadata"] = metadata payload = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
if timestamp is not None: payload = {k: v for k, v in payload.items() if v is not None}
payload["timestamp"] = timestamp
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"}) capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "async"})
params = self._prepare_params() params = self._prepare_params()
@@ -1270,10 +1259,11 @@ class AsyncMemoryClient:
return response.json() return response.json()
@api_error_handler @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. """Delete all memories, with optional filtering.
Args: Args:
options: Typed options for the delete_all operation (DeleteAllMemoryOptions).
**kwargs: Optional parameters for filtering (user_id, agent_id, app_id). **kwargs: Optional parameters for filtering (user_id, agent_id, app_id).
Returns: Returns:
@@ -1287,6 +1277,7 @@ class AsyncMemoryClient:
NetworkError: If network connectivity issues occur. NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). 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 = self._prepare_params(kwargs)
response = await self.async_client.delete("/v1/memories/", params=params) response = await self.async_client.delete("/v1/memories/", params=params)
response.raise_for_status() response.raise_for_status()
@@ -1554,63 +1545,65 @@ class AsyncMemoryClient:
@api_error_handler @api_error_handler
async def update_project( async def update_project(
self, self,
options: Optional[ProjectUpdateOptions] = None,
custom_instructions: Optional[str] = None, custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None, custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None, retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
enable_graph: Optional[bool] = None, enable_graph: Optional[bool] = None,
version: Optional[str] = None, memory_depth: Optional[str] = None,
usecase_setting: Optional[str] = None,
multilingual: Optional[bool] = None, multilingual: Optional[bool] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Update the project settings. """Update the project settings.
Args: Args:
custom_instructions: New instructions for the project options: Typed options for the update operation (ProjectUpdateOptions).
custom_categories: New categories for the project custom_instructions: New instructions for the project.
retrieval_criteria: New retrieval criteria for the project custom_categories: New categories for the project.
enable_graph: Enable or disable the graph for the project retrieval_criteria: New retrieval criteria for the project.
version: Version of the project enable_graph: Enable or disable the graph for the project.
multilingual: Whether to use the input language for memory storage and retrieval 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: Returns:
Dictionary containing the API response. Dictionary containing the API response.
Raises: Raises:
ValidationError: If the input data is invalid. ValueError: If org_id or project_id are not set, or no update fields provided.
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.
""" """
logger.warning( 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): if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories") raise ValueError("org_id and project_id must be set to update instructions or categories")
if ( kwargs = {
custom_instructions is None **(options.model_dump(exclude_unset=True) if options else {}),
and custom_categories is None **{
and retrieval_criteria is None k: v
and enable_graph is None for k, v in {
and version is None "custom_instructions": custom_instructions,
and multilingual is None "custom_categories": custom_categories,
): "retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"memory_depth": memory_depth,
"usecase_setting": usecase_setting,
"multilingual": multilingual,
}.items()
if v is not None
},
}
if not kwargs:
raise ValueError( 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( payload = self._prepare_params(kwargs)
{
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"version": version,
"multilingual": multilingual,
}
)
response = await self.async_client.patch( response = await self.async_client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/", f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
json=payload, json=payload,
@@ -1619,15 +1612,7 @@ class AsyncMemoryClient:
capture_client_event( capture_client_event(
"client.update_project", "client.update_project",
self, self,
{ {**kwargs, "sync_type": "async"},
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"version": version,
"multilingual": multilingual,
"sync_type": "async",
},
) )
return response.json() return response.json()
+103
View File
@@ -0,0 +1,103 @@
"""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"
)
enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory for this operation")
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")
enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory for this search")
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")
enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory for retrieval")
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"
)
enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory")
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 -3
View File
@@ -3,11 +3,11 @@ from typing import Any, Dict, Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from mem0.configs.rerankers.config import RerankerConfig
from mem0.embeddings.configs import EmbedderConfig from mem0.embeddings.configs import EmbedderConfig
from mem0.graphs.configs import GraphStoreConfig from mem0.graphs.configs import GraphStoreConfig
from mem0.llms.configs import LlmConfig from mem0.llms.configs import LlmConfig
from mem0.vector_stores.configs import VectorStoreConfig from mem0.vector_stores.configs import VectorStoreConfig
from mem0.configs.rerankers.config import RerankerConfig
# Set up the directory path # Set up the directory path
home_dir = os.path.expanduser("~") home_dir = os.path.expanduser("~")
@@ -56,8 +56,8 @@ class MemoryConfig(BaseModel):
description="The version of the API", description="The version of the API",
default="v1.1", default="v1.1",
) )
custom_fact_extraction_prompt: Optional[str] = Field( custom_instructions: Optional[str] = Field(
description="Custom prompt for the fact extraction", description="Custom instructions for fact extraction",
default=None, default=None,
) )
custom_update_memory_prompt: Optional[str] = Field( custom_update_memory_prompt: Optional[str] = Field(
+1 -1
View File
@@ -17,7 +17,7 @@ class ElasticsearchConfig(BaseModel):
use_ssl: bool = Field(True, description="Use SSL for connection") use_ssl: bool = Field(True, description="Use SSL for connection")
auto_create_index: bool = Field(True, description="Automatically create index during initialization") auto_create_index: bool = Field(True, description="Automatically create index during initialization")
custom_search_query: Optional[Callable[[List[float], int, Optional[Dict]], Dict]] = Field( 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") headers: Optional[Dict[str, str]] = Field(None, description="Custom headers to include in requests")
+9 -9
View File
@@ -346,14 +346,14 @@ class NeptuneBase(ABC):
): ):
pass pass
def search(self, query, filters, limit=100): def search(self, query, filters, top_k=100):
""" """
Search for memories and related graph data. Search for memories and related graph data.
Args: Args:
query (str): Query to search for. query (str): Query to search for.
filters (dict): A dictionary containing filters to be applied during the search. 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: Returns:
dict: A dictionary containing: dict: A dictionary containing:
@@ -438,13 +438,13 @@ class NeptuneBase(ABC):
""" """
pass 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. Retrieves all nodes and relationships from the graph database based on filtering criteria.
Args: Args:
filters (dict): A dictionary containing filters to be applied during the retrieval. 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: Returns:
list: A list of dictionaries, each containing: list: A list of dictionaries, each containing:
- 'contexts': The base data store response for each memory. - 'contexts': The base data store response for each memory.
@@ -452,7 +452,7 @@ class NeptuneBase(ABC):
""" """
# return all nodes and relationships # 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) results = self.graph.query(query, params=params)
final_results = [] final_results = []
@@ -470,13 +470,13 @@ class NeptuneBase(ABC):
return final_results return final_results
@abstractmethod @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 Returns the OpenCypher query and parameters to get all edges/nodes in the memory store
""" """
pass 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. Search similar nodes among and their respective incoming and outgoing relations.
""" """
@@ -484,14 +484,14 @@ class NeptuneBase(ABC):
for node in node_list: for node in node_list:
n_embedding = self.embedding_model.embed(node) 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) ans = self.graph.query(cypher_query, params=params)
result_relations.extend(ans) result_relations.extend(ans)
return result_relations return result_relations
@abstractmethod @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 Returns the OpenCypher query and parameters to search for similar nodes in the memory store
""" """
+9 -9
View File
@@ -380,7 +380,7 @@ class MemoryGraph(NeptuneBase):
source_nodes = self.vector_store.search( source_nodes = self.vector_store.search(
query="", query="",
vectors=source_embedding, vectors=source_embedding,
limit=self.vector_store_limit, top_k=self.vector_store_limit,
filters={"user_id": user_id}, filters={"user_id": user_id},
) )
@@ -413,7 +413,7 @@ class MemoryGraph(NeptuneBase):
destination_nodes = self.vector_store.search( destination_nodes = self.vector_store.search(
query="", query="",
vectors=destination_embedding, vectors=destination_embedding,
limit=self.vector_store_limit, top_k=self.vector_store_limit,
filters={"user_id": user_id}, filters={"user_id": user_id},
) )
@@ -455,12 +455,12 @@ class MemoryGraph(NeptuneBase):
logger.debug(f"delete_all query={cypher}") logger.debug(f"delete_all query={cypher}")
return cypher, params 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 Returns the OpenCypher query and parameters to get all edges/nodes in the memory store
:param filters: search filters :param filters: search filters
:param limit: return limit :param top_k: return limit
:return: str, dict :return: str, dict
""" """
@@ -469,16 +469,16 @@ class MemoryGraph(NeptuneBase):
RETURN n.name AS source, type(r) AS relationship, m.name AS target RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit LIMIT $limit
""" """
params = {"user_id": filters["user_id"], "limit": limit} params = {"user_id": filters["user_id"], "limit": top_k}
return cypher, params 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 Returns the OpenCypher query and parameters to search for similar nodes in the memory store
:param n_embedding: node vector :param n_embedding: node vector
:param filters: search filters :param filters: search filters
:param limit: return limit :param top_k: return limit
:return: str, dict :return: str, dict
""" """
@@ -486,7 +486,7 @@ class MemoryGraph(NeptuneBase):
search_nodes = self.vector_store.search( search_nodes = self.vector_store.search(
query="", query="",
vectors=n_embedding, vectors=n_embedding,
limit=self.vector_store_limit, top_k=self.vector_store_limit,
filters=filters, filters=filters,
) )
@@ -504,7 +504,7 @@ class MemoryGraph(NeptuneBase):
params = { params = {
"n_ids": ids, "n_ids": ids,
"user_id": filters["user_id"], "user_id": filters["user_id"],
"limit": limit, "limit": top_k,
} }
logger.debug(f"_search_graph_db\n query={cypher_query}") logger.debug(f"_search_graph_db\n query={cypher_query}")
+7 -7
View File
@@ -3,8 +3,8 @@ import logging
from .base import NeptuneBase from .base import NeptuneBase
try: try:
from langchain_aws import NeptuneAnalyticsGraph
from botocore.config import Config from botocore.config import Config
from langchain_aws import NeptuneAnalyticsGraph
except ImportError: except ImportError:
raise ImportError("langchain_aws is not installed. Please install it using 'make install_all'.") 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}") logger.debug(f"delete_all query={cypher}")
return cypher, params 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 Returns the OpenCypher query and parameters to get all edges/nodes in the memory store
:param filters: search filters :param filters: search filters
:param limit: return limit :param top_k: return limit
:return: str, dict :return: str, dict
""" """
@@ -426,16 +426,16 @@ class MemoryGraph(NeptuneBase):
RETURN n.name AS source, type(r) AS relationship, m.name AS target RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit LIMIT $limit
""" """
params = {"user_id": filters["user_id"], "limit": limit} params = {"user_id": filters["user_id"], "limit": top_k}
return cypher, params 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 Returns the OpenCypher query and parameters to search for similar nodes in the memory store
:param n_embedding: node vector :param n_embedding: node vector
:param filters: search filters :param filters: search filters
:param limit: return limit :param top_k: return limit
:return: str, dict :return: str, dict
""" """
@@ -468,7 +468,7 @@ class MemoryGraph(NeptuneBase):
"n_embedding": n_embedding, "n_embedding": n_embedding,
"threshold": self.threshold, "threshold": self.threshold,
"user_id": filters["user_id"], "user_id": filters["user_id"],
"limit": limit, "limit": top_k,
} }
logger.debug(f"_search_graph_db\n query={cypher_query}") logger.debug(f"_search_graph_db\n query={cypher_query}")
+7 -7
View File
@@ -213,14 +213,14 @@ class MemoryGraph:
return {"deleted_entities": deleted_entities, "added_entities": added_entities} 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. Search for memories and related graph data.
Args: Args:
query (str): Query to search for. query (str): Query to search for.
filters (dict): A dictionary containing filters to be applied during the search. 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: Returns:
list: A list of dicts with keys "source", "relationship", "destination". list: A list of dicts with keys "source", "relationship", "destination".
@@ -286,13 +286,13 @@ class MemoryGraph:
) )
self.ag.commit() 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. Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
Args: Args:
filters (dict): A dictionary containing filters to be applied during the retrieval. 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: Returns:
list: A list of dictionaries, each containing: list: A list of dictionaries, each containing:
- 'source': The source node name. - 'source': The source node name.
@@ -308,7 +308,7 @@ class MemoryGraph:
where_parts.extend(["n.run_id = %s", "m.run_id = %s"]) where_parts.extend(["n.run_id = %s", "m.run_id = %s"])
params.extend([filters["run_id"], filters["run_id"]]) params.extend([filters["run_id"], filters["run_id"]])
where_clause = " AND ".join(where_parts) where_clause = " AND ".join(where_parts)
params.append(limit) params.append(top_k)
results = self._exec_cypher( results = self._exec_cypher(
f"MATCH (n)-[r]->(m) WHERE {where_clause} " f"MATCH (n)-[r]->(m) WHERE {where_clause} "
@@ -408,7 +408,7 @@ class MemoryGraph:
# -- graph DB operations --------------------------------------------------- # -- 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.""" """Search similar nodes and their respective incoming and outgoing relations."""
result_relations = [] result_relations = []
@@ -430,7 +430,7 @@ class MemoryGraph:
rel_where = " AND ".join(rel_where_parts) rel_where = " AND ".join(rel_where_parts)
# For each similar node, fetch its relationships # For each similar node, fetch its relationships
for sn in similar_nodes[:limit]: for sn in similar_nodes[:top_k]:
node_name = sn["name"] node_name = sn["name"]
similarity = sn["similarity"] similarity = sn["similarity"]
+7 -7
View File
@@ -93,14 +93,14 @@ class MemoryGraph:
return {"deleted_entities": deleted_entities, "added_entities": added_entities} 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. Search for memories and related graph data.
Args: Args:
query (str): Query to search for. query (str): Query to search for.
filters (dict): A dictionary containing filters to be applied during the search. 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: Returns:
dict: A dictionary containing: dict: A dictionary containing:
@@ -171,18 +171,18 @@ class MemoryGraph:
params["run_id"] = filters["run_id"] params["run_id"] = filters["run_id"]
self.graph.query(cypher, params=params) 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. Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
Args: Args:
filters (dict): A dictionary containing filters to be applied during the retrieval. 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: Returns:
list: A list of dictionaries, each containing: list: A list of dictionaries, each containing:
- 'contexts': The base data store response for each memory. - 'contexts': The base data store response for each memory.
- 'entities': A list of strings representing the nodes and relationships - '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 # Build node properties based on filters
node_props = ["user_id: $user_id"] node_props = ["user_id: $user_id"]
@@ -291,7 +291,7 @@ class MemoryGraph:
logger.debug(f"Extracted entities: {entities}") logger.debug(f"Extracted entities: {entities}")
return 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.""" """Search similar nodes among and their respective incoming and outgoing relations."""
result_relations = [] result_relations = []
@@ -332,7 +332,7 @@ class MemoryGraph:
"n_embedding": n_embedding, "n_embedding": n_embedding,
"threshold": self.threshold, "threshold": self.threshold,
"user_id": filters["user_id"], "user_id": filters["user_id"],
"limit": limit, "limit": top_k,
} }
if filters.get("agent_id"): if filters.get("agent_id"):
params["agent_id"] = filters["agent_id"] params["agent_id"] = filters["agent_id"]
+9 -9
View File
@@ -113,14 +113,14 @@ class MemoryGraph:
return {"deleted_entities": deleted_entities, "added_entities": added_entities} 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. Search for memories and related graph data.
Args: Args:
query (str): Query to search for. query (str): Query to search for.
filters (dict): A dictionary containing filters to be applied during the search. 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: Returns:
dict: A dictionary containing: dict: A dictionary containing:
@@ -139,7 +139,7 @@ class MemoryGraph:
bm25 = BM25Okapi(search_outputs_sequence) bm25 = BM25Okapi(search_outputs_sequence)
tokenized_query = query.split(" ") 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 = [] search_results = []
for item in reranked_results: for item in reranked_results:
@@ -191,12 +191,12 @@ class MemoryGraph:
params["run_id"] = filters["run_id"] params["run_id"] = filters["run_id"]
self.kuzu_execute(cypher, parameters=params) 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. Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
Args: Args:
filters (dict): A dictionary containing filters to be applied during the retrieval. 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: Returns:
list: A list of dictionaries, each containing: list: A list of dictionaries, each containing:
- 'contexts': The base data store response for each memory. - 'contexts': The base data store response for each memory.
@@ -205,7 +205,7 @@ class MemoryGraph:
params = { params = {
"user_id": filters["user_id"], "user_id": filters["user_id"],
"limit": limit, "limit": top_k,
} }
# Build node properties based on filters # Build node properties based on filters
node_props = ["user_id: $user_id"] node_props = ["user_id: $user_id"]
@@ -316,14 +316,14 @@ class MemoryGraph:
logger.debug(f"Extracted entities: {entities}") logger.debug(f"Extracted entities: {entities}")
return 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.""" """Search similar nodes among and their respective incoming and outgoing relations."""
result_relations = [] result_relations = []
params = { params = {
"threshold": threshold if threshold else self.threshold, "threshold": threshold if threshold else self.threshold,
"user_id": filters["user_id"], "user_id": filters["user_id"],
"limit": limit, "limit": top_k,
} }
# Build node properties for filtering # Build node properties for filtering
node_props = ["user_id: $user_id"] node_props = ["user_id: $user_id"]
@@ -364,7 +364,7 @@ class MemoryGraph:
parameters=params)) parameters=params))
# Kuzu does not support sort/limit over unions. Do it manually for now. # 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 return result_relations
+39 -39
View File
@@ -258,7 +258,7 @@ class Memory(MemoryBase):
def __init__(self, config: MemoryConfig = MemoryConfig()): def __init__(self, config: MemoryConfig = MemoryConfig()):
self.config = config 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.custom_update_memory_prompt = self.config.custom_update_memory_prompt
self.embedding_model = EmbedderFactory.create( self.embedding_model = EmbedderFactory.create(
self.config.embedder.provider, self.config.embedder.provider,
@@ -525,8 +525,8 @@ class Memory(MemoryBase):
parsed_messages = parse_messages(messages) parsed_messages = parse_messages(messages)
if self.config.custom_fact_extraction_prompt: if self.config.custom_instructions:
system_prompt = self.config.custom_fact_extraction_prompt system_prompt = self.config.custom_instructions
user_prompt = f"Input:\n{parsed_messages}" user_prompt = f"Input:\n{parsed_messages}"
else: else:
# Determine if this should use agent memory extraction based on agent_id presence # Determine if this should use agent memory extraction based on agent_id presence
@@ -582,7 +582,7 @@ class Memory(MemoryBase):
existing_memories = self.vector_store.search( existing_memories = self.vector_store.search(
query=new_mem, query=new_mem,
vectors=messages_embeddings, vectors=messages_embeddings,
limit=5, top_k=5,
filters=search_filters, filters=search_filters,
) )
for mem in existing_memories: for mem in existing_memories:
@@ -785,7 +785,7 @@ class Memory(MemoryBase):
agent_id: Optional[str] = None, agent_id: Optional[str] = None,
run_id: Optional[str] = None, run_id: Optional[str] = None,
filters: Optional[Dict[str, Any]] = None, filters: Optional[Dict[str, Any]] = None,
limit: int = 100, top_k: int = 100,
): ):
""" """
List all memories. List all memories.
@@ -797,7 +797,7 @@ class Memory(MemoryBase):
filters (dict, optional): Additional custom key-value filters to apply to the search. filters (dict, optional): Additional custom key-value filters to apply to the search.
These are merged with the ID-based scoping filters. For example, These are merged with the ID-based scoping filters. For example,
`filters={"actor_id": "some_user"}`. `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: Returns:
dict: A dictionary containing a list of memories under the "results" key, dict: A dictionary containing a list of memories under the "results" key,
@@ -815,13 +815,13 @@ class Memory(MemoryBase):
keys, encoded_ids = process_telemetry_filters(effective_filters) keys, encoded_ids = process_telemetry_filters(effective_filters)
capture_event( 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: 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 = ( 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.enable_graph else None
) )
concurrent.futures.wait( concurrent.futures.wait(
@@ -836,8 +836,8 @@ class Memory(MemoryBase):
return {"results": all_memories_result} return {"results": all_memories_result}
def _get_all_from_vector_store(self, filters, limit): def _get_all_from_vector_store(self, filters, top_k):
memories_result = self.vector_store.list(filters=filters, limit=limit) memories_result = self.vector_store.list(filters=filters, top_k=top_k)
# Handle different vector store return formats by inspecting first element # Handle different vector store return formats by inspecting first element
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0: if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0:
@@ -890,7 +890,7 @@ class Memory(MemoryBase):
user_id: Optional[str] = None, user_id: Optional[str] = None,
agent_id: Optional[str] = None, agent_id: Optional[str] = None,
run_id: Optional[str] = None, run_id: Optional[str] = None,
limit: int = 100, top_k: int = 100,
filters: Optional[Dict[str, Any]] = None, filters: Optional[Dict[str, Any]] = None,
threshold: Optional[float] = None, threshold: Optional[float] = None,
rerank: bool = True, rerank: bool = True,
@@ -902,7 +902,7 @@ class Memory(MemoryBase):
user_id (str, optional): ID of the user to search for. Defaults to None. 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. 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. 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. 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. 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: filters (dict, optional): Enhanced metadata filtering with operators:
@@ -947,7 +947,7 @@ class Memory(MemoryBase):
"mem0.search", "mem0.search",
self, self,
{ {
"limit": limit, "top_k": top_k,
"version": self.api_version, "version": self.api_version,
"keys": keys, "keys": keys,
"encoded_ids": encoded_ids, "encoded_ids": encoded_ids,
@@ -958,9 +958,9 @@ class Memory(MemoryBase):
) )
with concurrent.futures.ThreadPoolExecutor() as executor: 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 = ( 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.enable_graph else None
) )
concurrent.futures.wait( concurrent.futures.wait(
@@ -973,7 +973,7 @@ class Memory(MemoryBase):
# Apply reranking if enabled and reranker is available # Apply reranking if enabled and reranker is available
if rerank and self.reranker and original_memories: if rerank and self.reranker and original_memories:
try: try:
reranked_memories = self.reranker.rerank(query, original_memories, limit) reranked_memories = self.reranker.rerank(query, original_memories, top_k)
original_memories = reranked_memories original_memories = reranked_memories
except Exception as e: except Exception as e:
logger.warning(f"Reranking failed, using original results: {e}") logger.warning(f"Reranking failed, using original results: {e}")
@@ -1079,9 +1079,9 @@ class Memory(MemoryBase):
return True return True
return False 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") 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 = [ promoted_payload_keys = [
"user_id", "user_id",
@@ -1643,8 +1643,8 @@ class AsyncMemory(MemoryBase):
return returned_memories return returned_memories
parsed_messages = parse_messages(messages) parsed_messages = parse_messages(messages)
if self.config.custom_fact_extraction_prompt: if self.config.custom_instructions:
system_prompt = self.config.custom_fact_extraction_prompt system_prompt = self.config.custom_instructions
user_prompt = f"Input:\n{parsed_messages}" user_prompt = f"Input:\n{parsed_messages}"
else: else:
# Determine if this should use agent memory extraction based on agent_id presence # Determine if this should use agent memory extraction based on agent_id presence
@@ -1699,7 +1699,7 @@ class AsyncMemory(MemoryBase):
self.vector_store.search, self.vector_store.search,
query=new_mem_content, query=new_mem_content,
vectors=embeddings, vectors=embeddings,
limit=5, top_k=5,
filters=search_filters, filters=search_filters,
) )
return [{"id": mem.id, "text": mem.payload.get("data", "")} for mem in existing_mems] return [{"id": mem.id, "text": mem.payload.get("data", "")} for mem in existing_mems]
@@ -1922,7 +1922,7 @@ class AsyncMemory(MemoryBase):
agent_id: Optional[str] = None, agent_id: Optional[str] = None,
run_id: Optional[str] = None, run_id: Optional[str] = None,
filters: Optional[Dict[str, Any]] = None, filters: Optional[Dict[str, Any]] = None,
limit: int = 100, top_k: int = 100,
): ):
""" """
List all memories. List all memories.
@@ -1934,7 +1934,7 @@ class AsyncMemory(MemoryBase):
filters (dict, optional): Additional custom key-value filters to apply to the search. filters (dict, optional): Additional custom key-value filters to apply to the search.
These are merged with the ID-based scoping filters. For example, These are merged with the ID-based scoping filters. For example,
`filters={"actor_id": "some_user"}`. `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: Returns:
dict: A dictionary containing a list of memories under the "results" key, dict: A dictionary containing a list of memories under the "results" key,
@@ -1955,19 +1955,19 @@ class AsyncMemory(MemoryBase):
keys, encoded_ids = process_telemetry_filters(effective_filters) keys, encoded_ids = process_telemetry_filters(effective_filters)
capture_event( 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 graph_task = None
if self.enable_graph: if self.enable_graph:
graph_get_all = getattr(self.graph, "get_all", None) graph_get_all = getattr(self.graph, "get_all", None)
if callable(graph_get_all): if callable(graph_get_all):
if asyncio.iscoroutinefunction(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: 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 = {} results_dict = {}
if graph_task: if graph_task:
@@ -1978,8 +1978,8 @@ class AsyncMemory(MemoryBase):
return results_dict return results_dict
async def _get_all_from_vector_store(self, filters, limit): async def _get_all_from_vector_store(self, filters, top_k):
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit) 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 # Handle different vector store return formats by inspecting first element
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0: if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0:
@@ -2032,7 +2032,7 @@ class AsyncMemory(MemoryBase):
user_id: Optional[str] = None, user_id: Optional[str] = None,
agent_id: Optional[str] = None, agent_id: Optional[str] = None,
run_id: Optional[str] = None, run_id: Optional[str] = None,
limit: int = 100, top_k: int = 100,
filters: Optional[Dict[str, Any]] = None, filters: Optional[Dict[str, Any]] = None,
threshold: Optional[float] = None, threshold: Optional[float] = None,
metadata_filters: Optional[Dict[str, Any]] = None, metadata_filters: Optional[Dict[str, Any]] = None,
@@ -2045,7 +2045,7 @@ class AsyncMemory(MemoryBase):
user_id (str, optional): ID of the user to search for. Defaults to None. 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. 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. 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. 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. 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: filters (dict, optional): Enhanced metadata filtering with operators:
@@ -2091,7 +2091,7 @@ class AsyncMemory(MemoryBase):
"mem0.search", "mem0.search",
self, self,
{ {
"limit": limit, "top_k": top_k,
"version": self.api_version, "version": self.api_version,
"keys": keys, "keys": keys,
"encoded_ids": encoded_ids, "encoded_ids": encoded_ids,
@@ -2101,14 +2101,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 graph_task = None
if self.enable_graph: if self.enable_graph:
if hasattr(self.graph.search, "__await__"): # Check if graph search is async 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: 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: if graph_task:
original_memories, graph_entities = await asyncio.gather(vector_store_task, graph_task) original_memories, graph_entities = await asyncio.gather(vector_store_task, graph_task)
@@ -2121,7 +2121,7 @@ class AsyncMemory(MemoryBase):
try: try:
# Run reranking in thread pool to avoid blocking async loop # Run reranking in thread pool to avoid blocking async loop
reranked_memories = await asyncio.to_thread( 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 original_memories = reranked_memories
except Exception as e: except Exception as e:
@@ -2228,10 +2228,10 @@ class AsyncMemory(MemoryBase):
return True return True
return False 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") embeddings = await asyncio.to_thread(self.embedding_model.embed, query, "search")
memories = await asyncio.to_thread( 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 = [ promoted_payload_keys = [
+9 -9
View File
@@ -98,14 +98,14 @@ class MemoryGraph:
return {"deleted_entities": deleted_entities, "added_entities": added_entities} 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. Search for memories and related graph data.
Args: Args:
query (str): Query to search for. query (str): Query to search for.
filters (dict): A dictionary containing filters to be applied during the search. 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: Returns:
dict: A dictionary containing: dict: A dictionary containing:
@@ -172,14 +172,14 @@ class MemoryGraph:
params = {"user_id": filters["user_id"]} params = {"user_id": filters["user_id"]}
self.graph.query(cypher, params=params) 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. Retrieves all nodes and relationships from the graph database based on optional filtering criteria.
Args: Args:
filters (dict): A dictionary containing filters to be applied during the retrieval. filters (dict): A dictionary containing filters to be applied during the retrieval.
Supports 'user_id' (required) and 'agent_id' (optional). 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: Returns:
list: A list of dictionaries, each containing: list: A list of dictionaries, each containing:
- 'source': The source node name. - 'source': The source node name.
@@ -193,14 +193,14 @@ class MemoryGraph:
RETURN n.name AS source, type(r) AS relationship, m.name AS target RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit 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: else:
query = """ query = """
MATCH (n:Entity {user_id: $user_id})-[r]->(m:Entity {user_id: $user_id}) 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 RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit 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) results = self.graph.query(query, params=params)
@@ -293,7 +293,7 @@ class MemoryGraph:
logger.debug(f"Extracted entities: {entities}") logger.debug(f"Extracted entities: {entities}")
return 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.""" """Search similar nodes among and their respective incoming and outgoing relations."""
result_relations = [] result_relations = []
@@ -324,7 +324,7 @@ class MemoryGraph:
"threshold": self.threshold, "threshold": self.threshold,
"user_id": filters["user_id"], "user_id": filters["user_id"],
"agent_id": filters["agent_id"], "agent_id": filters["agent_id"],
"limit": limit, "limit": top_k,
} }
else: else:
cypher_query = """ cypher_query = """
@@ -348,7 +348,7 @@ class MemoryGraph:
"n_embedding": n_embedding, "n_embedding": n_embedding,
"threshold": self.threshold, "threshold": self.threshold,
"user_id": filters["user_id"], "user_id": filters["user_id"],
"limit": limit, "limit": top_k,
} }
ans = self.graph.query(cypher_query, params=params) ans = self.graph.query(cypher_query, params=params)
+1 -1
View File
@@ -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 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 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 400 error. This function appends a JSON format instruction to the system
prompt if 'json' is not already present in either prompt. prompt if 'json' is not already present in either prompt.
+4 -4
View File
@@ -59,7 +59,7 @@ class Completions:
run_id: Optional[str] = None, run_id: Optional[str] = None,
metadata: Optional[dict] = None, metadata: Optional[dict] = None,
filters: Optional[dict] = None, filters: Optional[dict] = None,
limit: Optional[int] = 10, top_k: Optional[int] = 10,
# LLM arguments # LLM arguments
timeout: Optional[Union[float, str, httpx.Timeout]] = None, timeout: Optional[Union[float, str, httpx.Timeout]] = None,
temperature: Optional[float] = None, temperature: Optional[float] = None,
@@ -103,7 +103,7 @@ class Completions:
prepared_messages = self._prepare_messages(messages) prepared_messages = self._prepare_messages(messages)
if prepared_messages[-1]["role"] == "user": if prepared_messages[-1]["role"] == "user":
self._async_add_to_memory(messages, user_id, agent_id, run_id, metadata, filters) 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") logger.debug(f"Retrieved {len(relevant_memories)} relevant memories")
prepared_messages[-1]["content"] = self._format_query_with_memories(messages, 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() 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 # 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:] message_input = [f"{message['role']}: {message['content']}" for message in messages][-6:]
# TODO: Make it better by summarizing the past conversation # TODO: Make it better by summarizing the past conversation
@@ -173,7 +173,7 @@ class Completions:
agent_id=agent_id, agent_id=agent_id,
run_id=run_id, run_id=run_id,
filters=filters, filters=filters,
limit=limit, top_k=top_k,
) )
def _format_query_with_memories(self, messages, relevant_memories): def _format_query_with_memories(self, messages, relevant_memories):
+8 -8
View File
@@ -205,14 +205,14 @@ class AzureAISearch(VectorStoreBase):
filter_expression = " and ".join(filter_conditions) filter_expression = " and ".join(filter_conditions)
return filter_expression return filter_expression
def search(self, query, vectors, limit=5, filters=None): def search(self, query, vectors, top_k=5, filters=None):
""" """
Search for similar vectors. Search for similar vectors.
Args: Args:
query (str): Query. query (str): Query.
vectors (List[float]): Query vector. vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None. filters (Dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -222,13 +222,13 @@ class AzureAISearch(VectorStoreBase):
if filters: if filters:
filter_expression = self._build_filter_expression(filters) filter_expression = self._build_filter_expression(filters)
vector_query = VectorizedQuery(vector=vectors, k_nearest_neighbors=limit, fields="vector") vector_query = VectorizedQuery(vector=vectors, k_nearest_neighbors=top_k, fields="vector")
if self.hybrid_search: if self.hybrid_search:
search_results = self.search_client.search( search_results = self.search_client.search(
search_text=query, search_text=query,
vector_queries=[vector_query], vector_queries=[vector_query],
filter=filter_expression, filter=filter_expression,
top=limit, top=top_k,
vector_filter_mode=self.vector_filter_mode, vector_filter_mode=self.vector_filter_mode,
search_fields=["payload"], search_fields=["payload"],
) )
@@ -236,7 +236,7 @@ class AzureAISearch(VectorStoreBase):
search_results = self.search_client.search( search_results = self.search_client.search(
vector_queries=[vector_query], vector_queries=[vector_query],
filter=filter_expression, filter=filter_expression,
top=limit, top=top_k,
vector_filter_mode=self.vector_filter_mode, vector_filter_mode=self.vector_filter_mode,
) )
@@ -327,13 +327,13 @@ class AzureAISearch(VectorStoreBase):
index = self.index_client.get_index(self.index_name) index = self.index_client.get_index(self.index_name)
return {"name": index.name, "fields": index.fields} return {"name": index.name, "fields": index.fields}
def list(self, filters=None, limit=100): def list(self, filters=None, top_k=100):
""" """
List all vectors in the index. List all vectors in the index.
Args: Args:
filters (dict, optional): Filters to apply to the list. filters (dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
@@ -342,7 +342,7 @@ class AzureAISearch(VectorStoreBase):
if filters: if filters:
filter_expression = self._build_filter_expression(filters) filter_expression = self._build_filter_expression(filters)
search_results = self.search_client.search(search_text="*", filter=filter_expression, top=limit) search_results = self.search_client.search(search_text="*", filter=filter_expression, top=top_k)
results = [] results = []
for result in search_results: for result in search_results:
payload = json.loads(extract_json(result["payload"])) payload = json.loads(extract_json(result["payload"]))
+8 -8
View File
@@ -7,8 +7,8 @@ from pydantic import BaseModel
try: try:
import pymysql import pymysql
from pymysql.cursors import DictCursor
from dbutils.pooled_db import PooledDB from dbutils.pooled_db import PooledDB
from pymysql.cursors import DictCursor
except ImportError: except ImportError:
raise ImportError( raise ImportError(
"Azure MySQL vector store requires PyMySQL and DBUtils. " "Azure MySQL vector store requires PyMySQL and DBUtils. "
@@ -243,7 +243,7 @@ class AzureMySQL(VectorStoreBase):
self, self,
query: str, query: str,
vectors: List[float], vectors: List[float],
limit: int = 5, top_k: int = 5,
filters: Optional[Dict] = None, filters: Optional[Dict] = None,
) -> List[OutputData]: ) -> List[OutputData]:
""" """
@@ -252,7 +252,7 @@ class AzureMySQL(VectorStoreBase):
Args: Args:
query (str): Query string (not used in vector search) query (str): Query string (not used in vector search)
vectors (List[float]): Query vector vectors (List[float]): Query vector
limit (int): Number of results to return top_k (int): Number of results to return
filters (Dict, optional): Filters to apply to the search filters (Dict, optional): Filters to apply to the search
Returns: Returns:
@@ -291,9 +291,9 @@ class AzureMySQL(VectorStoreBase):
distance = 1 - similarity distance = 1 - similarity
scored_results.append((row['id'], distance, row['payload'])) scored_results.append((row['id'], distance, row['payload']))
# Sort by distance and limit # Sort by distance and apply limit
scored_results.sort(key=lambda x: x[1]) scored_results.sort(key=lambda x: x[1])
scored_results = scored_results[:limit] scored_results = scored_results[:top_k]
return [ return [
OutputData(id=r[0], score=float(r[1]), payload=json.loads(r[2]) if isinstance(r[2], str) else r[2]) OutputData(id=r[0], score=float(r[1]), payload=json.loads(r[2]) if isinstance(r[2], str) else r[2])
@@ -406,14 +406,14 @@ class AzureMySQL(VectorStoreBase):
def list( def list(
self, self,
filters: Optional[Dict] = None, filters: Optional[Dict] = None,
limit: int = 100 top_k: int = 100
) -> List[List[OutputData]]: ) -> List[List[OutputData]]:
""" """
List all vectors in the collection. List all vectors in the collection.
Args: Args:
filters (Dict, optional): Filters to apply filters (Dict, optional): Filters to apply
limit (int): Number of vectors to return top_k (int): Number of vectors to return
Returns: Returns:
List[List[OutputData]]: List of vectors List[List[OutputData]]: List of vectors
@@ -436,7 +436,7 @@ class AzureMySQL(VectorStoreBase):
{filter_clause} {filter_clause}
LIMIT %s LIMIT %s
""", """,
(*filter_params, limit) (*filter_params, top_k)
) )
results = cur.fetchall() results = cur.fetchall()
+6 -6
View File
@@ -185,14 +185,14 @@ class BaiduDB(VectorStoreBase):
row = Row(id=idx, vector=vector, metadata=metadata) row = Row(id=idx, vector=vector, metadata=metadata)
self._table.upsert(rows=[row]) self._table.upsert(rows=[row])
def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None) -> list: def search(self, query: str, vectors: list, top_k: int = 5, filters: dict = None) -> list:
""" """
Search for similar vectors. Search for similar vectors.
Args: Args:
query (str): Query string. query (str): Query string.
vectors (List[float]): Query vector. vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None. filters (Dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -207,7 +207,7 @@ class BaiduDB(VectorStoreBase):
request = VectorTopkSearchRequest( request = VectorTopkSearchRequest(
vector_field="vector", vector_field="vector",
vector=FloatVector(vectors), vector=FloatVector(vectors),
limit=limit, limit=top_k,
filter=search_filter, filter=search_filter,
config=VectorSearchConfig(ef=200), config=VectorSearchConfig(ef=200),
) )
@@ -313,20 +313,20 @@ class BaiduDB(VectorStoreBase):
""" """
return self._table.stats() return self._table.stats()
def list(self, filters: dict = None, limit: int = 100) -> list: def list(self, filters: dict = None, top_k: int = 100) -> list:
""" """
List all vectors in the table. List all vectors in the table.
Args: Args:
filters (Dict, optional): Filters to apply to the list. filters (Dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
""" """
projections = ["id", "metadata"] projections = ["id", "metadata"]
list_filter = self._create_filter(filters) if filters else None list_filter = self._create_filter(filters) if filters else None
result = self._table.select(filter=list_filter, projections=projections, limit=limit) result = self._table.select(filter=list_filter, projections=projections, limit=top_k)
memories = [] memories = []
for row in result.rows: for row in result.rows:
+2 -2
View File
@@ -13,7 +13,7 @@ class VectorStoreBase(ABC):
pass pass
@abstractmethod @abstractmethod
def search(self, query, vectors, limit=5, filters=None): def search(self, query, vectors, top_k=5, filters=None):
"""Search for similar vectors.""" """Search for similar vectors."""
pass pass
@@ -48,7 +48,7 @@ class VectorStoreBase(ABC):
pass pass
@abstractmethod @abstractmethod
def list(self, filters=None, limit=None): def list(self, filters=None, top_k=None):
"""List all memories.""" """List all memories."""
pass pass
+8 -8
View File
@@ -7,8 +7,8 @@ import numpy as np
from pydantic import BaseModel from pydantic import BaseModel
try: try:
from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider from cassandra.auth import PlainTextAuthProvider
from cassandra.cluster import Cluster
except ImportError: except ImportError:
raise ImportError( raise ImportError(
"Apache Cassandra vector store requires cassandra-driver. " "Apache Cassandra vector store requires cassandra-driver. "
@@ -214,7 +214,7 @@ class CassandraDB(VectorStoreBase):
self, self,
query: str, query: str,
vectors: List[float], vectors: List[float],
limit: int = 5, top_k: int = 5,
filters: Optional[Dict] = None, filters: Optional[Dict] = None,
) -> List[OutputData]: ) -> List[OutputData]:
""" """
@@ -223,7 +223,7 @@ class CassandraDB(VectorStoreBase):
Args: Args:
query (str): Query string (not used in vector search) query (str): Query string (not used in vector search)
vectors (List[float]): Query vector vectors (List[float]): Query vector
limit (int): Number of results to return top_k (int): Number of results to return
filters (Dict, optional): Filters to apply to the search filters (Dict, optional): Filters to apply to the search
Returns: Returns:
@@ -263,9 +263,9 @@ class CassandraDB(VectorStoreBase):
scored_results.append((row.id, distance, row.payload)) scored_results.append((row.id, distance, row.payload))
# Sort by distance and limit # Sort by distance and apply limit
scored_results.sort(key=lambda x: x[1]) scored_results.sort(key=lambda x: x[1])
scored_results = scored_results[:limit] scored_results = scored_results[:top_k]
return [ return [
OutputData( OutputData(
@@ -427,14 +427,14 @@ class CassandraDB(VectorStoreBase):
def list( def list(
self, self,
filters: Optional[Dict] = None, filters: Optional[Dict] = None,
limit: int = 100 top_k: int = 100
) -> List[List[OutputData]]: ) -> List[List[OutputData]]:
""" """
List all vectors in the collection. List all vectors in the collection.
Args: Args:
filters (Dict, optional): Filters to apply filters (Dict, optional): Filters to apply
limit (int): Number of vectors to return top_k (int): Number of vectors to return
Returns: Returns:
List[List[OutputData]]: List of vectors List[List[OutputData]]: List of vectors
@@ -443,7 +443,7 @@ class CassandraDB(VectorStoreBase):
query = f""" query = f"""
SELECT id, vector, payload SELECT id, vector, payload
FROM {self.keyspace}.{self.collection_name} FROM {self.keyspace}.{self.collection_name}
LIMIT {limit} LIMIT {top_k}
""" """
rows = self.session.execute(query) rows = self.session.execute(query)
+6 -6
View File
@@ -141,7 +141,7 @@ class ChromaDB(VectorStoreBase):
self.collection.add(ids=ids, embeddings=vectors, metadatas=payloads) self.collection.add(ids=ids, embeddings=vectors, metadatas=payloads)
def search( def search(
self, query: str, vectors: List[list], limit: int = 5, filters: Optional[Dict] = None self, query: str, vectors: List[list], top_k: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
""" """
Search for similar vectors. Search for similar vectors.
@@ -149,14 +149,14 @@ class ChromaDB(VectorStoreBase):
Args: Args:
query (str): Query. query (str): Query.
vectors (List[list]): List of vectors to search. vectors (List[list]): List of vectors to search.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None. filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
List[OutputData]: Search results. List[OutputData]: Search results.
""" """
where_clause = self._generate_where_clause(filters) if filters else None where_clause = self._generate_where_clause(filters) if filters else None
results = self.collection.query(query_embeddings=vectors, where=where_clause, n_results=limit) results = self.collection.query(query_embeddings=vectors, where=where_clause, n_results=top_k)
final_results = self._parse_output(results) final_results = self._parse_output(results)
return final_results return final_results
@@ -222,19 +222,19 @@ class ChromaDB(VectorStoreBase):
""" """
return self.client.get_collection(name=self.collection_name) return self.client.get_collection(name=self.collection_name)
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, top_k: int = 100) -> List[OutputData]:
""" """
List all vectors in a collection. List all vectors in a collection.
Args: Args:
filters (Optional[Dict], optional): Filters to apply to the list. Defaults to None. filters (Optional[Dict], optional): Filters to apply to the list. Defaults to None.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
""" """
where_clause = self._generate_where_clause(filters) if filters else None where_clause = self._generate_where_clause(filters) if filters else None
results = self.collection.get(where=where_clause, limit=limit) results = self.collection.get(where=where_clause, limit=top_k)
return [self._parse_output(results)] return [self._parse_output(results)]
def reset(self): def reset(self):
+19 -11
View File
@@ -2,20 +2,28 @@ import json
import logging import logging
import re import re
import uuid import uuid
from typing import Optional, List from datetime import date, datetime
from datetime import datetime, date from typing import List, Optional
from databricks.sdk.service.catalog import ColumnInfo, ColumnTypeName, TableType, DataSourceFormat
from databricks.sdk.service.catalog import TableConstraint, PrimaryKeyConstraint
from databricks.sdk import WorkspaceClient from databricks.sdk import WorkspaceClient
from databricks.sdk.service.catalog import (
ColumnInfo,
ColumnTypeName,
DataSourceFormat,
PrimaryKeyConstraint,
TableConstraint,
TableType,
)
from databricks.sdk.service.sql import StatementParameterListItem from databricks.sdk.service.sql import StatementParameterListItem
from databricks.sdk.service.vectorsearch import ( from databricks.sdk.service.vectorsearch import (
VectorIndexType,
DeltaSyncVectorIndexSpecRequest, DeltaSyncVectorIndexSpecRequest,
DirectAccessVectorIndexSpec, DirectAccessVectorIndexSpec,
EmbeddingSourceColumn, EmbeddingSourceColumn,
EmbeddingVectorColumn, EmbeddingVectorColumn,
VectorIndexType,
) )
from pydantic import BaseModel from pydantic import BaseModel
from mem0.memory.utils import extract_json from mem0.memory.utils import extract_json
from mem0.vector_stores.base import VectorStoreBase from mem0.vector_stores.base import VectorStoreBase
@@ -446,14 +454,14 @@ class Databricks(VectorStoreBase):
logger.error(f"Insert operation failed: {e}") logger.error(f"Insert operation failed: {e}")
raise raise
def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None) -> List[MemoryResult]: def search(self, query: str, vectors: list, top_k: int = 5, filters: dict = None) -> List[MemoryResult]:
""" """
Search for similar vectors or text using the Databricks Vector Search index. Search for similar vectors or text using the Databricks Vector Search index.
Args: Args:
query (str): Search query text (for text-based search). query (str): Search query text (for text-based search).
vectors (list): Query vector (for vector-based search). vectors (list): Query vector (for vector-based search).
limit (int): Maximum number of results. top_k (int): Maximum number of results.
filters (dict): Filters to apply. filters (dict): Filters to apply.
Returns: Returns:
@@ -468,7 +476,7 @@ class Databricks(VectorStoreBase):
query_kwargs = { query_kwargs = {
"index_name": self.fully_qualified_index_name, "index_name": self.fully_qualified_index_name,
"columns": self.column_names, "columns": self.column_names,
"num_results": limit, "num_results": top_k,
"query_type": self.query_type, "query_type": self.query_type,
"filters_json": filters_json, "filters_json": filters_json,
} }
@@ -719,20 +727,20 @@ class Databricks(VectorStoreBase):
logger.error(f"Failed to get info for index '{name or self.index_name}': {e}") logger.error(f"Failed to get info for index '{name or self.index_name}': {e}")
raise raise
def list(self, filters: dict = None, limit: int = None) -> list[MemoryResult]: def list(self, filters: dict = None, top_k: int = None) -> list[MemoryResult]:
""" """
List all recent created memories from the vector store. List all recent created memories from the vector store.
Args: Args:
filters (dict, optional): Filters to apply. filters (dict, optional): Filters to apply.
limit (int, optional): Maximum number of results. top_k (int, optional): Maximum number of results.
Returns: Returns:
List containing list of MemoryResult objects. List containing list of MemoryResult objects.
""" """
try: try:
filters_json = json.dumps(filters) if filters else None filters_json = json.dumps(filters) if filters else None
num_results = limit or 100 num_results = top_k or 100
columns = self.column_names columns = self.column_names
# Use query_text for Delta Sync with model endpoint, query_vector otherwise # Use query_text for Delta Sync with model endpoint, query_vector otherwise
query_kwargs = { query_kwargs = {
+6 -6
View File
@@ -129,7 +129,7 @@ class ElasticsearchDB(VectorStoreBase):
return results return results
def search( def search(
self, query: str, vectors: List[float], limit: int = 5, filters: Optional[Dict] = None self, query: str, vectors: List[float], top_k: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
""" """
Search with two options: Search with two options:
@@ -137,10 +137,10 @@ class ElasticsearchDB(VectorStoreBase):
2. Use KNN search on vectors with pre-filtering if no custom search query is provided 2. Use KNN search on vectors with pre-filtering if no custom search query is provided
""" """
if self.custom_search_query: if self.custom_search_query:
search_query = self.custom_search_query(vectors, limit, filters) search_query = self.custom_search_query(vectors, top_k, filters)
else: else:
search_query = { search_query = {
"knn": {"field": "vector", "query_vector": vectors, "k": limit, "num_candidates": limit * 2} "knn": {"field": "vector", "query_vector": vectors, "k": top_k, "num_candidates": top_k * 2}
} }
if filters: if filters:
filter_conditions = [] filter_conditions = []
@@ -203,7 +203,7 @@ class ElasticsearchDB(VectorStoreBase):
"""Get information about a collection (index).""" """Get information about a collection (index)."""
return self.client.indices.get(index=name) return self.client.indices.get(index=name)
def list(self, filters: Optional[Dict] = None, limit: Optional[int] = None) -> List[List[OutputData]]: def list(self, filters: Optional[Dict] = None, top_k: Optional[int] = None) -> List[List[OutputData]]:
"""List all memories.""" """List all memories."""
query: Dict[str, Any] = {"query": {"match_all": {}}} query: Dict[str, Any] = {"query": {"match_all": {}}}
@@ -213,8 +213,8 @@ class ElasticsearchDB(VectorStoreBase):
filter_conditions.append({"term": {f"metadata.{key}": value}}) filter_conditions.append({"term": {f"metadata.{key}": value}})
query["query"] = {"bool": {"must": filter_conditions}} query["query"] = {"bool": {"must": filter_conditions}}
if limit: if top_k:
query["size"] = limit query["size"] = top_k
response = self.client.search(index=self.collection_name, body=query) response = self.client.search(index=self.collection_name, body=query)
+15 -16
View File
@@ -2,14 +2,13 @@ import logging
import os import os
import pickle import pickle
import uuid import uuid
import warnings
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List, Optional
import numpy as np import numpy as np
from pydantic import BaseModel from pydantic import BaseModel
import warnings
try: try:
# Suppress SWIG deprecation warnings from FAISS # Suppress SWIG deprecation warnings from FAISS
warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*SwigPy.*") warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*SwigPy.*")
@@ -115,23 +114,23 @@ class FAISS(VectorStoreBase):
except Exception as e: except Exception as e:
logger.warning(f"Failed to save FAISS index: {e}") logger.warning(f"Failed to save FAISS index: {e}")
def _parse_output(self, scores, ids, limit=None) -> List[OutputData]: def _parse_output(self, scores, ids, top_k=None) -> List[OutputData]:
""" """
Parse the output data. Parse the output data.
Args: Args:
scores: Similarity scores from FAISS. scores: Similarity scores from FAISS.
ids: Indices from FAISS. ids: Indices from FAISS.
limit: Maximum number of results to return. top_k: Maximum number of results to return.
Returns: Returns:
List[OutputData]: Parsed output data. List[OutputData]: Parsed output data.
""" """
if limit is None: if top_k is None:
limit = len(ids) top_k = len(ids)
results = [] results = []
for i in range(min(len(ids), limit)): for i in range(min(len(ids), top_k)):
if ids[i] == -1: # FAISS returns -1 for empty results if ids[i] == -1: # FAISS returns -1 for empty results
continue continue
@@ -225,7 +224,7 @@ class FAISS(VectorStoreBase):
logger.info(f"Inserted {len(vectors)} vectors into collection {self.collection_name}") logger.info(f"Inserted {len(vectors)} vectors into collection {self.collection_name}")
def search( def search(
self, query: str, vectors: List[list], limit: int = 5, filters: Optional[Dict] = None self, query: str, vectors: List[list], top_k: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
""" """
Search for similar vectors. Search for similar vectors.
@@ -233,7 +232,7 @@ class FAISS(VectorStoreBase):
Args: Args:
query (str): Query (not used, kept for API compatibility). query (str): Query (not used, kept for API compatibility).
vectors (List[list]): List of vectors to search. vectors (List[list]): List of vectors to search.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None. filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -250,19 +249,19 @@ class FAISS(VectorStoreBase):
if self.normalize_L2 and self.distance_strategy.lower() == "euclidean": if self.normalize_L2 and self.distance_strategy.lower() == "euclidean":
faiss.normalize_L2(query_vectors) faiss.normalize_L2(query_vectors)
fetch_k = limit * 2 if filters else limit fetch_k = top_k * 2 if filters else top_k
scores, indices = self.index.search(query_vectors, fetch_k) scores, indices = self.index.search(query_vectors, fetch_k)
results = self._parse_output(scores[0], indices[0], limit) results = self._parse_output(scores[0], indices[0], top_k)
if filters: if filters:
filtered_results = [] filtered_results = []
for result in results: for result in results:
if self._apply_filters(result.payload, filters): if self._apply_filters(result.payload, filters):
filtered_results.append(result) filtered_results.append(result)
if len(filtered_results) >= limit: if len(filtered_results) >= top_k:
break break
results = filtered_results[:limit] results = filtered_results[:top_k]
return results return results
@@ -450,13 +449,13 @@ class FAISS(VectorStoreBase):
"distance": self.distance_strategy, "distance": self.distance_strategy,
} }
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, top_k: int = 100) -> List[OutputData]:
""" """
List all vectors in a collection. List all vectors in a collection.
Args: Args:
filters (Optional[Dict], optional): Filters to apply to the list. Defaults to None. filters (Optional[Dict], optional): Filters to apply to the list. Defaults to None.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
@@ -482,7 +481,7 @@ class FAISS(VectorStoreBase):
) )
count += 1 count += 1
if count >= limit: if count >= top_k:
break break
return [results] return [results]
+5 -5
View File
@@ -91,15 +91,15 @@ class Langchain(VectorStoreBase):
texts = [payload.get("data", "") for payload in payloads] if payloads else [""] * len(vectors) texts = [payload.get("data", "") for payload in payloads] if payloads else [""] * len(vectors)
self.client.add_texts(texts=texts, metadatas=payloads, ids=ids) self.client.add_texts(texts=texts, metadatas=payloads, ids=ids)
def search(self, query: str, vectors: List[List[float]], limit: int = 5, filters: Optional[Dict] = None): def search(self, query: str, vectors: List[List[float]], top_k: int = 5, filters: Optional[Dict] = None):
""" """
Search for similar vectors in LangChain. Search for similar vectors in LangChain.
""" """
# For each vector, perform a similarity search # For each vector, perform a similarity search
if filters: if filters:
results = self.client.similarity_search_by_vector(embedding=vectors, k=limit, filter=filters) results = self.client.similarity_search_by_vector(embedding=vectors, k=top_k, filter=filters)
else: else:
results = self.client.similarity_search_by_vector(embedding=vectors, k=limit) results = self.client.similarity_search_by_vector(embedding=vectors, k=top_k)
final_results = self._parse_output(results) final_results = self._parse_output(results)
return final_results return final_results
@@ -152,7 +152,7 @@ class Langchain(VectorStoreBase):
""" """
return {"name": self.collection_name} return {"name": self.collection_name}
def list(self, filters=None, limit=None): def list(self, filters=None, top_k=None):
""" """
List all vectors in a collection. List all vectors in a collection.
""" """
@@ -164,7 +164,7 @@ class Langchain(VectorStoreBase):
# Handle all filters, not just user_id # Handle all filters, not just user_id
where_clause = filters where_clause = filters
result = self.client._collection.get(where=where_clause, limit=limit) result = self.client._collection.get(where=where_clause, limit=top_k)
# Convert the result to the expected format # Convert the result to the expected format
if result and isinstance(result, dict): if result and isinstance(result, dict):
+6 -6
View File
@@ -139,14 +139,14 @@ class MilvusDB(VectorStoreBase):
return memory return memory
def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None) -> list: def search(self, query: str, vectors: list, top_k: int = 5, filters: dict = None) -> list:
""" """
Search for similar vectors. Search for similar vectors.
Args: Args:
query (str): Query. query (str): Query.
vectors (List[float]): Query vector. vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None. filters (Dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -156,7 +156,7 @@ class MilvusDB(VectorStoreBase):
hits = self.client.search( hits = self.client.search(
collection_name=self.collection_name, collection_name=self.collection_name,
data=[vectors], data=[vectors],
limit=limit, limit=top_k,
filter=query_filter, filter=query_filter,
output_fields=["*"], output_fields=["*"],
) )
@@ -235,19 +235,19 @@ class MilvusDB(VectorStoreBase):
""" """
return self.client.get_collection_stats(collection_name=self.collection_name) return self.client.get_collection_stats(collection_name=self.collection_name)
def list(self, filters: dict = None, limit: int = 100) -> list: def list(self, filters: dict = None, top_k: int = 100) -> list:
""" """
List all vectors in a collection. List all vectors in a collection.
Args: Args:
filters (Dict, optional): Filters to apply to the list. filters (Dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
""" """
query_filter = self._create_filter(filters) if filters else None query_filter = self._create_filter(filters) if filters else None
result = self.client.query(collection_name=self.collection_name, filter=query_filter, limit=limit) result = self.client.query(collection_name=self.collection_name, filter=query_filter, limit=top_k)
memories = [] memories = []
for data in result: for data in result:
obj = OutputData(id=data.get("id"), score=None, payload=data.get("metadata")) obj = OutputData(id=data.get("id"), score=None, payload=data.get("metadata"))
+7 -7
View File
@@ -113,14 +113,14 @@ class MongoDB(VectorStoreBase):
except PyMongoError as e: except PyMongoError as e:
logger.error(f"Error inserting data: {e}") logger.error(f"Error inserting data: {e}")
def search(self, query: str, vectors: List[float], limit=5, filters: Optional[Dict] = None) -> List[OutputData]: def search(self, query: str, vectors: List[float], top_k=5, filters: Optional[Dict] = None) -> List[OutputData]:
""" """
Search for similar vectors using the vector search index. Search for similar vectors using the vector search index.
Args: Args:
query (str): Query string query (str): Query string
vectors (List[float]): Query vector. vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. filters (Dict, optional): Filters to apply to the search.
Returns: Returns:
@@ -139,8 +139,8 @@ class MongoDB(VectorStoreBase):
{ {
"$vectorSearch": { "$vectorSearch": {
"index": self.index_name, "index": self.index_name,
"limit": limit, "limit": top_k,
"numCandidates": min(limit * 20, 10000), "numCandidates": min(top_k * 20, 10000),
"queryVector": vectors, "queryVector": vectors,
"path": "embedding", "path": "embedding",
} }
@@ -271,13 +271,13 @@ class MongoDB(VectorStoreBase):
logger.error(f"Error getting collection info: {e}") logger.error(f"Error getting collection info: {e}")
return {} return {}
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, top_k: int = 100) -> List[OutputData]:
""" """
List vectors in the collection. List vectors in the collection.
Args: Args:
filters (Dict, optional): Filters to apply to the list. filters (Dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return. top_k (int, optional): Number of vectors to return.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
@@ -292,7 +292,7 @@ class MongoDB(VectorStoreBase):
if filter_conditions: if filter_conditions:
query = {"$and": filter_conditions} query = {"$and": filter_conditions}
cursor = self.collection.find(query).limit(limit) cursor = self.collection.find(query).limit(top_k)
results = [OutputData(id=str(doc["_id"]), score=None, payload=doc.get("payload")) for doc in cursor] results = [OutputData(id=str(doc["_id"]), score=None, payload=doc.get("payload")) for doc in cursor]
logger.info(f"Retrieved {len(results)} documents from collection '{self.collection_name}'.") logger.info(f"Retrieved {len(results)} documents from collection '{self.collection_name}'.")
return results return results
+6 -6
View File
@@ -133,7 +133,7 @@ class NeptuneAnalyticsVector(VectorStoreBase):
def search( def search(
self, query: str, vectors: List[float], limit: int = 5, filters: Optional[Dict] = None self, query: str, vectors: List[float], top_k: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
""" """
Search for similar vectors using embedding similarity. Search for similar vectors using embedding similarity.
@@ -144,7 +144,7 @@ class NeptuneAnalyticsVector(VectorStoreBase):
Args: Args:
query (str): Search query text (unused in vector search). query (str): Search query text (unused in vector search).
vectors (List[float]): Query embedding vector. vectors (List[float]): Query embedding vector.
limit (int, optional): Maximum number of results to return. Defaults to 5. top_k (int, optional): Maximum number of results to return. Defaults to 5.
filters (Optional[Dict]): Optional filters to apply to search results. filters (Optional[Dict]): Optional filters to apply to search results.
Returns: Returns:
@@ -159,7 +159,7 @@ class NeptuneAnalyticsVector(VectorStoreBase):
query_string = f""" query_string = f"""
CALL neptune.algo.vectors.topKByEmbeddingWithFiltering({{ CALL neptune.algo.vectors.topKByEmbeddingWithFiltering({{
topK: {limit}, topK: {top_k},
embedding: {vectors} embedding: {vectors}
{filter_clause} {filter_clause}
}} }}
@@ -309,7 +309,7 @@ class NeptuneAnalyticsVector(VectorStoreBase):
pass pass
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, top_k: int = 100) -> List[OutputData]:
""" """
List all vectors in the collection with optional filtering. List all vectors in the collection with optional filtering.
@@ -317,7 +317,7 @@ class NeptuneAnalyticsVector(VectorStoreBase):
Args: Args:
filters (Optional[Dict]): Optional filters to apply based on metadata. filters (Optional[Dict]): Optional filters to apply based on metadata.
limit (int, optional): Maximum number of vectors to return. Defaults to 100. top_k (int, optional): Maximum number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors with their metadata. List[OutputData]: List of vectors with their metadata.
@@ -325,7 +325,7 @@ class NeptuneAnalyticsVector(VectorStoreBase):
where_clause = self._get_where_clause(filters) if filters else "" where_clause = self._get_where_clause(filters) if filters else ""
para = { para = {
"limit": limit, "limit": top_k,
} }
query_string = f""" query_string = f"""
MATCH (n :{self.collection_name}) MATCH (n :{self.collection_name})
+7 -7
View File
@@ -158,7 +158,7 @@ class OpenSearchDB(VectorStoreBase):
return results return results
def search( def search(
self, query: str, vectors: List[float], limit: int = 5, filters: Optional[Dict] = None self, query: str, vectors: List[float], top_k: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
"""Search for similar vectors using OpenSearch k-NN search with optional filters.""" """Search for similar vectors using OpenSearch k-NN search with optional filters."""
@@ -167,13 +167,13 @@ class OpenSearchDB(VectorStoreBase):
"knn": { "knn": {
"vector_field": { "vector_field": {
"vector": vectors, "vector": vectors,
"k": limit * 2, "k": top_k * 2,
} }
} }
} }
# Start building the full query # Start building the full query
query_body = {"size": limit * 2, "query": None} query_body = {"size": top_k * 2, "query": None}
# Prepare filter conditions if applicable # Prepare filter conditions if applicable
filter_clauses = [] filter_clauses = []
@@ -196,7 +196,7 @@ class OpenSearchDB(VectorStoreBase):
hits = response["hits"]["hits"] hits = response["hits"]["hits"]
results = [ results = [
OutputData(id=hit["_source"].get("id"), score=hit["_score"], payload=hit["_source"].get("payload", {})) OutputData(id=hit["_source"].get("id"), score=hit["_score"], payload=hit["_source"].get("payload", {}))
for hit in hits[:limit] # Ensure we don't exceed limit for hit in hits[:top_k] # Ensure we don't exceed top_k
] ]
return results return results
except Exception as e: except Exception as e:
@@ -284,7 +284,7 @@ class OpenSearchDB(VectorStoreBase):
"""Get information about a collection (index).""" """Get information about a collection (index)."""
return self.client.indices.get(index=name) return self.client.indices.get(index=name)
def list(self, filters: Optional[Dict] = None, limit: Optional[int] = None) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, top_k: Optional[int] = None) -> List[OutputData]:
try: try:
"""List all memories with optional filters.""" """List all memories with optional filters."""
query: Dict = {"query": {"match_all": {}}} query: Dict = {"query": {"match_all": {}}}
@@ -299,8 +299,8 @@ class OpenSearchDB(VectorStoreBase):
if filter_clauses: if filter_clauses:
query["query"] = {"bool": {"filter": filter_clauses}} query["query"] = {"bool": {"filter": filter_clauses}}
if limit: if top_k:
query["size"] = limit query["size"] = top_k
response = self.client.search(index=self.collection_name, body=query) response = self.client.search(index=self.collection_name, body=query)
hits = response["hits"]["hits"] hits = response["hits"]["hits"]
+6 -6
View File
@@ -203,7 +203,7 @@ class PGVector(VectorStoreBase):
self, self,
query: str, query: str,
vectors: list[float], vectors: list[float],
limit: Optional[int] = 5, top_k: Optional[int] = 5,
filters: Optional[dict] = None, filters: Optional[dict] = None,
) -> List[OutputData]: ) -> List[OutputData]:
""" """
@@ -212,7 +212,7 @@ class PGVector(VectorStoreBase):
Args: Args:
query (str): Query. query (str): Query.
vectors (List[float]): Query vector. vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None. filters (Dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -237,7 +237,7 @@ class PGVector(VectorStoreBase):
ORDER BY distance ORDER BY distance
LIMIT %s LIMIT %s
""", """,
(vectors, *filter_params, limit), (vectors, *filter_params, top_k),
) )
results = cur.fetchall() results = cur.fetchall()
@@ -350,14 +350,14 @@ class PGVector(VectorStoreBase):
def list( def list(
self, self,
filters: Optional[dict] = None, filters: Optional[dict] = None,
limit: Optional[int] = 100 top_k: Optional[int] = 100
) -> List[OutputData]: ) -> List[OutputData]:
""" """
List all vectors in a collection. List all vectors in a collection.
Args: Args:
filters (Dict, optional): Filters to apply to the list. filters (Dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors. List[OutputData]: List of vectors.
@@ -380,7 +380,7 @@ class PGVector(VectorStoreBase):
""" """
with self._get_cursor() as cur: with self._get_cursor() as cur:
cur.execute(query, (*filter_params, limit)) cur.execute(query, (*filter_params, top_k))
results = cur.fetchall() results = cur.fetchall()
return [[OutputData(id=str(r[0]), score=None, payload=r[2]) for r in results]] return [[OutputData(id=str(r[0]), score=None, payload=r[2]) for r in results]]
+6 -6
View File
@@ -204,7 +204,7 @@ class PineconeDB(VectorStoreBase):
return pinecone_filter return pinecone_filter
def search( def search(
self, query: str, vectors: List[float], limit: int = 5, filters: Optional[Dict] = None self, query: str, vectors: List[float], top_k: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
""" """
Search for similar vectors. Search for similar vectors.
@@ -212,7 +212,7 @@ class PineconeDB(VectorStoreBase):
Args: Args:
query (str): Query. query (str): Query.
vectors (list): List of vectors to search. vectors (list): List of vectors to search.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (dict, optional): Filters to apply to the search. Defaults to None. filters (dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -222,7 +222,7 @@ class PineconeDB(VectorStoreBase):
query_params = { query_params = {
"vector": vectors, "vector": vectors,
"top_k": limit, "top_k": top_k,
"include_metadata": True, "include_metadata": True,
"include_values": False, "include_values": False,
} }
@@ -320,13 +320,13 @@ class PineconeDB(VectorStoreBase):
""" """
return self.client.describe_index(self.collection_name) return self.client.describe_index(self.collection_name)
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, top_k: int = 100) -> List[OutputData]:
""" """
List vectors in an index with optional filtering. List vectors in an index with optional filtering.
Args: Args:
filters (dict, optional): Filters to apply to the list. Defaults to None. filters (dict, optional): Filters to apply to the list. Defaults to None.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
dict: List of vectors with their metadata. dict: List of vectors with their metadata.
@@ -340,7 +340,7 @@ class PineconeDB(VectorStoreBase):
query_params = { query_params = {
"vector": zero_vector, "vector": zero_vector,
"top_k": limit, "top_k": top_k,
"include_metadata": True, "include_metadata": True,
"include_values": True, "include_values": True,
} }
+6 -6
View File
@@ -308,14 +308,14 @@ class Qdrant(VectorStoreBase):
must_not=must_not or None, must_not=must_not or None,
) )
def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None) -> list: def search(self, query: str, vectors: list, top_k: int = 5, filters: dict = None) -> list:
""" """
Search for similar vectors. Search for similar vectors.
Args: Args:
query (str): Query. query (str): Query.
vectors (list): Query vector. vectors (list): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (dict, optional): Filters to apply to the search. Defaults to None. filters (dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -326,7 +326,7 @@ class Qdrant(VectorStoreBase):
collection_name=self.collection_name, collection_name=self.collection_name,
query=vectors, query=vectors,
query_filter=query_filter, query_filter=query_filter,
limit=limit, limit=top_k,
) )
return hits.points return hits.points
@@ -404,13 +404,13 @@ class Qdrant(VectorStoreBase):
""" """
return self.client.get_collection(collection_name=self.collection_name) return self.client.get_collection(collection_name=self.collection_name)
def list(self, filters: dict = None, limit: int = 100) -> list: def list(self, filters: dict = None, top_k: int = 100) -> list:
""" """
List all vectors in a collection. List all vectors in a collection.
Args: Args:
filters (dict, optional): Filters to apply to the list. Defaults to None. filters (dict, optional): Filters to apply to the list. Defaults to None.
limit (int, optional): Number of vectors to return. Defaults to 100. top_k (int, optional): Number of vectors to return. Defaults to 100.
Returns: Returns:
list: List of vectors. list: List of vectors.
@@ -419,7 +419,7 @@ class Qdrant(VectorStoreBase):
result = self.client.scroll( result = self.client.scroll(
collection_name=self.collection_name, collection_name=self.collection_name,
scroll_filter=query_filter, scroll_filter=query_filter,
limit=limit, limit=top_k,
with_payload=True, with_payload=True,
with_vectors=False, with_vectors=False,
) )
+5 -5
View File
@@ -141,7 +141,7 @@ class RedisDB(VectorStoreBase):
data.append(entry) data.append(entry)
self.index.load(data, id_field="memory_id") self.index.load(data, id_field="memory_id")
def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None): def search(self, query: str, vectors: list, top_k: int = 5, filters: dict = None):
conditions = [Tag(key) == value for key, value in filters.items() if value is not None] conditions = [Tag(key) == value for key, value in filters.items() if value is not None]
filter = reduce(lambda x, y: x & y, conditions) filter = reduce(lambda x, y: x & y, conditions)
@@ -150,7 +150,7 @@ class RedisDB(VectorStoreBase):
vector_field_name="embedding", vector_field_name="embedding",
return_fields=["memory_id", "hash", "agent_id", "run_id", "user_id", "memory", "metadata", "created_at"], return_fields=["memory_id", "hash", "agent_id", "run_id", "user_id", "memory", "metadata", "created_at"],
filter_expression=filter, filter_expression=filter,
num_results=limit, num_results=top_k,
) )
results = self.index.query(v) results = self.index.query(v)
@@ -254,15 +254,15 @@ class RedisDB(VectorStoreBase):
# Recreate the index with the same parameters # Recreate the index with the same parameters
self.create_col(collection_name, self.embedding_model_dims) self.create_col(collection_name, self.embedding_model_dims)
def list(self, filters: dict = None, limit: int = None) -> list: def list(self, filters: dict = None, top_k: int = None) -> list:
""" """
List all recent created memories from the vector store. List all recent created memories from the vector store.
""" """
conditions = [Tag(key) == value for key, value in filters.items() if value is not None] conditions = [Tag(key) == value for key, value in filters.items() if value is not None]
filter = reduce(lambda x, y: x & y, conditions) filter = reduce(lambda x, y: x & y, conditions)
query = Query(str(filter)).sort_by("created_at", asc=False) query = Query(str(filter)).sort_by("created_at", asc=False)
if limit is not None: if top_k is not None:
query = Query(str(filter)).sort_by("created_at", asc=False).paging(0, limit) query = Query(str(filter)).sort_by("created_at", asc=False).paging(0, top_k)
results = self.index.search(query) results = self.index.search(query)
return [ return [
+5 -5
View File
@@ -99,12 +99,12 @@ class S3Vectors(VectorStoreBase):
vectors=vectors_to_put, vectors=vectors_to_put,
) )
def search(self, query, vectors, limit=5, filters=None): def search(self, query, vectors, top_k=5, filters=None):
params = { params = {
"vectorBucketName": self.vector_bucket_name, "vectorBucketName": self.vector_bucket_name,
"indexName": self.collection_name, "indexName": self.collection_name,
"queryVector": {"float32": vectors}, "queryVector": {"float32": vectors},
"topK": limit, "topK": top_k,
"returnMetadata": True, "returnMetadata": True,
"returnDistance": True, "returnDistance": True,
} }
@@ -149,7 +149,7 @@ class S3Vectors(VectorStoreBase):
response = self.client.get_index(vectorBucketName=self.vector_bucket_name, indexName=self.collection_name) response = self.client.get_index(vectorBucketName=self.vector_bucket_name, indexName=self.collection_name)
return response.get("index", {}) return response.get("index", {})
def list(self, filters=None, limit=None): def list(self, filters=None, top_k=None):
# Note: list_vectors does not support metadata filtering. # Note: list_vectors does not support metadata filtering.
if filters: if filters:
logger.warning("S3 Vectors `list` does not support metadata filtering. Ignoring filters.") logger.warning("S3 Vectors `list` does not support metadata filtering. Ignoring filters.")
@@ -160,8 +160,8 @@ class S3Vectors(VectorStoreBase):
"returnData": False, "returnData": False,
"returnMetadata": True, "returnMetadata": True,
} }
if limit: if top_k:
params["maxResults"] = limit params["maxResults"] = top_k
paginator = self.client.get_paginator("list_vectors") paginator = self.client.get_paginator("list_vectors")
pages = paginator.paginate(**params) pages = paginator.paginate(**params)
+6 -6
View File
@@ -116,7 +116,7 @@ class Supabase(VectorStoreBase):
self.collection.upsert(records) self.collection.upsert(records)
def search( def search(
self, query: str, vectors: List[float], limit: int = 5, filters: Optional[dict] = None self, query: str, vectors: List[float], top_k: int = 5, filters: Optional[dict] = None
) -> List[OutputData]: ) -> List[OutputData]:
""" """
Search for similar vectors. Search for similar vectors.
@@ -124,7 +124,7 @@ class Supabase(VectorStoreBase):
Args: Args:
query (str): Query. query (str): Query.
vectors (List[float]): Query vector. vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. top_k (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None. filters (Dict, optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
@@ -132,7 +132,7 @@ class Supabase(VectorStoreBase):
""" """
filters = self._preprocess_filters(filters) filters = self._preprocess_filters(filters)
results = self.collection.query( results = self.collection.query(
data=vectors, limit=limit, filters=filters, include_metadata=True, include_value=True data=vectors, limit=top_k, filters=filters, include_metadata=True, include_value=True
) )
return [OutputData(id=str(result[0]), score=float(result[1]), payload=result[2]) for result in results] return [OutputData(id=str(result[0]), score=float(result[1]), payload=result[2]) for result in results]
@@ -209,13 +209,13 @@ class Supabase(VectorStoreBase):
"index": {"method": info.index_method, "metric": info.distance_metric}, "index": {"method": info.index_method, "metric": info.distance_metric},
} }
def list(self, filters: Optional[dict] = None, limit: int = 100) -> List[OutputData]: def list(self, filters: Optional[dict] = None, top_k: int = 100) -> List[OutputData]:
""" """
List vectors in the collection. List vectors in the collection.
Args: Args:
filters (Dict, optional): Filters to apply filters (Dict, optional): Filters to apply
limit (int, optional): Maximum number of results to return. Defaults to 100. top_k (int, optional): Maximum number of results to return. Defaults to 100.
Returns: Returns:
List[OutputData]: List of vectors List[OutputData]: List of vectors
@@ -223,7 +223,7 @@ class Supabase(VectorStoreBase):
filters = self._preprocess_filters(filters) filters = self._preprocess_filters(filters)
query = [0] * self.embedding_model_dims query = [0] * self.embedding_model_dims
ids = self.collection.query( ids = self.collection.query(
data=query, limit=limit, filters=filters, include_metadata=True, include_value=False data=query, limit=top_k, filters=filters, include_metadata=True, include_value=False
) )
ids = [id[0] for id in ids] ids = [id[0] for id in ids]
records = self.collection.fetch(ids=ids) records = self.collection.fetch(ids=ids)

Some files were not shown because too many files have changed in this diff Show More