Compare commits

...

42 Commits

Author SHA1 Message Date
Dev Khant ac085db500 version bump -> 0.1.112 (#3058) 2025-06-27 15:19:57 +05:30
Dev Khant 2cc253341c Fix mongodb config name (#3052) 2025-06-26 23:06:21 +05:30
Dev Khant e3e2da6d45 Fix: Gemini Embeddings and LLM (#3050) 2025-06-26 21:05:00 +05:30
Dev Khant acf7a30d32 Doc: Add async_mode (#3037) 2025-06-25 10:51:49 -07:00
Saket Aryan a4f6751741 fix(ui-backend): resolve provider name format inconsistency in form configuration (#3041) 2025-06-25 09:48:22 -07:00
Ryan Rozich 6f3fbd087d fix: Fix memory categorization by updating dependencies and correcting API usage (#3005)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Saket Aryan <94069182+whysosaket@users.noreply.github.com>
2025-06-25 22:02:26 +05:30
Antaripa Saha a98842422b doc: Broken links fixed in docs (#3034) 2025-06-25 17:18:29 +05:30
Antaripa Saha aaf879322c Platform feature docs revamp (#3007) 2025-06-25 00:57:08 -07:00
Laith Al-Saadoon 8139b5887f fix: bedrock llm, embeddings, tools, temporary creds (#3023) 2025-06-24 20:46:06 +05:30
Saket Aryan b4b27f099e Add immutable param to add method and bump version (#3022) 2025-06-24 05:03:35 +05:30
Dev Khant dc877fd3ba version bump -> 0.1.111 (#3016) 2025-06-23 21:52:03 +05:30
Akshat Jain 2bb0653e67 Add: Json Parsing to solve Hallucination Errors (#3013) 2025-06-23 21:50:16 +05:30
Akshat Jain eb24b92227 Add : Openmemory Local Support using New Library (#3014)
Co-authored-by: Saket Aryan <saketaryan2002@gmail.com>
2025-06-23 20:45:46 +05:30
Akshat Jain a5ec286fd4 Add: Openmemory Augment support (#3015) 2025-06-23 20:45:01 +05:30
NiLAy 89499aedbe Feature/vllm support (#2981) 2025-06-23 13:18:38 +05:30
Akshat Jain 386d8b87ae Fix: Migrate Gemini Embeddings (#3002)
Co-authored-by: Dev-Khant <devkhant24@gmail.com>
2025-06-23 13:16:10 +05:30
Akshat Jain c173ec32d0 Improve Docs: Agent Id - Mem0 OSS Graph Memory (#2969) 2025-06-21 23:34:28 +05:30
Akshat Jain dd6f6f7a2e Fix: Add MCP Client Integration Guide and update installation commands (#2956) 2025-06-20 22:09:10 +05:30
Dev Khant b6684b96f7 version bump -> 0.1.110 (#3001) 2025-06-20 20:30:51 +05:30
Akarsha Sehwag 1fa0f0a157 fix(opensearch): update logger warning (#2999) 2025-06-20 20:28:51 +05:30
Saket Aryan 2754f45387 Make V2 Add as Default (#2997) 2025-06-20 16:56:42 +05:30
Parshva Daftari ecd4d91046 Fix failing CI pipeline (#2979) 2025-06-20 15:19:11 +05:30
Prateek Chhikara a5a247b161 Update client.update() method documentation in OpenAPI specification (#2990) 2025-06-19 14:04:12 -07:00
Dev Khant d47cb8d284 Doc: Fix example in quickstart page (#2986) 2025-06-19 13:51:20 +05:30
Dev Khant fa15db089d Update Changelog (#2985) 2025-06-19 12:32:33 +05:30
Shili Cao d35065c887 Feature: baidu vector db integration (#2929) 2025-06-19 11:12:12 +05:30
Prateek Chhikara cdee6a4ff0 Enhance update method to support metadata (#2976) 2025-06-18 10:07:18 -07:00
Dev Khant 9eb4e77c75 Fix pinecone for async memory (#2975) 2025-06-18 01:37:45 +05:30
Akshat Jain c700d790db Fix Build CI Failure (#2973) 2025-06-17 09:39:19 -07:00
Antaripa Saha a90b572389 Memory agent powered by voice (Cartesia + Agno) (#2970) 2025-06-17 18:54:53 +05:30
i-sun 62c330e5b3 feat(LM Studio): Add response_format param for LM Studio to config (#2502) 2025-06-17 17:55:18 +05:30
Akshat Jain c70dc7614b Fix: Add Google Genai library support (#2941) 2025-06-17 17:47:09 +05:30
Fenil Faldu e0003247c3 feat: add AgentOps integration (#2898) 2025-06-17 11:38:39 +05:30
Saket Aryan 888ee766c5 TS SDK - filter memories param (#2971) 2025-06-17 10:54:35 +05:30
Saket Aryan c7e91171a0 Added Param output_format in AI SDK (#2960) 2025-06-15 06:42:56 +05:30
Dev Khant 18c870ec79 version bump -> 0.1.108 (#2958) 2025-06-14 21:58:12 +05:30
Dev Khant 3e5f68ee90 Add logger in Opensearch (#2957) 2025-06-14 21:55:22 +05:30
Fabian Valle a0cd4065d9 +MongoDB Vector Support (#2367)
Co-authored-by: Divya Gupta <divya.gupta@mongodb.com>
2025-06-14 17:57:06 +05:30
John Lockwood 7c0c4a03c4 Feat/add python version test envs (#2774) 2025-06-14 17:43:16 +05:30
John Lockwood a8ace18607 Fix/pin pinecone issue #2772 (#2773) 2025-06-14 17:38:32 +05:30
Dev Khant df43f904d1 deploy minor version -> 0.1.107rc2 (#2953) 2025-06-13 12:09:54 +05:30
Prateek Chhikara a5a07d711b Updates in client to support summary (#2951) 2025-06-13 12:04:38 +05:30
101 changed files with 4935 additions and 1074 deletions
+5 -1
View File
@@ -52,8 +52,12 @@ jobs:
with:
path: .venv
key: venv-mem0-${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}
- name: Install GEOS Libraries
run: sudo apt-get update && sudo apt-get install -y libgeos-dev
- name: Install dependencies
run: |
pip install --upgrade pip wheel setuptools
pip install --only-binary=shapely shapely
make install_all
pip install -e ".[test]"
pip install pinecone pinecone-text
@@ -102,4 +106,4 @@ jobs:
with:
file: coverage.xml
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+21 -15
View File
@@ -16,18 +16,19 @@ To make a contribution, follow these steps:
For more details about pull requests, please read [GitHub's guides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
### 📦 Package manager
### 📦 Development Environment
We use `poetry` as our package manager. You can install poetry by following the instructions [here](https://python-poetry.org/docs/#installation).
Please DO NOT use pip or conda to install the dependencies. Instead, use poetry:
We use `hatch` for managing development environments. To set up:
```bash
make install_all
# Activate environment for specific Python version:
hatch shell dev_py_3_9 # Python 3.9
hatch shell dev_py_3_10 # Python 3.10
hatch shell dev_py_3_11 # Python 3.11
#activate
poetry shell
# The environment will automatically install all dev dependencies
# Run tests within the activated shell:
make test
```
### 📌 Pre-commit
@@ -40,16 +41,21 @@ pre-commit install
### 🧪 Testing
We use `pytest` to test our code. You can run the tests by running the following command:
We use `pytest` to test our code across multiple Python versions. You can run tests using:
```bash
poetry run pytest tests
# or
# Run tests with default Python version
make test
# Test specific Python versions:
make test-py-3.9 # Python 3.9 environment
make test-py-3.10 # Python 3.10 environment
make test-py-3.11 # Python 3.11 environment
# When using hatch shells, run tests with:
make test # After activating a shell with hatch shell test_XX
```
Several packages have been removed from Poetry to make the package lighter. Therefore, it is recommended to run `make install_all` to install the remaining packages and ensure all tests pass. Make sure that all tests pass before submitting a pull request.
Make sure that all tests pass across all supported Python versions before submitting a pull request.
We look forward to your pull requests and can't wait to see your contributions!
We look forward to your pull requests and can't wait to see your contributions!
+11 -2
View File
@@ -12,8 +12,8 @@ install:
install_all:
pip install ruff==0.6.9 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
google-generativeai elasticsearch opensearch-py vecs pinecone pinecone-text faiss-cpu langchain-community \
upstash-vector azure-search-documents langchain-memgraph langchain-neo4j rank-bm25
google-generativeai elasticsearch opensearch-py vecs "pinecone<7.0.0" pinecone-text faiss-cpu langchain-community \
upstash-vector azure-search-documents langchain-memgraph langchain-neo4j rank-bm25 pymochow
# Format code with ruff
format:
@@ -41,3 +41,12 @@ clean:
test:
hatch run test
test-py-3.9:
hatch run dev_py_3_9:test
test-py-3.10:
hatch run dev_py_3_10:test
test-py-3.11:
hatch run dev_py_3_11:test
+239 -244
View File
@@ -8,6 +8,127 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2025-06-19" description="v0.1.109">
**New Features:**
- **AgentOps:** Added AgentOps integration
- **LM Studio:** Added response_format parameter for LM Studio configuration
- **Examples:** Added Memory agent powered by voice (Cartesia + Agno)
**Improvements:**
- **AI SDK:** Added output_format parameter
- **Client:** Enhanced update method to support metadata
- **Google:** Added Google Genai library support
**Bug Fixes:**
- **Build:** Fixed Build CI failure
- **Pinecone:** Fixed pinecone for async memory
</Update>
<Update label="2025-06-14" description="v0.1.108">
**New Features:**
- **MongoDB:** Added MongoDB Vector Store support
- **Client:** Added client support for summary functionality
**Improvements:**
- **Pinecone:** Fixed pinecone version issues
- **OpenSearch:** Added logger support
- **Testing:** Added python version test environments
</Update>
<Update label="2025-06-11" description="v0.1.107">
**Improvements:**
- **Documentation:**
- Updated Livekit documentation migration
- Updated OpenMemory hosted version documentation
- **Core:** Updated categorization flow
- **Storage:** Fixed migration issues
</Update>
<Update label="2025-06-09" description="v0.1.106">
**New Features:**
- **Cloudflare:** Added Cloudflare vector store support
- **Search:** Added threshold parameter to search functionality
- **API:** Added wildcard character support for v2 Memory APIs
**Improvements:**
- **Documentation:** Updated README docs for OpenMemory environment setup
- **Core:** Added support for unique user IDs
**Bug Fixes:**
- **Core:** Fixed error handling exceptions
</Update>
<Update label="2025-06-03" description="v0.1.104">
**Bug Fixes:**
- **Vector Stores:** Fixed GET_ALL functionality for FAISS and OpenSearch
</Update>
<Update label="2025-06-02" description="v0.1.103">
**New Features:**
- **LLM:** Added support for OpenAI compatible LLM providers with baseUrl configuration
**Improvements:**
- **Documentation:**
- Fixed broken links
- Improved Graph Memory features documentation clarity
- Updated enable_graph documentation
- **TypeScript SDK:** Updated Google SDK peer dependency version
- **Client:** Added async mode parameter
</Update>
<Update label="2025-05-26" description="v0.1.102">
**New Features:**
- **Examples:** Added Neo4j example
- **AI SDK:** Added Google provider support
- **OpenMemory:** Added LLM and Embedding Providers support
**Improvements:**
- **Documentation:**
- Updated memory export documentation
- Enhanced role-based memory attribution rules documentation
- Updated API reference and messages documentation
- Added Mastra and Raycast documentation
- Added NOT filter documentation for Search and GetAll V2
- Announced Claude 4 support
- **Core:**
- Removed support for passing string as input in client.add()
- Added support for sarvam-m model
- **TypeScript SDK:** Fixed types from message interface
**Bug Fixes:**
- **Memory:** Prevented saving prompt artifacts as memory when no new facts are present
- **OpenMemory:** Fixed typos in MCP tool description
</Update>
<Update label="2025-05-15" description="v0.1.101">
**New Features:**
- **Neo4j:** Added base label configuration support
**Improvements:**
- **Documentation:**
- Updated Healthcare example index
- Enhanced collaborative task agent documentation clarity
- Added criteria-based filtering documentation
- **OpenMemory:** Added cURL command for easy installation
- **Build:** Migrated to Hatch build system
</Update>
<Update label="2025-05-10" description="v0.1.100">
**New Features:**
@@ -288,6 +409,21 @@ mode: "wide"
<Tab title="TypeScript">
<Update label="2025-06-24" description="v2.1.33">
**Improvement :**
- **Client:** Added `immutable` param to `add` method.
</Update>
<Update label="2025-06-20" description="v2.1.32">
**Improvement :**
- **Client:** Made `api_version` V2 as default.
</Update>
<Update label="2025-06-17" description="v2.1.31">
**Improvement :**
- **Client:** Added param `filter_memories`.
</Update>
<Update label="2025-06-06" description="v2.1.30">
**New Features:**
- **OSS:** Added Cloudflare support
@@ -437,6 +573,104 @@ mode: "wide"
<Tab title="Platform">
<Update label="2025-06-19" description="">
**New Features:**
- **Rate Limiting:** Implemented comprehensive rate limiting system
**Improvements:**
- **Performance:** Added performance indexes for memory stats query
**Bug Fixes:**
- **Search:** Fixed search events not respecting top-k parameter
</Update>
<Update label="2025-06-18" description="">
**New Features:**
- **Memory Management:** Implemented OpenAI Batch API for Memory Cleaning with fallback
- **Playground:** Added Claude 4 support on Playground
**Improvements:**
- **Memory:** Added ability to update memory metadata
</Update>
<Update label="2025-06-17" description="">
**New Features:**
- **UI:** New Memories Page UI design
</Update>
<Update label="2025-06-16" description="">
**Improvements:**
- **Infrastructure:** Migrated to Application Load Balancer (ALB)
</Update>
<Update label="2025-06-13" description="">
**Improvements:**
- **Memory Management:** Enhanced Memory Management with Cosine Similarity Fallback
</Update>
<Update label="2025-06-11" description="">
**New Features:**
- **OMM:** Added OMM Script and UI functionality
**Improvements:**
- **API:** Added filters validation to semantic_search_v2 endpoint
</Update>
<Update label="2025-06-09" description="">
**New Features:**
- **Intercom:** Set Intercom events for ADD and SEARCH operations
- **OpenMemory:** Added Posthog integration and feedback functionality
- **MCP:** New JavaScript MCP Server with feedback support
**Improvements:**
- **Structured Data:** Enhanced structured data handling in memory management
</Update>
<Update label="2025-06-06" description="">
**New Features:**
- **OAuth:** Added Mem0 OAuth integration
- **OMM:** Added OMM-Mem0 sync for deleted memories
</Update>
<Update label="2025-06-05" description="">
**New Features:**
- **Filters:** Implemented Wildcard Filters and refactored filter logic in V2 Views
</Update>
<Update label="2025-06-02" description="">
**New Features:**
- **OpenMemory Cloud:** Added OpenMemory Cloud support
- **Structured Data:** Added 'structured_attributes' field to Memory model
</Update>
<Update label="2025-05-30" description="">
**New Features:**
- **Projects:** Added version and enable_graph to project views
- **OpenMemory:** Added Postgres support for OpenMemory
</Update>
<Update label="2025-05-19" description="">
**Bug Fixes:**
@@ -444,254 +678,15 @@ mode: "wide"
</Update>
<Update label="2025-05-17" description="">
**New Features:**
- **Graph:** Added Neo4J Graph Migration
- **API:** Added API to set custom instructions
</Update>
<Update label="2025-05-16" description="">
**New Features:**
- **API:** Added Org-wide API Limit and Usage
**Improvements:**
- **Database:** Added migration for "is_deleted" column
- **Graph:** Improved graph queries
</Update>
<Update label="2025-05-15" description="">
**New Features:**
- **Lambda:** Added actions to lambda
- **Core:** Added background runs support
- **Models:** Added o4-mini for pro users
</Update>
<Update label="2025-05-10" description="">
**New Features:**
- **Integrations:** Added Intercom Events integration
- **Billing:** Added prefilled email for payments
- **Organizations:** Added Pro organization marking
**Improvements:**
- **UI:** Fixed loading jitter for organization selection
- **Infrastructure:** Improved production scaling
</Update>
<Update label="2025-05-09" description="">
**Improvements:**
- **Memory:** Fixed filters in Memory Page
- **Deployment:** Added custom categories for on-premise
</Update>
<Update label="2025-05-08" description="">
**Improvements:**
- **Backend:** Updated Django settings for metrics
- **Memory:** Added retries to memory filtering
- **Search:** Added scoring mechanism
</Update>
<Update label="2025-05-07" description="">
**Improvements:**
- **Deployment:** Updated deployment scripts
- **Testing:** Added code coverage tracking
- **Memory:** Added background cron job for memory quality
</Update>
<Update label="2025-05-06" description="">
**New Features:**
- **Models:** Added support for 4.1-mini model
**Improvements:**
- **Infrastructure:** Increased instance count
- **API:** Added V2 for Manage Entities
</Update>
<Update label="2025-05-04" description="">
**New Features:**
- **Testing:** Added code coverage tracking
- **AI:** Added Keywords AI integration
**Improvements:**
- **UI:** Updated UI with tabs
- **Database:** Added migrations for custom instructions
- **Search:** Added criteria filtering
</Update>
<Update label="2025-04-26" description="">
**Improvements:**
- **Performance:** Parallelized embedding calls
- **Monitoring:** Added timing for LLM calls
- **Search:** Added category checking in Search V2
- **Bug Fixes:** Fixed issues with ADD filters
- **Graph:** Implemented new graph updates
</Update>
<Update label="2025-04-25" description="">
**Improvements:**
- **Memory:** Fixed memory export functionality
- **Analytics:** Added logging for project
</Update>
<Update label="2025-04-24" description="">
**Improvements:**
- **Output:** Added memory_type display for ADD output
</Update>
<Update label="2025-04-23" description="">
**New Features:**
- **UI:** Added new Pricing Component
- **Memory:** Implemented Long/Short term memory categorization
- **Output:** Modified serializer to hide memory_type
**Documentation:**
- Updated README for deployment
</Update>
<Update label="2025-04-22" description="">
**New Features:**
- **Memory:** Added timestamp to ADD call
**Bug Fixes:**
- Fixed issues with coreV2
</Update>
<Update label="2025-04-21" description="">
**New Features:**
- **Memory:** Implemented backdating with migrations and backfilling script
</Update>
<Update label="2025-04-17" description="">
**New Features:**
- **Billing:** Integrated Stripe Billing Dashboard
- **Admin:** Added webhook creation functionality
**Bug Fixes:**
- Fixed Users Page issues
- Fixed Custom Categories
- Fixed Table components
- Updated Stripe configuration
</Update>
<Update label="2025-04-16" description="">
**Improvements:**
- **Performance:** Made Admin panel and Memory Page faster
- **Security:** Implemented active session cancellation
- **Analytics:** Added Stripe customer ID capture
</Update>
<Update label="2025-04-12" description="">
**New Features:**
- **Memory Management:**
- Added ability to delete memories from Project level with filters
- Added delete memories capability on Memories Page
- **Memory Visualization:** Released V1 Graph Memory Visualization
- **Graph Playground:** Enabled for @mem0.ai users
- **Notifications:** Added email alerts to organization owners when new members join
- **Memory Export:** Added date support for filtering memory exports
**Improvements:**
- **Performance:**
- Optimized graph for better performance
- Optimized database calls in ADD method
- **Analytics:** Added flagging of paid users in Posthog
- **CI/CD:** Improved CI pipeline and fixed lint issues
</Update>
<Update label="2025-04-10" description="">
**New Features:**
- **Notifications:** Implemented email notifications for organization owners when new members join
**Improvements:**
- **CI/CD:** Fixed Dockerfile for CI tests
</Update>
<Update label="2025-04-09" description="">
**Improvements:**
- **Integrations:** Updated chat model for Together Qwen
- **Platform:** Removed older platforms
- **Bug Fixes:** Fixed FILTER_MAPPING
</Update>
<Update label="2025-04-03" description="">
**New Features:**
- **Memory:** Added implicit memory capabilities
- **API:** Improved implicit lambda and get_all v2 functionality
</Update>
<Update label="2025-04-02" description="">
**New Features:**
- **Integrations:** Added Clay integration
**Improvements:**
- **Integrations:** Removed deepseek coder from Together
- **API:** Added custom instructions for add v2
</Update>
<Update label="2025-03-31" description="">
**Security:**
- **Validation:** Added key validation in messages
</Update>
<Update label="2025-03-28" description="">
- **Updated Playground Prompt**
- **Send Email on User Addition to Org/Proj**
- **Fix Search Entity**
</Update>
<Update label="2025-03-19" description="">
- **General Stability & Performance Improvements**
</Update>
</Tab>
<Tab title="Vercel AI SDK">
<Update label="2025-06-15" description="v1.0.6">
**New Features:**
- **Vercel AI SDK:** Added param `filter_memories`.
</Update>
<Update label="2025-05-23" description="v1.0.5">
**New Features:**
- **Vercel AI SDK:** Added support for Google provider.
+1 -1
View File
@@ -39,5 +39,5 @@ Here are the parameters available for configuring Gemini embedder:
| Parameter | Description | Default Value |
| --- | --- | --- |
| `model` | The name of the embedding model to use | `models/text-embedding-004` |
| `embedding_dims` | Dimensions of the embedding model | `768` |
| `embedding_dims` | Dimensions of the embedding model (output_dimensionality will be considered as embedding_dims, so please set embedding_dims accordingly) | `768` |
| `api_key` | The Gemini API key | `None` |
+2
View File
@@ -58,6 +58,7 @@ config = {
m = Memory.from_config(config)
m.add("Your text here", user_id="user", metadata={"category": "example"})
```
```typescript TypeScript
@@ -76,6 +77,7 @@ const config = {
const memory = new Memory(config);
await memory.add("Your text here", { userId: "user123", metadata: { category: "example" } });
```
</CodeGroup>
## Why is Config Needed?
+15 -7
View File
@@ -4,7 +4,11 @@ title: Gemini
<Snippet file="paper-release.mdx" />
To use Gemini model, you have to set the `GEMINI_API_KEY` environment variable. You can obtain the Gemini API key from the [Google AI Studio](https://aistudio.google.com/app/apikey)
To use the Gemini model, set the `GEMINI_API_KEY` environment variable. You can obtain the Gemini API key from [Google AI Studio](https://aistudio.google.com/app/apikey).
> **Note:** As of the latest release, Mem0 uses the new `google.genai` SDK instead of the deprecated `google.generativeai`. All message formatting and model interaction now use the updated `types` module from `google.genai`.
> **Note:** Some Gemini models are being deprecated and will retire soon. It is recommended to migrate to the latest stable models like `"gemini-2.0-flash-001"` or `"gemini-2.0-flash-lite-001"` to ensure ongoing support and improvements.
## Usage
@@ -12,28 +16,32 @@ To use Gemini model, you have to set the `GEMINI_API_KEY` environment variable.
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your-api-key" # used for embedding model
os.environ["GEMINI_API_KEY"] = "your-api-key"
os.environ["OPENAI_API_KEY"] = "your-openai-api-key" # Used for embedding model
os.environ["GEMINI_API_KEY"] = "your-gemini-api-key"
config = {
"llm": {
"provider": "gemini",
"config": {
"model": "gemini-1.5-flash-latest",
"model": "gemini-2.0-flash-001",
"temperature": 0.2,
"max_tokens": 2000,
"top_p": 1.0
}
}
}
m = Memory.from_config(config)
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
{"role": "user", "content": "I’m not a big fan of thrillers, but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thrillers and suggest sci-fi movies instead."}
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
## Config
+1
View File
@@ -23,6 +23,7 @@ config = {
"temperature": 0.2,
"max_tokens": 2000,
"lmstudio_base_url": "http://localhost:1234/v1", # default LM Studio API URL
"lmstudio_response_format": {"type": "json_schema", "json_schema": {"type": "object", "schema": {}}},
}
}
}
+109
View File
@@ -0,0 +1,109 @@
---
title: vLLM
---
<Snippet file="paper-release.mdx" />
[vLLM](https://docs.vllm.ai/) is a high-performance inference engine for large language models that provides significant performance improvements for local inference. It's designed to maximize throughput and memory efficiency for serving LLMs.
## Prerequisites
1. **Install vLLM**:
```bash
pip install vllm
```
2. **Start vLLM server**:
```bash
# For testing with a small model
vllm serve microsoft/DialoGPT-medium --port 8000
# For production with a larger model (requires GPU)
vllm serve Qwen/Qwen2.5-32B-Instruct --port 8000
```
## Usage
```python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your-api-key" # used for embedding model
config = {
"llm": {
"provider": "vllm",
"config": {
"model": "Qwen/Qwen2.5-32B-Instruct",
"vllm_base_url": "http://localhost:8000/v1",
"temperature": 0.1,
"max_tokens": 2000,
}
}
}
m = Memory.from_config(config)
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thrillers, but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thrillers and suggest sci-fi movies instead."}
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
## Configuration Parameters
| Parameter | Description | Default | Environment Variable |
| --------------- | --------------------------------- | ----------------------------- | -------------------- |
| `model` | Model name running on vLLM server | `"Qwen/Qwen2.5-32B-Instruct"` | - |
| `vllm_base_url` | vLLM server URL | `"http://localhost:8000/v1"` | `VLLM_BASE_URL` |
| `api_key` | API key (dummy for local) | `"vllm-api-key"` | `VLLM_API_KEY` |
| `temperature` | Sampling temperature | `0.1` | - |
| `max_tokens` | Maximum tokens to generate | `2000` | - |
## Environment Variables
You can set these environment variables instead of specifying them in config:
```bash
export VLLM_BASE_URL="http://localhost:8000/v1"
export VLLM_API_KEY="your-vllm-api-key"
export OPENAI_API_KEY="your-openai-api-key" # for embeddings
```
## Benefits
- **High Performance**: 2-24x faster inference than standard implementations
- **Memory Efficient**: Optimized memory usage with PagedAttention
- **Local Deployment**: Keep your data private and reduce API costs
- **Easy Integration**: Drop-in replacement for other LLM providers
- **Flexible**: Works with any model supported by vLLM
## Troubleshooting
1. **Server not responding**: Make sure vLLM server is running
```bash
curl http://localhost:8000/health
```
2. **404 errors**: Ensure correct base URL format
```python
"vllm_base_url": "http://localhost:8000/v1" # Note the /v1
```
3. **Model not found**: Check model name matches server
4. **Out of memory**: Try smaller models or reduce `max_model_len`
```bash
vllm serve Qwen/Qwen2.5-32B-Instruct --max-model-len 4096
```
## Config
All available parameters for the `vllm` config are present in [Master List of All Params in Config](../config).
+3 -1
View File
@@ -14,7 +14,9 @@ To use a llm, you must provide a configuration to customize its usage. If no con
For a comprehensive list of available parameters for llm configuration, please refer to [Config](./config).
To view all supported llms, visit the [Supported LLMs](./models).
## Supported LLMs
See the list of supported LLMs below.
<Note>
All LLMs are supported in Python. The following LLMs are also supported in TypeScript: **OpenAI**, **Anthropic**, and **Groq**.
+67
View File
@@ -0,0 +1,67 @@
---
title: Baidu VectorDB (Mochow)
---
[Baidu VectorDB](https://cloud.baidu.com/doc/VDB/index.html) is an enterprise-level distributed vector database service developed by Baidu Intelligent Cloud. It is powered by Baidu's proprietary "Mochow" vector database kernel, providing high performance, availability, and security for vector search.
### Usage
```python
import os
from mem0 import Memory
config = {
"vector_store": {
"provider": "baidu",
"config": {
"endpoint": "http://your-mochow-endpoint:8287",
"account": "root",
"api_key": "your-api-key",
"database_name": "mem0",
"table_name": "mem0_table",
"embedding_model_dims": 1536,
"metric_type": "COSINE"
}
}
}
m = Memory.from_config(config)
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movie? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
### Config
Here are the available parameters for the `mochow` config:
| Parameter | Description | Default Value |
| --- | --- | --- |
| `endpoint` | Endpoint URL for your Baidu VectorDB instance | Required |
| `account` | Baidu VectorDB account name | `root` |
| `api_key` | API key for accessing Baidu VectorDB | Required |
| `database_name` | Name of the database | `mem0` |
| `table_name` | Name of the table | `mem0_table` |
| `embedding_model_dims` | Dimensions of the embedding model | `1536` |
| `metric_type` | Distance metric for similarity search | `L2` |
### Distance Metrics
The following distance metrics are supported:
- `L2`: Euclidean distance (default)
- `IP`: Inner product
- `COSINE`: Cosine similarity
### Index Configuration
The vector index is automatically configured with the following HNSW parameters:
- `m`: 16 (number of connections per element)
- `efconstruction`: 200 (size of the dynamic candidate list)
- `auto_build`: true (automatically build index)
- `auto_build_index_policy`: Incremental build with 10000 rows increment
+49
View File
@@ -0,0 +1,49 @@
# MongoDB
[MongoDB](https://www.mongodb.com/) is a versatile document database that supports vector search capabilities, allowing for efficient high-dimensional similarity searches over large datasets with robust scalability and performance.
## Usage
```python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "sk-xx"
config = {
"vector_store": {
"provider": "mongodb",
"config": {
"db_name": "mem0-db",
"collection_name": "mem0-collection",
"user": "my-user",
"password": "my-password",
}
}
}
m = Memory.from_config(config)
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
## Config
Here are the parameters available for configuring MongoDB:
| Parameter | Description | Default Value |
| --- | --- | --- |
| db_name | Name of the MongoDB database | `"mem0_db"` |
| collection_name | Name of the MongoDB collection | `"mem0_collection"` |
| embedding_model_dims | Dimensions of the embedding vectors | `1536` |
| user | MongoDB user for authentication | `None` |
| password | Password for the MongoDB user | `None` |
| host | MongoDB host | `"localhost"` |
| port | MongoDB port | `27017` |
> **Note**: `user` and `password` must either be provided together or omitted together.
+1
View File
@@ -23,6 +23,7 @@ See the list of supported vector databases below.
<Card title="Upstash Vector" href="/components/vectordbs/dbs/upstash-vector"></Card>
<Card title="Milvus" href="/components/vectordbs/dbs/milvus"></Card>
<Card title="Pinecone" href="/components/vectordbs/dbs/pinecone"></Card>
<Card title="MongoDB" href="/components/vectordbs/dbs/mongodb"></Card>
<Card title="Azure" href="/components/vectordbs/dbs/azure"></Card>
<Card title="Redis" href="/components/vectordbs/dbs/redis"></Card>
<Card title="Elasticsearch" href="/components/vectordbs/dbs/elasticsearch"></Card>
@@ -0,0 +1,152 @@
---
title: Add Memory
description: Add memory into the Mem0 platform by storing user-assistant interactions and facts for later retrieval.
icon: "plus"
iconType: "solid"
---
## Overview
The `add` operation is how you store memory into Mem0. Whether you're working with a chatbot, a voice assistant, or a multi-agent system, this is the entry point to create long-term memory.
Memories typically come from a **user-assistant interaction** and Mem0 handles the extraction, transformation, and storage for you.
Mem0 offers two implementation flows:
- **Mem0 Platform** (Managed, scalable, with dashboard + API)
- **Mem0 Open Source** (Lightweight, fully local, flexible SDKs)
Each supports the same core memory operations, but with slightly different setup. Below, we walk through examples for both.
## Architecture
<Frame caption="Architecture diagram illustrating the process of adding memories.">
<img src="../../images/add_architecture.png" />
</Frame>
When you call `add`, Mem0 performs the following steps under the hood:
1. **Information Extraction**
The input messages are passed through an LLM that extracts key facts, decisions, preferences, or events worth remembering.
2. **Conflict Resolution**
Mem0 compares the new memory against existing ones to detect duplication or contradiction and handles updates accordingly.
3. **Memory Storage**
The result is stored in a vector database (for semantic search) and optionally in a graph structure (for relationship mapping).
You don’t need to handle any of this manually, Mem0 takes care of it with a single API call or SDK method.
---
## Example: Mem0 Platform
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
messages = [
{"role": "user", "content": "I'm planning a trip to Tokyo next month."},
{"role": "assistant", "content": "Great! I’ll remember that for future suggestions."}
]
client.add(
messages=messages,
user_id="alice",
version="v2"
)
```
```javascript JavaScript
import { MemoryClient } from "mem0ai";
const client = new MemoryClient({apiKey: "your-api-key"});
const messages = [
{ role: "user", content: "I'm planning a trip to Tokyo next month." },
{ role: "assistant", content: "Great! I’ll remember that for future suggestions." }
];
await client.add({
messages,
user_id: "alice",
version: "v2"
});
```
</CodeGroup>
---
## Example: Mem0 Open Source
<CodeGroup>
```python Python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your-api-key"
m = Memory()
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
# Store inferred memories (default behavior)
result = m.add(messages, user_id="alice", metadata={"category": "movie_recommendations"})
# Optionally store raw messages without inference
result = m.add(messages, user_id="alice", metadata={"category": "movie_recommendations"}, infer=False)
```
```javascript JavaScript
import { Memory } from 'mem0ai/oss';
const memory = new Memory();
const messages = [
{
role: "user",
content: "I like to drink coffee in the morning and go for a walk"
}
];
const result = memory.add(messages, {
userId: "alice",
metadata: { category: "preferences" }
});
```
</CodeGroup>
---
## When Should You Add Memory?
Add memory whenever your agent learns something useful:
- A new user preference is shared
- A decision or suggestion is made
- A goal or task is completed
- A new entity is introduced
- A user gives feedback or clarification
Storing this context allows the agent to reason better in future interactions.
### More Details
For full list of supported fields, required formats, and advanced options, see the
[Add Memory API Reference](/api-reference/memory/add-memories).
---
## Need help?
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx"/>
@@ -0,0 +1,141 @@
---
title: Delete Memory
description: Remove memories from Mem0 either individually, in bulk, or via filters.
icon: "trash"
iconType: "solid"
---
## Overview
Memories can become outdated, irrelevant, or need to be removed for privacy or compliance reasons. Mem0 offers flexible ways to delete memory:
1. **Delete a Single Memory**: Using a specific memory ID
2. **Batch Delete**: Delete multiple known memory IDs (up to 1000)
3. **Filtered Delete**: Delete memories matching a filter (e.g., `user_id`, `metadata`, `run_id`)
This page walks through code example for each method.
## Use Cases
- Forget a user’s past preferences by request
- Remove outdated or incorrect memory entries
- Clean up memory after session expiration
- Comply with data deletion requests (e.g., GDPR)
---
## 1. Delete a Single Memory by ID
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
memory_id = "your_memory_id"
client.delete(memory_id=memory_id)
```
```javascript JavaScript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
client.delete("your_memory_id")
.then(result => console.log(result))
.catch(error => console.error(error));
```
</CodeGroup>
---
## 2. Batch Delete Multiple Memories
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
delete_memories = [
{"memory_id": "id1"},
{"memory_id": "id2"}
]
response = client.batch_delete(delete_memories)
print(response)
```
```javascript JavaScript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
const deleteMemories = [
{ memory_id: "id1" },
{ memory_id: "id2" }
];
client.batchDelete(deleteMemories)
.then(response => console.log('Batch delete response:', response))
.catch(error => console.error(error));
```
</CodeGroup>
---
## 3. Delete Memories by Filter (e.g., user_id)
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
# Delete all memories for a specific user
client.delete_all(user_id="alice")
```
```javascript JavaScript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
client.deleteAll({ user_id: "alice" })
.then(result => console.log(result))
.catch(error => console.error(error));
```
</CodeGroup>
You can also filter by other parameters such as:
- `agent_id`
- `run_id`
- `metadata` (as JSON string)
---
## Key Differences
| Method | Use When | IDs Needed | Filters |
|----------------------|-------------------------------------------|------------|----------|
| `delete(memory_id)` | You know exactly which memory to remove | ✔ | ✘ |
| `batch_delete([...])`| You have a known list of memory IDs | ✔ | ✘ |
| `delete_all(...)` | You want to delete by user/agent/run/etc | ✘ | ✔ |
### More Details
For request/response schema and additional filtering options, see:
- [Delete Memory API Reference](/api-reference/memory/delete-memory)
- [Batch Delete API Reference](/api-reference/memory/batch-delete)
- [Delete Memories by Filter Reference](/api-reference/memory/delete-memories)
You’ve now seen how to add, search, update, and delete memories in Mem0.
---
## Need help?
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx"/>
@@ -0,0 +1,124 @@
---
title: Search Memory
description: Retrieve relevant memories from Mem0 using powerful semantic and filtered search capabilities.
icon: "magnifying-glass"
iconType: "solid"
---
## Overview
The `search` operation allows you to retrieve relevant memories based on a natural language query and optional filters like user ID, agent ID, categories, and more. This is the foundation of giving your agents memory-aware behavior.
Mem0 supports:
- Semantic similarity search
- Metadata filtering (with advanced logic)
- Reranking and thresholds
- Cross-agent, multi-session context resolution
This applies to both:
- **Mem0 Platform** (hosted API with full-scale features)
- **Mem0 Open Source** (local-first with LLM inference and local vector DB)
## Architecture
<Frame caption="Architecture diagram illustrating the memory search process.">
<img src="../../images/search_architecture.png" />
</Frame>
The search flow follows these steps:
1. **Query Processing**
An LLM refines and optimizes your natural language query.
2. **Vector Search**
Semantic embeddings are used to find the most relevant memories using cosine similarity.
3. **Filtering & Ranking**
Logical and comparison-based filters are applied. Memories are scored, filtered, and optionally reranked.
4. **Results Delivery**
Relevant memories are returned with associated metadata and timestamps.
---
## Example: Mem0 Platform
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
query = "What do you know about me?"
filters = {
"OR": [
{"user_id": "alice"},
{"agent_id": {"in": ["travel-assistant", "customer-support"]}}
]
}
results = client.search(query, version="v2", filters=filters)
```
```javascript JavaScript
import { MemoryClient } from "mem0ai";
const client = new MemoryClient({apiKey: "your-api-key"});
const query = "I'm craving some pizza. Any recommendations?";
const filters = {
AND: [
{ user_id: "alice" }
]
};
const results = await client.search(query, {
version: "v2",
filters
});
```
</CodeGroup>
---
## Example: Mem0 Open Source
<CodeGroup>
```python Python
from mem0 import Memory
m = Memory()
related_memories = m.search("Should I drink coffee or tea?", user_id="alice")
```
```javascript JavaScript
import { Memory } from 'mem0ai/oss';
const memory = new Memory();
const relatedMemories = memory.search("Should I drink coffee or tea?", { userId: "alice" });
```
</CodeGroup>
---
## Tips for Better Search
- Use descriptive natural queries (Mem0 can interpret intent)
- Apply filters for scoped, faster lookup
- Use `version: "v2"` for enhanced results
- Consider wildcard filters (e.g., `run_id: "*"`) for broader matches
- Tune with `top_k`, `threshold`, or `rerank` if needed
### More Details
For the full list of filter logic, comparison operators, and optional search parameters, see the
[Search Memory API Reference](/api-reference/memory/v2-search-memories).
---
## Need help?
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx"/>
@@ -0,0 +1,117 @@
---
title: Update Memory
description: Modify an existing memory by updating its content or metadata.
icon: "pencil"
iconType: "solid"
---
## Overview
User preferences, interests, and behaviors often evolve over time. The `update` operation lets you revise a stored memory, whether it's updating facts and memories, rephrasing a message, or enriching metadata.
Mem0 supports both:
- **Single Memory Update** for one specific memory using its ID
- **Batch Update** for updating many memories at once (up to 1000)
This guide includes usage for both single update and batch update of memories through **Mem0 Platform**
## Use Cases
- Refine a vague or incorrect memory after a correction
- Add or edit memory with new metadata (e.g., categories, tags)
- Evolve factual knowledge as the user’s profile changes
- A user profile evolves: “I love spicy food” → later says “Actually, I can’t handle spicy food.”
Updating memory ensures your agents remain accurate, adaptive, and personalized.
---
## Update Memory
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
memory_id = "your_memory_id"
client.update(
memory_id=memory_id,
text="Updated memory content about the user",
metadata={"category": "profile-update"}
)
```
```javascript JavaScript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
const memory_id = "your_memory_id";
client.update(memory_id, {
text: "Updated memory content about the user",
metadata: { category: "profile-update" }
})
.then(result => console.log(result))
.catch(error => console.error(error));
```
</CodeGroup>
---
## Batch Update
Update up to 1000 memories in one call.
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
update_memories = [
{"memory_id": "id1", "text": "Watches football"},
{"memory_id": "id2", "text": "Likes to travel"}
]
response = client.batch_update(update_memories)
print(response)
```
```javascript JavaScript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
const updateMemories = [
{ memoryId: "id1", text: "Watches football" },
{ memoryId: "id2", text: "Likes to travel" }
];
client.batchUpdate(updateMemories)
.then(response => console.log('Batch update response:', response))
.catch(error => console.error(error));
```
</CodeGroup>
---
## Tips
- You can update both `text` and `metadata` in the same call.
- Use `batchUpdate` when you're applying similar corrections at scale.
- If memory is marked `immutable`, it must first be deleted and re-added.
- Combine this with feedback mechanisms (e.g., user thumbs-up/down) to self-improve memory.
### More Details
Refer to the full [Update Memory API Reference](/api-reference/memory/update-memory) and [Batch Update Reference](/api-reference/memory/batch-update) for schema and advanced fields.
---
## Need help?
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx"/>
+27 -40
View File
@@ -19,10 +19,10 @@
"tab": "Documentation",
"groups": [
{
"group": "Get Started",
"group": "Getting Started",
"icon": "rocket",
"pages": [
"overview",
"what-is-mem0",
"quickstart",
"faqs"
]
@@ -32,7 +32,16 @@
"icon": "brain",
"pages": [
"core-concepts/memory-types",
"core-concepts/memory-operations"
{
"group": "Memory Operations",
"icon": "gear",
"pages": [
"core-concepts/memory-operations/add",
"core-concepts/memory-operations/search",
"core-concepts/memory-operations/update",
"core-concepts/memory-operations/delete"
]
}
]
},
{
@@ -46,21 +55,19 @@
"icon": "star",
"pages": [
"platform/features/platform-overview",
"platform/features/contextual-add",
"platform/features/async-client",
"platform/features/advanced-retrieval",
"platform/features/criteria-retrieval",
"platform/features/contextual-add",
"platform/features/multimodal-support",
"platform/features/timestamp",
"platform/features/selective-memory",
"platform/features/custom-categories",
"platform/features/custom-instructions",
"platform/features/direct-import",
"platform/features/async-client",
"platform/features/memory-export",
"platform/features/timestamp",
"platform/features/expiration-date",
"platform/features/webhooks",
"platform/features/graph-memory",
"platform/features/feedback-mechanism",
"platform/features/expiration-date"
"platform/features/feedback-mechanism"
]
}
]
@@ -69,12 +76,12 @@
"group": "Open Source",
"icon": "code-branch",
"pages": [
"open-source/quickstart",
"open-source/overview",
"open-source/python-quickstart",
"open-source/node-quickstart",
{
"group": "Features",
"icon": "wrench",
"icon": "star",
"pages": [
"open-source/features/async-memory",
"open-source/features/openai_compatibility",
@@ -117,7 +124,8 @@
"components/llms/models/xAI",
"components/llms/models/sarvam",
"components/llms/models/lmstudio",
"components/llms/models/langchain"
"components/llms/models/langchain",
"components/llms/models/vllm"
]
}
]
@@ -137,6 +145,7 @@
"components/vectordbs/dbs/pgvector",
"components/vectordbs/dbs/milvus",
"components/vectordbs/dbs/pinecone",
"components/vectordbs/dbs/mongodb",
"components/vectordbs/dbs/azure",
"components/vectordbs/dbs/redis",
"components/vectordbs/dbs/elasticsearch",
@@ -145,7 +154,8 @@
"components/vectordbs/dbs/vertex_ai",
"components/vectordbs/dbs/weaviate",
"components/vectordbs/dbs/faiss",
"components/vectordbs/dbs/langchain"
"components/vectordbs/dbs/langchain",
"components/vectordbs/dbs/baidu"
]
}
]
@@ -191,7 +201,8 @@
"icon": "square-terminal",
"pages": [
"openmemory/overview",
"openmemory/quickstart"
"openmemory/quickstart",
"openmemory/integrations"
]
},
{
@@ -235,6 +246,7 @@
"icon": "plug",
"pages": [
"integrations",
"integrations/agentops",
"integrations/vercel-ai-sdk",
"integrations/flowise",
"integrations/crewai",
@@ -346,31 +358,6 @@
]
}
]
},
{
"anchor": "Your Dashboard",
"href": "https://app.mem0.ai",
"icon": "chart-simple"
},
{
"anchor": "Demo",
"href": "https://mem0.dev/demo",
"icon": "play"
},
{
"anchor": "Discord",
"href": "https://mem0.dev/DiD",
"icon": "discord"
},
{
"anchor": "GitHub",
"href": "https://github.com/mem0ai/mem0",
"icon": "github"
},
{
"anchor": "Support",
"href": "mailto:founders@mem0.ai",
"icon": "envelope"
}
]
},
+1 -1
View File
@@ -22,7 +22,7 @@ os.environ["OPENAI_API_KEY"] = "<your-openai-api-key>"
llm = OpenAI(model="gpt-4o")
```
Initialize the Mem0 client. You can find your API key [here](https://app.mem0.ai/dashboard/). Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/quickstart).
Initialize the Mem0 client. You can find your API key [here](https://app.mem0.ai/dashboard/api-keys). Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/overview).
```python
os.environ["MEM0_API_KEY"] = "<your-mem0-api-key>"
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

+18
View File
@@ -18,6 +18,23 @@ Here are the available integrations for Mem0:
## Integrations
<CardGroup cols={2}>
<Card
title="AgentOps"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="25"
height="26"
viewBox="0 0 30 36"
fill="none"
>
<path d="M10.4659 6.47277C10.45 6.37428 10.4381 6.27986 10.4303 6.18101L10.4285 6.16388C10.4212 6.09482 10.414 6.02566 10.4106 5.95626L1.18538 21.8752C0.505422 23.0493 0.323356 24.4208 0.675227 25.7289C0.849119 26.3869 1.14971 26.9859 1.55323 27.5098C1.95675 28.0338 2.46282 28.4751 3.05175 28.8143C3.83464 29.2675 4.70856 29.5 5.59028 29.5C6.03318 29.5 6.4798 29.4408 6.91899 29.3226C8.23581 28.972 9.3349 28.1326 10.0152 26.9545L15.9268 16.749V16.7449L16.5001 15.7637L17.6431 13.7936L16.5001 11.8234L15.9309 10.8381L15.9268 10.8341L13.7836 7.13406C13.6651 6.933 13.5741 6.72418 13.5109 6.51165C13.2817 5.80223 13.3292 5.04172 13.6097 4.37599L13.8115 4.02535C14.3532 3.09155 15.31 2.53987 16.3184 2.47692C16.3738 2.46915 16.4251 2.46915 16.4804 2.46915C16.5421 2.46915 16.6038 2.47257 16.6654 2.47599L16.6822 2.47692C17.6906 2.53987 18.6474 3.09155 19.1892 4.02535L21.2216 7.52838L21.8146 8.55289L21.8421 8.60399L30.1024 22.8601C30.5174 23.5814 30.6281 24.4167 30.4148 25.2205C30.1975 26.0244 29.6832 26.6942 28.9598 27.1081C28.2364 27.5258 27.3977 27.6361 26.5911 27.4195C25.7844 27.2066 25.1123 26.6905 24.6968 25.9696L18.2119 14.7788L17.069 16.7449L22.9847 26.9545C23.6646 28.1326 24.7641 28.972 26.0809 29.3226C26.5197 29.4408 26.9626 29.5 27.4096 29.5C28.2914 29.5 29.1612 29.2675 29.9482 28.8143C31.1264 28.1367 31.9728 27.0411 32.3247 25.7289C32.6766 24.4208 32.4949 23.0493 31.8145 21.8752L21.1261 3.43034C20.7029 2.51617 20.0033 1.72011 19.0621 1.18027C18.5281 0.877027 17.9708 0.675975 17.3975 0.581189C17.3027 0.565268 17.2076 0.549717 17.1129 0.537868C17.0099 0.52602 16.9074 0.518244 16.8045 0.510469C16.6027 0.498621 16.3972 0.494548 16.1914 0.510469C16.0885 0.518244 15.9859 0.52639 15.883 0.537868C15.795 0.54887 15.7067 0.563384 15.6187 0.577852L15.5984 0.581189C15.0291 0.675605 14.4673 0.876657 13.9375 1.18027C12.9885 1.72789 12.2766 2.53579 11.8537 3.46181C11.7742 3.63473 11.707 3.81282 11.6471 3.99314C11.6361 4.02668 11.6269 4.06051 11.6177 4.09435C11.612 4.11503 11.6064 4.13579 11.6003 4.15642C11.5624 4.28601 11.5275 4.41634 11.4996 4.54853C11.4885 4.60231 11.4794 4.65668 11.4703 4.71111L11.4666 4.73329C11.4443 4.86399 11.4264 4.99543 11.4145 5.12762C11.4093 5.18686 11.4045 5.24573 11.4012 5.30534C11.3934 5.44567 11.3923 5.58637 11.3963 5.72744C11.3969 5.74403 11.3962 5.76062 11.3956 5.7772C11.3949 5.79616 11.3942 5.81512 11.3952 5.83407C11.3952 5.86184 11.3952 5.88924 11.3993 5.92071C11.3998 5.9291 11.4006 5.93736 11.4014 5.94564C11.402 5.95125 11.4026 5.95687 11.403 5.96255C11.4045 5.98181 11.4064 6.00106 11.4082 6.02031C11.4097 6.03577 11.4109 6.05122 11.4122 6.06674C11.4142 6.09134 11.4163 6.11621 11.419 6.14139L11.4428 6.32282C11.4506 6.38983 11.4625 6.46092 11.4744 6.52757C11.5063 6.68863 11.5468 6.84896 11.5936 7.0078C11.5944 7.0102 11.5949 7.0127 11.5955 7.0152C11.5958 7.01662 11.5961 7.01804 11.5965 7.01944C11.5967 7.02051 11.597 7.02157 11.5974 7.02261C11.6483 7.19293 11.7081 7.36177 11.7787 7.52838C11.8619 7.72943 11.9607 7.92641 12.0715 8.11932L12.3245 8.5566V8.56067L12.4984 8.85614L12.7199 9.24232H12.7239L12.728 9.25417L14.7802 12.7927V12.7968L14.7883 12.805V12.809L15.3576 13.7943L14.7883 14.7796L8.30344 25.9703C7.88431 26.6912 7.21216 27.2077 6.40921 27.4202C6.14019 27.4913 5.86338 27.5306 5.59474 27.5306C5.053 27.5306 4.51906 27.3888 4.04085 27.1089C3.31705 26.6953 2.79909 26.0251 2.58581 25.2213C2.36845 24.4174 2.47917 23.5821 2.89829 22.8609L11.1585 8.60473L11.186 8.56141V8.55734C11.1266 8.45478 11.0753 8.35629 11.024 8.25409C11.0105 8.22496 10.9969 8.19611 10.9834 8.16739C10.9458 8.08735 10.9086 8.00836 10.8739 7.92715C10.8718 7.92504 10.8708 7.92194 10.8698 7.91887C10.8688 7.91602 10.8679 7.91319 10.8661 7.91123V7.90346C10.8423 7.8483 10.8186 7.79311 10.7989 7.73795C10.7476 7.60799 10.7041 7.47803 10.6644 7.3477C10.6012 7.15479 10.5536 6.96152 10.518 6.76861C10.4942 6.67012 10.4786 6.5757 10.4667 6.47684C10.4667 6.47684 10.47 6.47684 10.4659 6.47277Z" fill="currentColor"></path>
</svg>
}
href="/integrations/agentops"
>
Monitor and analyze Mem0 operations with comprehensive AI agent analytics and LLM observability.
</Card>
<Card
title="LangChain"
icon={
@@ -305,6 +322,7 @@ Here are the available integrations for Mem0:
>
Build autonomous agents with memory using Agno framework.
</Card>
<Card
title="Keywords AI"
icon={
+172
View File
@@ -0,0 +1,172 @@
---
title: AgentOps
---
<Snippet file="paper-release.mdx" />
Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [AgentOps](https://agentops.ai), a comprehensive monitoring and analytics platform for AI agents. This integration enables automatic tracking and analysis of memory operations, providing insights into agent performance and memory usage patterns.
## Overview
1. Automatic monitoring of Mem0 operations and performance metrics
2. Real-time tracking of memory add, search, and retrieval operations
3. Analytics dashboard with memory usage patterns and insights
4. Error tracking and debugging capabilities for memory operations
## Prerequisites
Before setting up Mem0 with AgentOps, ensure you have:
1. Installed the required packages:
```bash
pip install mem0ai agentops
```
2. Valid API keys:
- [AgentOps API Key](https://app.agentops.ai/dashboard/api-keys)
- OpenAI API Key (for LLM operations)
- [Mem0 API Key](https://app.mem0.ai/dashboard/api-keys) (optional, for cloud operations)
## Basic Integration Example
The following example demonstrates how to integrate Mem0 with AgentOps monitoring for comprehensive memory operation tracking:
```python
#Import the required libraries for local memory management with Mem0
from mem0 import Memory, AsyncMemory
import os
import asyncio
import logging
from dotenv import load_dotenv
import agentops
#Set up environment variables for API keys
os.environ["AGENTOPS_API_KEY"] = os.getenv("AGENTOPS_API_KEY")
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
#Set up the configuration for local memory storage and define sample user data.
local_config = {
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini",
"temperature": 0.1,
"max_tokens": 2000,
},
}
}
user_id = "alice_demo"
agent_id = "assistant_demo"
run_id = "session_001"
sample_messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
{
"role": "assistant",
"content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future.",
},
]
sample_preferences = [
"I prefer dark roast coffee over light roast",
"I exercise every morning at 6 AM",
"I'm vegetarian and avoid all meat products",
"I love reading science fiction novels",
"I work in software engineering",
]
#This function demonstrates sequential memory operations using the synchronous Memory class
def demonstrate_sync_memory(local_config, sample_messages, sample_preferences, user_id):
"""
Demonstrate synchronous Memory class operations.
"""
agentops.start_trace("mem0_memory_example", tags=["mem0_memory_example"])
try:
memory = Memory.from_config(local_config)
result = memory.add(
sample_messages, user_id=user_id, metadata={"category": "movie_preferences", "session": "demo"}
)
for i, preference in enumerate(sample_preferences):
result = memory.add(preference, user_id=user_id, metadata={"type": "preference", "index": i})
search_queries = [
"What movies does the user like?",
"What are the user's food preferences?",
"When does the user exercise?",
]
for query in search_queries:
results = memory.search(query, user_id=user_id)
if results and "results" in results:
for j, result in enumerate(results):
print(f"Result {j+1}: {result.get('memory', 'N/A')}")
else:
print("No results found")
all_memories = memory.get_all(user_id=user_id)
if all_memories and "results" in all_memories:
print(f"Total memories: {len(all_memories['results'])}")
delete_all_result = memory.delete_all(user_id=user_id)
print(f"Delete all result: {delete_all_result}")
agentops.end_trace(end_state="success")
except Exception as e:
agentops.end_trace(end_state="error")
# Execute sync demonstrations
demonstrate_sync_memory(local_config, sample_messages, sample_preferences, user_id)
```
For detailed information on this integration, refer to the official [Agentops Mem0 integration documentation](https://docs.agentops.ai/v2/integrations/mem0).
## Key Features
### 1. Automatic Operation Tracking
AgentOps automatically monitors all Mem0 operations:
- **Memory Operations**: Track add, search, get_all, delete operations and much more
- **Performance Metrics**: Monitor response times and success rates
- **Error Tracking**: Capture and analyze operation failures
### 2. Real-time Analytics Dashboard
Access comprehensive analytics through the AgentOps dashboard:
- **Usage Patterns**: Visualize memory usage trends over time
- **User Behavior**: Analyze how different users interact with memory
- **Performance Insights**: Identify bottlenecks and optimization opportunities
### 3. Session Management
Organize your monitoring with structured sessions:
- **Session Tracking**: Group related operations into logical sessions
- **Success/Failure Rates**: Track session outcomes for reliability monitoring
- **Custom Metadata**: Add context to sessions for better analysis
## Best Practices
1. **Initialize Early**: Always initialize AgentOps before importing Mem0 classes
2. **Session Management**: Use meaningful session names and end sessions appropriately
3. **Error Handling**: Wrap operations in try-catch blocks and report failures
4. **Tagging**: Use tags to organize different types of memory operations
5. **Environment Separation**: Use different projects or tags for dev/staging/prod
## Help & Resources
- [AgentOps Documentation](https://docs.agentops.ai/)
- [AgentOps Dashboard](https://app.agentops.ai/)
- [Mem0 Platform](https://app.mem0.ai/)
<Snippet file="get-help.mdx" />
+1 -1
View File
@@ -63,7 +63,7 @@ context = {
Set your Mem0 OSS by providing configuration details:
<Note type="info">
To know more about Mem0 OSS, read [Mem0 OSS Quickstart](https://docs.mem0.ai/open-source/quickstart).
To know more about Mem0 OSS, read [Mem0 OSS Quickstart](https://docs.mem0.ai/open-source/overview).
</Note>
```python
+77 -5
View File
@@ -1,7 +1,7 @@
---
title: Overview
description: 'Enhance your memory system with graph-based knowledge representation and retrieval'
icon: "database"
icon: "info"
iconType: "solid"
---
@@ -238,16 +238,24 @@ The Mem0's graph supports the following operations:
### Add Memories
<Note>
If you are using Mem0 with Graph Memory, it is recommended to pass `user_id`. Use `userId` in NodeSDK.
Mem0 with Graph Memory supports both "user_id" and "agent_id" parameters. You can use either or both to organize your memories. Use "userId" and "agentId" in NodeSDK.
</Note>
<CodeGroup>
```python Python
# Using only user_id
m.add("I like pizza", user_id="alice")
# Using both user_id and agent_id
m.add("I like pizza", user_id="alice", agent_id="food-assistant")
```
```typescript TypeScript
// Using only userId
memory.add("I like pizza", { userId: "alice" });
// Using both userId and agentId
memory.add("I like pizza", { userId: "alice", agentId: "food-assistant" });
```
```json Output
@@ -260,11 +268,19 @@ memory.add("I like pizza", { userId: "alice" });
<CodeGroup>
```python Python
# Get all memories for a user
m.get_all(user_id="alice")
# Get all memories for a specific agent belonging to a user
m.get_all(user_id="alice", agent_id="food-assistant")
```
```typescript TypeScript
// Get all memories for a user
memory.getAll({ userId: "alice" });
// Get all memories for a specific agent belonging to a user
memory.getAll({ userId: "alice", agentId: "food-assistant" });
```
```json Output
@@ -277,7 +293,8 @@ memory.getAll({ userId: "alice" });
'metadata': None,
'created_at': '2024-08-20T14:09:27.588719-07:00',
'updated_at': None,
'user_id': 'alice'
'user_id': 'alice',
'agent_id': 'food-assistant'
}
],
'entities': [
@@ -295,11 +312,19 @@ memory.getAll({ userId: "alice" });
<CodeGroup>
```python Python
# Search memories for a user
m.search("tell me my name.", user_id="alice")
# Search memories for a specific agent belonging to a user
m.search("tell me my name.", user_id="alice", agent_id="food-assistant")
```
```typescript TypeScript
// Search memories for a user
memory.search("tell me my name.", { userId: "alice" });
// Search memories for a specific agent belonging to a user
memory.search("tell me my name.", { userId: "alice", agentId: "food-assistant" });
```
```json Output
@@ -312,7 +337,8 @@ memory.search("tell me my name.", { userId: "alice" });
'metadata': None,
'created_at': '2024-08-20T14:09:27.588719-07:00',
'updated_at': None,
'user_id': 'alice'
'user_id': 'alice',
'agent_id': 'food-assistant'
}
],
'entities': [
@@ -331,11 +357,19 @@ memory.search("tell me my name.", { userId: "alice" });
<CodeGroup>
```python Python
# Delete all memories for a user
m.delete_all(user_id="alice")
# Delete all memories for a specific agent belonging to a user
m.delete_all(user_id="alice", agent_id="food-assistant")
```
```typescript TypeScript
// Delete all memories for a user
memory.deleteAll({ userId: "alice" });
// Delete all memories for a specific agent belonging to a user
memory.deleteAll({ userId: "alice", agentId: "food-assistant" });
```
</CodeGroup>
@@ -516,6 +550,44 @@ memory.search("Who is spiderman?", { userId: "alice123" });
> **Note:** The Graph Memory implementation is not standalone. You will be adding/retrieving memories to the vector store and the graph store simultaneously.
## Using Multiple Agents with Graph Memory
When working with multiple agents, you can use the "agent_id" parameter to organize memories by both user and agent. This allows you to:
1. Create agent-specific knowledge graphs
2. Share common knowledge between agents
3. Isolate sensitive or specialized information to specific agents
### Example: Multi-Agent Setup
<CodeGroup>
```python Python
# Add memories for different agents
m.add("I prefer Italian cuisine", user_id="bob", agent_id="food-assistant")
m.add("I'm allergic to peanuts", user_id="bob", agent_id="health-assistant")
m.add("I live in Seattle", user_id="bob") # Shared across all agents
# Search within specific agent context
food_preferences = m.search("What food do I like?", user_id="bob", agent_id="food-assistant")
health_info = m.search("What are my allergies?", user_id="bob", agent_id="health-assistant")
location = m.search("Where do I live?", user_id="bob") # Searches across all agents
```
```typescript TypeScript
// Add memories for different agents
memory.add("I prefer Italian cuisine", { userId: "bob", agentId: "food-assistant" });
memory.add("I'm allergic to peanuts", { userId: "bob", agentId: "health-assistant" });
memory.add("I live in Seattle", { userId: "bob" }); // Shared across all agents
// Search within specific agent context
const foodPreferences = memory.search("What food do I like?", { userId: "bob", agentId: "food-assistant" });
const healthInfo = memory.search("What are my allergies?", { userId: "bob", agentId: "health-assistant" });
const location = memory.search("Where do I live?", { userId: "bob" }); // Searches across all agents
```
</CodeGroup>
If you want to use a managed version of Mem0, please check out [Mem0](https://mem0.dev/pd). If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx" />
<Snippet file="get-help.mdx" />
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Node SDK
title: Node SDK Quickstart
description: 'Get started with Mem0 quickly!'
icon: "node"
iconType: "solid"
@@ -1,6 +1,6 @@
---
title: Overview
icon: "info"
icon: "eye"
iconType: "solid"
---
+2 -2
View File
@@ -1,5 +1,5 @@
---
title: Python SDK
title: Python SDK Quickstart
description: 'Get started with Mem0 quickly!'
icon: "python"
iconType: "solid"
@@ -513,7 +513,7 @@ chat_completion = client.chat.completions.create(
## APIs
Get started with using Mem0 APIs in your applications. For more details, refer to the [Platform](/platform/quickstart.mdx).
Get started with using Mem0 APIs in your applications. For more details, refer to the [Platform](../platform/quickstart).
Here is an example of how to use Mem0 APIs:
+12 -6
View File
@@ -1769,27 +1769,27 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Update a memory\nmemory_id = \"<memory_id>\"\nmessage = \"Your updated memory message here\"\nclient.update(memory_id, message)"
"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)"
},
{
"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// Update a specific memory\nconst memory_id=<memory_id>\nconst message=\"Your updated memory message here\"\nclient.update(memory_id, message)\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// Update a specific memory\nconst memory_id = \"<memory_id>\";\nclient.update(memory_id, { \n text: \"Your updated memory message here\",\n metadata: { category: \"example\" }\n})\n .then(result => console.log(result))\n .catch(error => console.error(error));"
},
{
"lang": "cURL",
"source": "curl --request PUT \\\n --url https://api.mem0.ai/v1/memories/{memory_id}/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\"text\": \"Your updated memory text here\"}'"
"source": "curl --request PUT \\\n --url https://api.mem0.ai/v1/memories/{memory_id}/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\"text\": \"Your updated memory text here\", \"metadata\": {\"category\": \"example\"}}'"
},
{
"lang": "Go",
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/v1/memories/{memory_id}/\"\n\n\tpayload := strings.NewReader(`{\n\t\"text\": \"Your updated memory text here\"\n}`)\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/v1/memories/{memory_id}/\"\n\n\tpayload := strings.NewReader(`{\n\t\"text\": \"Your updated memory text here\",\n\t\"metadata\": {\n\t\t\"category\": \"example\"\n\t}\n}`)\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
},
{
"lang": "PHP",
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/memories/{memory_id}/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n CURLOPT_POSTFIELDS => json_encode({\n \"text\": \"Your updated memory text here\"\n })\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/memories/{memory_id}/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n CURLOPT_POSTFIELDS => json_encode([\n \"text\" => \"Your updated memory text here\",\n \"metadata\" => [\"category\" => \"example\"]\n ])\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
},
{
"lang": "Java",
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/v1/memories/{memory_id}/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body({\"text\": \"Your updated memory text here\"})\n .asString();"
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/v1/memories/{memory_id}/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\\\"text\\\": \\\"Your updated memory text here\\\", \\\"metadata\\\": {\\\"category\\\": \\\"example\\\"}}\")\n .asString();"
}
],
"x-codegen-request-body-name": "data"
@@ -4910,6 +4910,12 @@
"type": "boolean",
"default": false
},
"async_mode": {
"description": "Whether to add the memory completely asynchronously.",
"title": "Async mode",
"type": "boolean",
"default": false
},
"timestamp": {
"description": "The timestamp of the memory. Format: Unix timestamp",
"title": "Timestamp",
+54
View File
@@ -0,0 +1,54 @@
---
title: MCP Client Integration Guide
icon: "plug"
iconType: "solid"
---
## Connecting an MCP Client
Once your OpenMemory server is running locally, you can connect any compatible MCP client to your personal memory stream. This enables a seamless memory layer integration for AI tools and agents.
Ensure the following environment variables are correctly set in your configuration files:
**In `/ui/.env`:**
```env
NEXT_PUBLIC_API_URL=http://localhost:8765
NEXT_PUBLIC_USER_ID=<user-id>
```
**In `/api/.env`:**
```env
OPENAI_API_KEY=sk-xxx
USER=<user-id>
```
These values define where your MCP server is running and which user's memory is accessed.
### MCP Client Setup
Use the following one step command to configure OpenMemory Local MCP to a client. The general command format is as follows:
```bash
npx @openmemory/install local http://localhost:8765/mcp/<client-name>/sse/<user-id> --client <client-name>
```
Replace `<client-name>` with the desired client name and `<user-id>` with the value specified in your environment variables.
### Example Commands for Supported Clients
| Client | Command |
|-------------|---------|
| Claude | `npx install-mcp http://localhost:8765/mcp/claude/sse/<user-id> --client claude` |
| Cursor | `npx install-mcp http://localhost:8765/mcp/cursor/sse/<user-id> --client cursor` |
| Cline | `npx install-mcp http://localhost:8765/mcp/cline/sse/<user-id> --client cline` |
| RooCline | `npx install-mcp http://localhost:8765/mcp/roocline/sse/<user-id> --client roocline` |
| Windsurf | `npx install-mcp http://localhost:8765/mcp/windsurf/sse/<user-id> --client windsurf` |
| Witsy | `npx install-mcp http://localhost:8765/mcp/witsy/sse/<user-id> --client witsy` |
| Enconvo | `npx install-mcp http://localhost:8765/mcp/enconvo/sse/<user-id> --client enconvo` |
| Augment | `npx install-mcp http://localhost:8765/mcp/augment/sse/<user-id> --client augment` |
### What This Does
Running one of the above commands registers the specified MCP client and connects it to your OpenMemory server. This enables the client to stream and store contextual memory for the provided user ID.
The connection status and memory activity can be monitored via the OpenMemory UI at [http://localhost:3000](http://localhost:3000).
+2 -2
View File
@@ -24,7 +24,7 @@ Add shared, persistent, low-friction memory to your MCP-compatible clients in se
Example installation: `npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key`
OpenMemory is a local memory infrastructure powered by Mem0 that lets you carry your memory accross any AI app. It provides a unified memory layer that stays with you, enabling agents and assistants to remember what matters across applications.
OpenMemory is a local memory infrastructure powered by Mem0 that lets you carry your memory across any AI app. It provides a unified memory layer that stays with you, enabling agents and assistants to remember what matters across applications.
<img src="https://github.com/user-attachments/assets/3c701757-ad82-4afa-bfbe-e049c2b4320b" alt="OpenMemory UI" />
@@ -59,7 +59,7 @@ curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh |
```
This will start the OpenMemory server and the OpenMemory UI. Deleting the container will lead to the deletion of the memory store.
We suggest you follow the instructions [here](/openmemory/quickstart#setting-up-openmemory) to set up OpenMemory on your local machine, with more persistant memory store.
We suggest you follow the instructions [here](/openmemory/quickstart#setting-up-openmemory) to set up OpenMemory on your local machine, with more persistent memory store.
## How the OpenMemory MCP Server Works
+1 -1
View File
@@ -150,7 +150,7 @@ pnpm dev
You can configure the MCP client using the following command (replace username with your username):
```bash
npx install-mcp i "http://localhost:8765/mcp/cursor/sse/username" --client cursor
npx @openmemory/install local "http://localhost:8765/mcp/cursor/sse/username" --client cursor
```
The OpenMemory dashboard will be available at http://localhost:3000. From here, you can view and manage your memories, as well as check connection status with your MCP clients.
+144 -74
View File
@@ -6,100 +6,170 @@ iconType: "solid"
<Snippet file="paper-release.mdx" />
Mem0's **Advanced Retrieval** feature delivers superior search results by leveraging state-of-the-art search algorithms. Beyond the default search functionality, Mem0 offers the following advanced retrieval modes:
Mem0’s **Advanced Retrieval** provides additional control over how memories are selected and ranked during search. While the default search uses embedding-based semantic similarity, Advanced Retrieval introduces specialized options to improve recall, ranking accuracy, or filtering based on specific use case.
1. **Keyword Search**
You can enable any of the following modes independently or together:
This mode emphasizes keywords within the query, returning memories that contain the most relevant keywords alongside those from the default search. By default, this parameter is set to `false`. Enabling it enhances search recall, though it may slightly impact precision.
- Keyword Search
- Reranking
- Filtering
```python
client.search(query, keyword_search=True, user_id='alex')
```
Each enhancement can be toggled independently via the `search()` API call. These flags are off by default. These are useful when building agents that require fine-grained retrieval control
**Example:**
```python
# Search for memories about food preferences with keyword search enabled
query = "What are my food preferences?"
results = client.search(query, keyword_search=True, user_id='alex')
## Keyword Search
# Output might include:
# - "Vegetarian. Allergic to nuts." (highly relevant)
# - "Prefers spicy food and enjoys Thai cuisine" (relevant)
# - "Mentioned disliking sea food during restaurant discussion" (keyword match)
Keyword search expands the result set by including memories that contain lexically similar terms and important keywords from the query, even if they're not semantically similar.
# Without keyword_search=True, only the most relevant memories would be returned:
# - "Vegetarian. Allergic to nuts." (highly relevant)
# - "Prefers spicy food and enjoys Thai cuisine" (relevant)
# The keyword-based match about "sea food" would be excluded
```
### When to use
- You are searching for specific entities, names, or technical terms
- When you need comprehensive coverage of a topic
- You want broader recall at the cost of slight noise
2. **Reranking**
Normal retrieval gives you memories sorted in order of their relevancy, but the order may not be perfect. Reranking uses a deep neural network to correct this order, ensuring the most relevant memories appear first. If you are concerned about the order of memories, or want that the best results always comes at top then use reranking. This parameter is set to `false` by default. When enabled, it reorders the memories based on a more accurate relevance score.
### API Usage
```python
results = client.search(
query="What are my food preferences?",
keyword_search=True,
user_id="alex"
)
```
```python
client.search(query, rerank=True, user_id='alex')
```
### Example
**Example:**
```python
# Search for travel plans with reranking enabled
query = "What are my travel plans?"
results = client.search(query, rerank=True, user_id='alex')
**Without keyword_search:**
- "Vegetarian. Allergic to nuts."
- "Prefers spicy food and enjoys Thai cuisine"
# Without reranking, results might be ordered like:
# 1. "Traveled to France last year" (less relevant to current plans)
# 2. "Planning a trip to Japan next month" (more relevant to current plans)
# 3. "Interested in visiting Tokyo restaurants" (relevant to current plans)
**With keyword_search=True:**
- "Vegetarian. Allergic to nuts."
- "Prefers spicy food and enjoys Thai cuisine"
- "Mentioned disliking seafood during restaurant discussion"
# With reranking enabled, results would be reordered:
# 1. "Planning a trip to Japan next month" (most relevant to current plans)
# 2. "Interested in visiting Tokyo restaurants" (highly relevant to current plans)
# 3. "Traveled to France last year" (less relevant to current plans)
```
### Trade-offs
- Increases recall
- May slightly reduce precision
- Adds ~10ms latency
3. **Filtering**
Filtering allows you to narrow down search results by applying specific criterias. This parameter is set to `false` by default. When activated, it significantly enhances search precision by removing irrelevant memories, though it may slightly reduce recall. Filtering is particularly useful when you need highly specific information.
```python
client.search(query, filter_memories=True, user_id='alex')
```
## Reranking
**Example:**
```python
# Search for dietary restrictions with filtering enabled
query = "What are my dietary restrictions?"
results = client.search(query, filter_memories=True, user_id='alex')
Reranking reorders the retrieved results using a deep semantic relevance model that improves the position of the most relevant matches.
# Without filtering, results might include:
# - "Vegetarian. Allergic to nuts." (directly relevant)
# - "I enjoy cooking Italian food on weekends" (somewhat related to food)
# - "Mentioned disliking seafood during restaurant discussion" (food-related)
# - "Prefers to eat dinner at 7pm" (tangentially food-related)
### When to use
- You rely on top-1 or top-N precision
- When result order is critical for your application
- You want consistent result quality across sessions
# With filtering enabled, results would be focused:
# - "Vegetarian. Allergic to nuts." (directly relevant)
# - "Mentioned disliking seafood during restaurant discussion" (relevant restriction)
#
# The filtering process removes memories that are about food preferences
# but not specifically about dietary restrictions
```
### API Usage
```python
results = client.search(
query="What are my travel plans?",
rerank=True,
user_id="alex"
)
```
### Example
**Without rerank:**
1. "Traveled to France last year"
2. "Planning a trip to Japan next month"
3. "Interested in visiting Tokyo restaurants"
**With rerank=True:**
1. "Planning a trip to Japan next month"
2. "Interested in visiting Tokyo restaurants"
3. "Traveled to France last year"
### Trade-offs
- Significantly improves result ordering accuracy
- Ensures most relevant memories appear first
- Adds ~150–200ms latency
- Higher computational cost
## Filtering
Filtering allows you to narrow down search results by applying specific criteria from the set of retrieved memories.
### When to use
- You require highly specific results
- You are working with huge amount of data where noise is problematic
- You require quality over quantity results
### API Usage
```python
results = client.search(
query="What are my dietary restrictions?",
filter_memories=True,
user_id="alex"
)
```
### Example
**Without filtering:**
- "Vegetarian. Allergic to nuts."
- "I enjoy cooking Italian food on weekends"
- "Mentioned disliking seafood during restaurant discussion"
- "Prefers to eat dinner at 7pm"
**With filter_memories=True:**
- "Vegetarian. Allergic to nuts."
- "Mentioned disliking seafood during restaurant discussion"
### Trade-offs
- Maximizes precision (highly relevant results only)
- May reduce recall (filters out some relevant memories)
- Adds ~200-300ms latency
- Best for focused, specific queries
## Combining Modes
You can combine all three retrieval modes as needed:
```python
results = client.search(
query="What are my travel plans?",
keyword_search=True,
rerank=True,
filter_memories=True,
user_id="alex"
)
```
This configuration broadens the candidate pool with keywords, improves ordering via rerank, and finally cuts noise with filtering.
<Note> Combining all modes may add up to ~450ms latency per query. </Note>
## Performance Benchmarks
| **Mode** | **Approximate Latency** |
|------------------|-------------------------|
| `keyword_search` | &lt;10ms |
| `rerank` | 150–200ms |
| `filter_memories`| 200–300ms |
## Best Practices & Limitations
- Use `keyword_search` for broader recall when query context is limited
- Use `rerank` to prioritize the top-most relevant result
- Use `filter_memories` in production-facing or safety-critical agents
- Combine filtering and reranking for maximum accuracy
- Filters may eliminate all results—always handle the empty set gracefully
- Filtering uses LLM evaluation and may be rate-limited depending on your plan
<Note> You can enable or disable these search modes by passing the respective parameters to the `search` method. There is no required sequence for these modes, and any combination can be used based on your needs. </Note>
### Latency Numbers
Here are the typical latency ranges for each search mode:
| **Mode** | **Latency** |
|---------------------|------------------|
| **Keyword Search** | **&lt;10ms** |
| **Reranking** | **150-200ms** |
| **Filtering** | **200-300ms** |
---
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx" />
<Snippet file="get-help.mdx" />
+118 -89
View File
@@ -6,22 +6,57 @@ iconType: "solid"
<Snippet file="paper-release.mdx" />
Mem0's **Criteria Retrieval** feature allows you to retrieve memories based on specific criteria. This is useful when you need to find memories that match certain conditions or criteria, such as emotional content, sentiment, or other custom attributes.
## Setting Up Custom Criteria
Mem0’s **Criteria Retrieval** feature allows you to retrieve memories based on your defined criteria. It goes beyond generic semantic relevance and rank memories based on what matters to your application - emotional tone, intent, behavioral signals, or other custom traits.
You can define custom criteria at the project level, assigning weights to each criterion. These weights will be normalized during memory retrieval.
Instead of just searching for "how similar a memory is to this query?", you can define what *relevance* really means for your project. For example:
- Prioritize joyful memories when building a wellness assistant
- Downrank negative memories in a productivity-focused agent
- Highlight curiosity in a tutoring agent
You define **criteria** - custom attributes like "joy", "negativity", "confidence", or "urgency", and assign weights to control how they influence scoring. When you `search`, Mem0 uses these to re-rank memories that are semantically relevant, favoring those that better match your intent.
This gives you nuanced, intent-aware memory search that adapts to your use case.
## When to Use Criteria Retrieval
Use Criteria Retrieval if:
- You’re building an agent that should react to **emotions** or **behavioral signals**
- You want to guide memory selection based on **context**, not just content
- You have domain-specific signals like "risk", "positivity", "confidence", etc. that shape recall
## Setting Up Criteria Retrieval
Let’s walk through how to configure and use Criteria Retrieval step by step.
### Initialize the Client
Before defining any criteria, make sure to initialize the `MemoryClient` with your credentials and project ID:
```python
from mem0 import MemoryClient
client = MemoryClient(
api_key="mem0_api_key",
org_id="mem0_organization_id",
project_id="mem0_project_id"
api_key="your_mem0_api_key",
org_id="your_organization_id",
project_id="your_project_id"
)
```
# Define custom criteria with weights
### Define Your Criteria
Each criterion includes:
- A `name` (used in scoring)
- A `description` (interpreted by the LLM)
- A `weight` (how much it influences the final score)
```python
retrieval_criteria = [
{
"name": "joy",
@@ -39,19 +74,26 @@ retrieval_criteria = [
"weight": 1
}
]
# Update project with custom criteria
client.update_project(
retrieval_criteria=retrieval_criteria
)
```
## Using Criteria Retrieval
### Apply Criteria to Your Project
Once defined, register the criteria to your project:
```python
client.update_project(retrieval_criteria=retrieval_criteria)
```
Criteria apply project-wide. Once set, they affect all searches using `version="v2"`.
## Example Walkthrough
After setting up your criteria, you can use them to filter and retrieve memories. Here's an example:
### Add Memories
```python
# Add some example memories
messages = [
{"role": "user", "content": "What a beautiful sunny day! I feel so refreshed and ready to take on anything!"},
{"role": "user", "content": "I've always wondered how storms form—what triggers them in the atmosphere?"},
@@ -60,125 +102,112 @@ messages = [
]
client.add(messages, user_id="alice")
```
# Search with criteria-based filtering
### Run Standard vs. Criteria-Based Search
```python
# With criteria
filters = {
"AND": [
{"user_id": "alice"}
]
}
results_with_criteria = client.search(
query="Why I am feeling happy today?",
filters=filters,
query="Why I am feeling happy today?",
filters=filters,
version="v2"
)
# Standard search without criteria filtering
# Without criteria
results_without_criteria = client.search(
query="Why I am feeling happy today?",
query="Why I am feeling happy today?",
user_id="alice"
)
```
## Search Results Comparison
Let's compare the results from criteria-based retrieval versus standard retrieval to see how the emotional criteria affects ranking:
### Compare Results
### Search Results (with Criteria)
```python
[
{
"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day",
"score": 0.666,
...
},
{
"memory": "User finally has time to draw something after a long time",
"score": 0.616,
...
},
{
"memory": "User is happy today",
"score": 0.500,
...
},
{
"memory": "User is curious about how storms form and what triggers them in the atmosphere.",
"score": 0.400,
...
},
{
"memory": "It has been raining for days, making everything feel heavier.",
"score": 0.116,
...
}
{"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day", "score": 0.666, ...},
{"memory": "User finally has time to draw something after a long time", "score": 0.616, ...},
{"memory": "User is happy today", "score": 0.500, ...},
{"memory": "User is curious about how storms form and what triggers them in the atmosphere.", "score": 0.400, ...},
{"memory": "It has been raining for days, making everything feel heavier.", "score": 0.116, ...}
]
```
### Search Results (without Criteria)
```python
[
{
"memory": "User is happy today",
"score": 0.607,
...
},
{
"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day",
"score": 0.512,
...
},
{
"memory": "It has been raining for days, making everything feel heavier.",
"score": 0.4617,
...
},
{
"memory": "User is curious about how storms form and what triggers them in the atmosphere.",
"score": 0.340,
...
},
{
"memory": "User finally has time to draw something after a long time",
"score": 0.336,
...
}
{"memory": "User is happy today", "score": 0.607, ...},
{"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day", "score": 0.512, ...},
{"memory": "It has been raining for days, making everything feel heavier.", "score": 0.4617, ...},
{"memory": "User is curious about how storms form and what triggers them in the atmosphere.", "score": 0.340, ...},
{"memory": "User finally has time to draw something after a long time", "score": 0.336, ...},
]
```
Looking at the example results above, we can see how criteria-based filtering affects the output:
## Search Results Comparison
1. **Memory Ordering**: With criteria, memories with high joy scores (like feeling refreshed and drawing) are ranked higher, while without criteria, the most relevant memory ("User is happy today") comes first.
2. **Score Distribution**: With criteria, scores are more spread out (0.116 to 0.666) and reflect the criteria weights, while without criteria, scores are more clustered (0.336 to 0.607) and based purely on relevance.
3. **Trait Sensitivity**: “Rainy day” content is penalized due to negative tone. “Storm curiosity” is recognized and scored accordingly.
3. **Negative Content**: With criteria, the negative memory about rain has a much lower score (0.116) due to the emotion criteria, while without criteria it maintains a relatively high score (0.4617) due to its relevance.
4. **Curiosity Content**: The storm-related memory gets a moderate score (0.400) with criteria due to the curiosity weighting, while without criteria it's ranked lower (0.340) as it's less relevant to the happiness query.
## Key Differences
## Key Differences vs. Standard Search
1. **Scoring**: With criteria, normalized scores (0-1) are used based on custom criteria weights, while without criteria, standard relevance scoring is used
2. **Ordering**: With criteria, memories are first retrieved by relevance, then criteria-based filtering and prioritization is applied, while without criteria, ordering is solely by relevance
3. **Filtering**: With criteria, post-retrieval filtering based on custom criteria (joy, curiosity, etc.) is available, which isn't available without criteria
| Aspect | Standard Search | Criteria Retrieval |
|-------------------------|--------------------------------------|-------------------------------------------------|
| Ranking Logic | Semantic similarity only | Semantic + LLM-based criteria scoring |
| Control Over Relevance | None | Fully customizable with weighted criteria |
| Memory Reordering | Static based on similarity | Dynamically re-ranked by intent alignment |
| Emotional Sensitivity | No tone or trait awareness | Incorporates emotion, tone, or custom behaviors |
| Version Required | Defaults | `search(version="v2")` |
<Note>
When no custom criteria are specified, the search will default to standard relevance-based retrieval. In this case, results are returned based solely on their relevance to the query, without any additional filtering or prioritization that would normally be applied through criteria.
If no criteria are defined for a project, `version="v2"` behaves like normal search.
</Note>
## Best Practices
- Choose **3–5 criteria** that reflect your application’s intent
- Make descriptions **clear and distinct**, those are interpreted by an LLM
- Use **stronger weights** to amplify impact of important traits
- Avoid redundant or ambiguous criteria (e.g. “positivity” + “joy”)
- Always handle empty result sets in your application logic
## How It Works
1. **Criteria Definition**: Define custom criteria with names, descriptions, and weights
2. **Project Configuration**: Apply these criteria at the project level
3. **Memory Retrieval**: Use v2 search with filters to retrieve memories based on your criteria
4. **Weighted Scoring**: Memories are scored based on the defined criteria weights
1. **Criteria Definition**: Define custom criteria with a name, description, and weight. These describe what matters in a memory (e.g., joy, urgency, empathy).
2. **Project Configuration**: Register these criteria using `update_project()`. They apply at the project level and influence all searches using `version="v2"`.
3. **Memory Retrieval**: When you perform a search with `version="v2"`, Mem0 first retrieves relevant memories based on the query and your defined criteria.
4. **Weighted Scoring**: Each retrieved memory is evaluated and scored against the defined criteria and weights.
This lets you prioritize memories that align with your agent’s goals and not just those that look similar to the query.
<Note>
Criteria retrieval is currently supported only in search v2. Make sure to use `version="v2"` when performing searches with custom criteria.
</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" />
## Summary
- Define what “relevant” means using criteria
- Apply them per project via `update_project()`
- Use `version="v2"` to activate criteria-aware search
- Build agents that reason not just with relevance, but **contextual importance**
---
Need help designing or tuning your criteria?
<Snippet file="get-help.mdx" />
+10 -10
View File
@@ -11,34 +11,34 @@ Learn about the key features and capabilities that make Mem0 a powerful platform
## Core Features
<CardGroup>
<Card title="Advanced Retrieval" icon="magnifying-glass" href="/features/advanced-retrieval">
<Card title="Advanced Retrieval" icon="magnifying-glass" href="advanced-retrieval">
Superior search results using state-of-the-art algorithms, including keyword search, reranking, and filtering capabilities.
</Card>
<Card title="Contextual Add" icon="square-plus" href="/features/contextual-add">
<Card title="Contextual Add" icon="square-plus" href="contextual-add">
Only send your latest conversation history - we automatically retrieve the rest and generate properly contextualized memories.
</Card>
<Card title="Multimodal Support" icon="photo-film" href="/features/multimodal-support">
<Card title="Multimodal Support" icon="photo-film" href="multimodal-support">
Process and analyze various types of content including images.
</Card>
<Card title="Memory Customization" icon="filter" href="/features/selective-memory">
<Card title="Memory Customization" icon="filter" href="selective-memory">
Customize and curate stored memories to focus on relevant information while excluding unnecessary data, enabling improved accuracy, privacy control, and resource efficiency.
</Card>
<Card title="Custom Categories" icon="tags" href="/features/custom-categories">
<Card title="Custom Categories" icon="tags" href="custom-categories">
Create and manage custom categories to organize memories based on your specific needs and requirements.
</Card>
<Card title="Custom Instructions" icon="list-check" href="/features/custom-instructions">
<Card title="Custom Instructions" icon="list-check" href="custom-instructions">
Define specific guidelines for your project to ensure consistent handling of information and requirements.
</Card>
<Card title="Direct Import" icon="message-bot" href="/features/direct-import">
<Card title="Direct Import" icon="message-bot" href="direct-import">
Tailor the behavior of your Mem0 instance with custom prompts for specific use cases or domains.
</Card>
<Card title="Async Client" icon="bolt" href="/features/async-client">
<Card title="Async Client" icon="bolt" href="async-client">
Asynchronous client for non-blocking operations and high concurrency applications.
</Card>
<Card title="Memory Export" icon="file-export" href="/features/memory-export">
<Card title="Memory Export" icon="file-export" href="memory-export">
Export memories in structured formats using customizable Pydantic schemas.
</Card>
<Card title="Graph Memory" icon="graph" href="/features/graph-memory">
<Card title="Graph Memory" icon="graph" href="graph-memory">
Add memories in the form of nodes and edges in a graph database and search for related memories.
</Card>
</CardGroup>
+3 -3
View File
@@ -1,5 +1,5 @@
---
title: Introduction
title: Overview
description: 'Empower your AI applications with long-term memory and personalization'
icon: "eye"
iconType: "solid"
@@ -26,11 +26,11 @@ Mem0 Platform offers a powerful, user-centric solution for AI memory management
## Getting Started
Check out our [Platform Guide](/platform/guide) to start using Mem0 platform quickly.
Check out our [Platform Guide](/platform/quickstart) to start using Mem0 platform quickly.
## Next Steps
- Sign up to the [Mem0 Platform](https://mem0.dev/pd)
- Join our [Discord](https://mem0.dev/Did) or [Slack](https://mem0.dev/slack) with other developers and get support.
- Join our [Discord](https://mem0.dev/Did) with other developers and get support.
We're excited to see what you'll build with Mem0 Platform. Let's create smarter, more personalized AI experiences together!
+64 -9
View File
@@ -1,7 +1,7 @@
---
title: Guide
title: Quickstart
description: 'Get started with Mem0 Platform in minutes'
icon: "book"
icon: "bolt"
iconType: "solid"
---
@@ -349,6 +349,59 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
</CodeGroup>
#### Async Memory Addition
When you set `async_mode=True`, memory processing happens completely asynchronously in the background. This allows for faster API responses while your memories are processed. The memories will be available on the dashboard and for retrieval within a few seconds.
<CodeGroup>
```python Python
messages = [
{"role": "user", "content": "I love hiking and outdoor activities"},
{"role": "assistant", "content": "That's great! I'll remember your interest in hiking and outdoor activities for future recommendations."}
]
client.add(messages, user_id="alex", async_mode=True)
```
```javascript JavaScript
const messages = [
{"role": "user", "content": "I love hiking and outdoor activities"},
{"role": "assistant", "content": "That's great! I'll remember your interest in hiking and outdoor activities for future recommendations."}
];
client.add(messages, { user_id: "alex", async_mode: true })
.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 love hiking and outdoor activities"},
{"role": "assistant", "content": "That's great! I'll remember your interest in hiking and outdoor activities for future recommendations."}
],
"user_id": "alex",
"async_mode": true
}'
```
```json Output
{
"results": [
{
"message": "Memory processing has been queued for background execution"
}
]
}
```
</CodeGroup>
#### Monitor Memories
You can monitor memory operations on the platform dashboard:
@@ -1347,7 +1400,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
"memory":"Name: Alex. Vegetarian. Allergic to nuts.",
"user_id":"alex",
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":null,
"metadata":None,
"immutable": false,
"expiration_date": null,
"created_at":"2024-07-25T23:57:00.108347-07:00",
@@ -1780,24 +1833,26 @@ curl -X GET "https://api.mem0.ai/v1/memories/<memory-id-here>/history/" \
### 4.6 Update Memory
Update a memory with new data.
Update a memory with new data. You can update the memory's text, metadata, or both.
<CodeGroup>
```python Python
message = "I recently tried chicken and I loved it. I'm thinking of trying more non-vegetarian dishes.."
client.update(memory_id, message)
client.update(
memory_id="<memory-id-here>",
text="I am now a vegetarian.",
metadata={"diet": "vegetarian"}
)
```
```javascript JavaScript
const message = "I recently tried chicken and I loved it. I'm thinking of trying more non-vegetarian dishes..";
client.update("memory-id-here", message)
client.update("memory-id-here", { text: "I am now a vegetarian.", metadata: { diet: "vegetarian" } })
.then(result => console.log(result))
.catch(error => console.error(error));
```
```bash cURL
curl -X PUT "https://api.mem0.ai/v1/memories/memory-id-here" \
curl -X PUT "https://api.mem0.ai/v1/memories/<memory-id-here>" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
+15 -6
View File
@@ -5,9 +5,6 @@ iconType: "solid"
---
<Snippet file="paper-release.mdx" />
<Note type="info">
🎉 We're excited to announce that Claude 4 is now available with Mem0! Check it out [here](components/llms/models/anthropic).
</Note>
Mem0 offers two powerful ways to leverage our technology: [our managed platform](#mem0-platform-managed-solution) and [our open source solution](#mem0-open-source).
@@ -333,11 +330,23 @@ const memory = new Memory();
<CodeGroup>
```python Code
# For a user
result = m.add("I like to drink coffee in the morning and go for a walk.", user_id="alice", metadata={"category": "preferences"})
messages = [
{
"role": "user",
"content": "I like to drink coffee in the morning and go for a walk"
}
]
result = m.add(messages, user_id="alice", metadata={"category": "preferences"})
```
```typescript TypeScript
const result = memory.add("I like to drink coffee in the morning and go for a walk.", { userId: "alice", metadata: { category: "preferences" } });
const messages = [
{
role: "user",
content: "I like to drink coffee in the morning and go for a walk"
}
];
const result = memory.add(messages, { userId: "alice", metadata: { category: "preferences" } });
```
```json Output
@@ -406,7 +415,7 @@ const relatedMemories = memory.search("Should I drink coffee or tea?", { userId:
<Card title="Mem0 OSS Python SDK" icon="python" href="/open-source/python-quickstart">
Learn more about Mem0 OSS Python SDK
</Card>
<Card title="Mem0 OSS Node.js SDK" icon="node" href="/open-source-typescript/quickstart">
<Card title="Mem0 OSS Node.js SDK" icon="node" href="/open-source/node-quickstart">
Learn more about Mem0 OSS Node.js SDK
</Card>
</CardGroup>
+113
View File
@@ -0,0 +1,113 @@
---
title: What is Mem0?
icon: "brain"
iconType: "solid"
---
Mem0 is a memory layer designed for modern AI agents. It acts as a persistent memory layer that agents can use to:
- Recall relevant past interactions
- Store important user preferences and factual context
- Learn from successes and failures
It gives AI agents memory so they can remember, learn, and evolve across interactions. Mem0 integrates easily into your agent stack and scales from prototypes to production systems.
## Stateless vs. Stateful Agents
Most current agents are stateless: they process a query, generate a response, and forget everything. Even with huge context windows, everything resets the next session.
Stateful agents, powered by Mem0, are different. They retain context, recall what matters, and behave more intelligently over time.
<Frame caption="Stateless vs Stateful Agent">
<img src="../images/stateless-vs-stateful-agent.png" />
</Frame>
## Where Memory Fits in the Agent Stack
Mem0 sits alongside your retriever, planner, and LLM. Unlike retrieval-based systems (like RAG), Mem0 tracks past interactions, stores long-term knowledge, and evolves the agent’s behavior.
<Frame caption="Memory in Agent Architecture">
<img src="../images/memory-agent-stack.png" />
</Frame>
Memory is not about pushing more tokens into a prompt but about intelligently remembering context that matters. This distinction matters:
| Capability | Context Window | Mem0 Memory |
|------------------|------------------------|-----------------------------|
| Retention | Temporary | Persistent |
| Cost | Grows with input size | Optimized (only what matters) |
| Recall | Token proximity | Relevance + intent-based |
| Personalization | None | Deep, evolving profile |
| Behavior | Reactive | Adaptive |
## Memory vs. RAG: Complementary Tools
RAG (Retrieval-Augmented Generation) is great for fetching facts from documents. But it’s stateless. It doesn’t know who the user is, what they’ve asked before, or what failed last time.
Mem0 provides continuity. It stores decisions, preferences, and context—not just knowledge.
| Aspect | RAG | Mem0 Memory |
|--------------------|-------------------------------|-------------------------------|
| Statefulness | Stateless | Stateful |
| Recall Type | Document lookup | Evolving user context |
| Use Case | Ground answers in data | Guide behavior across time |
Together, they’re stronger: RAG informs the LLM; Mem0 shapes its memory.
## Types of Memory in Mem0
Mem0 supports different kinds of memory to mimic how humans store information:
- **Working Memory**: short-term session awareness
- **Factual Memory**: long-term structured knowledge (e.g., preferences, settings)
- **Episodic Memory**: records specific past conversations
- **Semantic Memory**: builds general knowledge over time
## Why Developers Choose Mem0
Mem0 isn’t a wrapper around a vector store. It’s a full memory engine with:
- **LLM-based extraction**: Intelligently decides what to remember
- **Filtering & decay**: Avoids memory bloat, forgets irrelevant info
- **Costs Reduction**: Save compute costs with smart prompt injection of only relevant memories
- **Dashboards & APIs**: Observability, fine-grained control
- **Cloud and OSS**: Use our platform version or our open-source SDK version
You plug Mem0 into your agent framework, it doesn’t replace your LLM or workflows. Instead, it adds a smart memory layer on top.
## Core Capabilities
- **Reduced token usage and faster responses**: sub-50 ms lookups
- **Semantic memory**: procedural, episodic, and factual support
- **Multimodal support**: handle both text and images
- **Graph memory**: connect insights and entities across sessions
- **Host your way**: either a managed service or a self-hosted version
## Getting Started
Mem0 offers two powerful ways to leverage our technology: our [managed platform](/platform/overview) and our [open source solution](/open-source/overview).
<CardGroup cols={3}>
<Card title="Quickstart" icon="rocket" href="/quickstart">
Integrate Mem0 in a few lines of code
</Card>
<Card title="Playground" icon="play" href="https://app.mem0.ai/playground">
Mem0 in action
</Card>
<Card title="Examples" icon="lightbulb" href="/examples">
See what you can build with Mem0
</Card>
</CardGroup>
## Need help?
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx"/>
+2 -2
View File
@@ -2552,7 +2552,7 @@ azure = ["adlfs (>=2024.2.0)"]
clip = ["open-clip", "pillow", "torch"]
dev = ["pre-commit", "ruff"]
docs = ["mkdocs", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]"]
embeddings = ["awscli (>=1.29.57)", "boto3 (>=1.28.57)", "botocore (>=1.31.57)", "cohere", "google-generativeai", "huggingface-hub", "instructorembedding", "open-clip-torch", "openai (>=1.6.1)", "pillow", "sentence-transformers", "torch"]
embeddings = ["awscli (>=1.29.57)", "boto3 (>=1.28.57)", "botocore (>=1.31.57)", "cohere", "google-generativeai", "huggingface-hub", "instructorembedding", "open-clip-torch", "openai (>=1.6.1)", "pillow", "sentence-transformers", "torch", "google-genai"]
tests = ["aiohttp", "boto3", "duckdb", "pandas (>=1.4)", "polars (>=0.19)", "pytest", "pytest-asyncio", "pytest-mock", "pytz", "tantivy"]
[[package]]
@@ -7129,7 +7129,7 @@ cffi = ["cffi (>=1.11)"]
aws = ["langchain-aws"]
elasticsearch = ["elasticsearch"]
gmail = ["google-api-core", "google-api-python-client", "google-auth", "google-auth-httplib2", "google-auth-oauthlib", "requests"]
google = ["google-generativeai"]
google = ["google-generativeai", "google-genai"]
googledrive = ["google-api-python-client", "google-auth-httplib2", "google-auth-oauthlib"]
lancedb = ["lancedb"]
llama2 = ["replicate"]
+3 -2
View File
@@ -4,6 +4,7 @@ from collections import defaultdict
import numpy as np
from openai import OpenAI
from mem0.memory.utils import extract_json
client = OpenAI()
@@ -22,7 +23,7 @@ The generated answer might be much longer, but you should be generous with your
For time related questions, the gold answer will be a specific date, month, year, etc. The generated answer might be much longer or use relative time references (like "last Tuesday" or "next month"), but you should be generous with your grading - as long as it refers to the same date or time period as the gold answer, it should be counted as CORRECT. Even if the format differs (e.g., "May 7th" vs "7 May"), consider it CORRECT if it's the same date.
Now it’s time for the real question:
Now it's time for the real question:
Question: {question}
Gold answer: {gold_answer}
Generated answer: {generated_answer}
@@ -49,7 +50,7 @@ def evaluate_llm_judge(question, gold_answer, generated_answer):
response_format={"type": "json_object"},
temperature=0.0,
)
label = json.loads(response.choices[0].message.content)["label"]
label = json.loads(extract_json(response.choices[0].message.content))["label"]
return 1 if label == "CORRECT" else 0
@@ -0,0 +1,124 @@
"""Simple Voice Agent with Memory: Personal Food Assistant.
A food assistant that remembers your dietary preferences and speaks recommendations
Powered by Agno + Cartesia + Mem0
export MEM0_API_KEY=your_mem0_api_key
export OPENAI_API_KEY=your_openai_api_key
export CARTESIA_API_KEY=your_cartesia_api_key
"""
from textwrap import dedent
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.cartesia import CartesiaTools
from agno.utils.audio import write_audio_to_file
from mem0 import MemoryClient
memory_client = MemoryClient()
USER_ID = "food_user_01"
# Agent instructions
agent_instructions = dedent(
"""Follow these steps SEQUENTIALLY to provide personalized food recommendations with voice:
1. Analyze the user's food request and identify what type of recommendation they need.
2. Consider their dietary preferences, restrictions, and cooking habits from memory context.
3. Generate a personalized food recommendation based on their stored preferences.
4. Analyze the appropriate tone for the response (helpful, enthusiastic, cautious for allergies).
5. Call `list_voices` to retrieve available voices.
6. Select a voice that matches the helpful, friendly tone.
7. Call `text_to_speech` to generate the final audio recommendation.
"""
)
# Simple agent that remembers food preferences
food_agent = Agent(
name="Personal Food Assistant",
description="Provides personalized food recommendations with memory and generates voice responses using Cartesia TTS tools.",
instructions=agent_instructions,
model=OpenAIChat(id="gpt-4o"),
tools=[CartesiaTools(voice_localize_enabled=True)],
show_tool_calls=True,
)
def get_food_recommendation(user_query: str, user_id):
"""Get food recommendation with memory context"""
# Search memory for relevant food preferences
memories_result = memory_client.search(
query=user_query,
user_id=user_id,
limit=5
)
# Add memory context to the message
memories = [f"- {result['memory']}" for result in memories_result]
memory_context = "Memories about user that might be relevant:\n" + "\n".join(memories)
# Combine memory context with user request
full_request = f"""
{memory_context}
User: {user_query}
Answer the user query based on provided context and create a voice note.
"""
# Generate response with voice (same pattern as translator)
food_agent.print_response(full_request)
response = food_agent.run_response
# Save audio file
if response.audio:
import time
timestamp = int(time.time())
filename = f"food_recommendation_{timestamp}.mp3"
write_audio_to_file(
response.audio[0].base64_audio,
filename=filename,
)
print(f"Audio saved as {filename}")
return response.content
def initialize_food_memory(user_id):
"""Initialize memory with food preferences"""
messages = [
{
"role": "user",
"content": "Hi, I'm Sarah. I'm vegetarian and lactose intolerant. I love spicy food, especially Thai and Indian cuisine.",
},
{
"role": "assistant",
"content": "Hello Sarah! I've noted that you're vegetarian, lactose intolerant, and love spicy Thai and Indian food.",
},
{
"role": "user",
"content": "I prefer quick breakfasts since I'm always rushing, but I like cooking elaborate dinners. I also meal prep on Sundays.",
},
{
"role": "assistant",
"content": "Got it! Quick breakfasts, elaborate dinners, and Sunday meal prep. I'll remember this for future recommendations.",
},
{
"role": "user",
"content": "I'm trying to eat more protein. I like quinoa, lentils, chickpeas, and tofu. I hate mushrooms though.",
},
{
"role": "assistant",
"content": "Perfect! I'll focus on protein-rich options like quinoa, lentils, chickpeas, and tofu, and avoid mushrooms.",
},
]
memory_client.add(messages, user_id=user_id)
print("Food preferences stored in memory")
# Initialize the memory for the user once in order for the agent to learn the user preference
initialize_food_memory(user_id=USER_ID)
print(get_food_recommendation("Which type of restaurants should I go tonight for dinner and cuisines preferred?", user_id=USER_ID))
# OUTPUT: 🎵 Audio saved as food_recommendation_1750162610.mp3
# For dinner tonight, considering your love for healthy spic optionsy, you could try a nice Thai, Indian, or Mexican restaurant.
# You might find dishes with quinoa, chickpeas, tofu, and fresh herbs delightful. Enjoy your dinner!
+144
View File
@@ -0,0 +1,144 @@
"""
Example of using vLLM with mem0 for high-performance memory operations.
SETUP INSTRUCTIONS:
1. Install vLLM:
pip install vllm
2. Start vLLM server (in a separate terminal):
vllm serve microsoft/DialoGPT-small --port 8000
Wait for the message: "Uvicorn running on http://0.0.0.0:8000"
(Small model: ~500MB download, much faster!)
3. Verify server is running:
curl http://localhost:8000/health
4. Run this example:
python examples/misc/vllm_example.py
Optional environment variables:
export VLLM_BASE_URL="http://localhost:8000/v1"
export VLLM_API_KEY="vllm-api-key"
"""
from mem0 import Memory
# Configuration for vLLM integration
config = {
"llm": {
"provider": "vllm",
"config": {
"model": "Qwen/Qwen2.5-32B-Instruct",
"vllm_base_url": "http://localhost:8000/v1",
"api_key": "vllm-api-key",
"temperature": 0.7,
"max_tokens": 100,
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small"
}
},
"vector_store": {
"provider": "qdrant",
"config": {
"collection_name": "vllm_memories",
"host": "localhost",
"port": 6333
}
}
}
def main():
"""
Demonstrate vLLM integration with mem0
"""
print("--> Initializing mem0 with vLLM...")
# Initialize memory with vLLM
memory = Memory.from_config(config)
print("--> Memory initialized successfully!")
# Example conversations to store
conversations = [
{
"messages": [
{"role": "user", "content": "I love playing chess on weekends"},
{"role": "assistant", "content": "That's great! Chess is an excellent strategic game that helps improve critical thinking."}
],
"user_id": "user_123"
},
{
"messages": [
{"role": "user", "content": "I'm learning Python programming"},
{"role": "assistant", "content": "Python is a fantastic language for beginners! What specific areas are you focusing on?"}
],
"user_id": "user_123"
},
{
"messages": [
{"role": "user", "content": "I prefer working late at night, I'm more productive then"},
{"role": "assistant", "content": "Many people find they're more creative and focused during nighttime hours. It's important to maintain a consistent schedule that works for you."}
],
"user_id": "user_123"
}
]
print("\n--> Adding memories using vLLM...")
# Add memories - now powered by vLLM's high-performance inference
for i, conversation in enumerate(conversations, 1):
result = memory.add(
messages=conversation["messages"],
user_id=conversation["user_id"]
)
print(f"Memory {i} added: {result}")
print("\n🔍 Searching memories...")
# Search memories - vLLM will process the search and memory operations
search_queries = [
"What does the user like to do on weekends?",
"What is the user learning?",
"When is the user most productive?"
]
for query in search_queries:
print(f"\nQuery: {query}")
memories = memory.search(
query=query,
user_id="user_123"
)
for memory_item in memories:
print(f" - {memory_item['memory']}")
print("\n--> Getting all memories for user...")
all_memories = memory.get_all(user_id="user_123")
print(f"Total memories stored: {len(all_memories)}")
for memory_item in all_memories:
print(f" - {memory_item['memory']}")
print("\n--> vLLM integration demo completed successfully!")
print("\nBenefits of using vLLM:")
print(" -> 2.7x higher throughput compared to standard implementations")
print(" -> 5x faster time-per-output-token")
print(" -> Efficient memory usage with PagedAttention")
print(" -> Simple configuration, same as other providers")
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"=> Error: {e}")
print("\nTroubleshooting:")
print("1. Make sure vLLM server is running: vllm serve microsoft/DialoGPT-small --port 8000")
print("2. Check if the model is downloaded and accessible")
print("3. Verify the base URL and port configuration")
print("4. Ensure you have the required dependencies installed")
+3 -4
View File
@@ -2,8 +2,8 @@
Mem0 is a self-improving memory layer for LLM applications, enabling personalized AI experiences that save costs and delight users. We offer both cloud and open-source solutions to cater to different needs.
See the complete [OSS Docs](https://docs.mem0.ai/open-source-typescript/quickstart).
See the complete [Platform API Reference](https://docs.mem0.ai/api-reference/overview).
See the complete [OSS Docs](https://docs.mem0.ai/open-source/node-quickstart).
See the complete [Platform API Reference](https://docs.mem0.ai/api-reference).
## 1. Installation
@@ -61,5 +61,4 @@ If you have any questions or need assistance, please reach out to us:
- Email: founders@mem0.ai
- [Join our discord community](https://mem0.ai/discord)
- [Join our slack community](https://mem0.ai/slack)
- GitHub Issues: [Report bugs or request features](https://github.com/mem0ai/mem0ai-node/issues)
- GitHub Issues: [Report bugs or request features](https://github.com/mem0ai/mem0/issues)
+11 -5
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "2.1.30",
"version": "2.1.33",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -98,17 +98,17 @@
},
"peerDependencies": {
"@anthropic-ai/sdk": "^0.40.1",
"@cloudflare/workers-types": "^4.20250504.0",
"@google/genai": "^1.2.0",
"@langchain/core": "^0.3.44",
"@mistralai/mistralai": "^1.5.2",
"@qdrant/js-client-rest": "1.13.0",
"@supabase/supabase-js": "^2.49.1",
"@types/jest": "29.5.14",
"@types/pg": "8.11.0",
"@types/sqlite3": "3.1.11",
"groq-sdk": "0.3.0",
"@langchain/core": "^0.3.44",
"cloudflare": "^4.2.0",
"@cloudflare/workers-types": "^4.20250504.0",
"groq-sdk": "0.3.0",
"neo4j-driver": "^5.28.1",
"ollama": "^0.5.14",
"pg": "8.11.3",
@@ -121,5 +121,11 @@
"publishConfig": {
"access": "public"
},
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b"
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"sqlite3"
]
}
}
+1 -1
View File
@@ -229,7 +229,7 @@ export default class MemoryClient {
}
if (options.api_version) {
options.version = options.api_version.toString();
options.version = options.api_version.toString() || "v2";
}
const payload = this._preparePayload(messages, options);
+2
View File
@@ -24,6 +24,8 @@ export interface MemoryOptions {
timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
filter_memories?: boolean;
immutable?: boolean;
}
export interface ProjectOptions {
+1 -1
View File
@@ -1,7 +1,7 @@
// @ts-nocheck
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
let version = "2.1.26";
let version = "2.1.33";
// Safely check for process.env in different environments
let MEM0_TELEMETRY = true;
+511 -102
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -43,6 +43,9 @@ class BaseLlmConfig(ABC):
sarvam_base_url: Optional[str] = "https://api.sarvam.ai/v1",
# LM Studio specific
lmstudio_base_url: Optional[str] = "http://localhost:1234/v1",
lmstudio_response_format: dict = None,
# vLLM specific
vllm_base_url: Optional[str] = "http://localhost:8000/v1",
# AWS Bedrock specific
aws_access_key_id: Optional[str] = None,
aws_secret_access_key: Optional[str] = None,
@@ -95,6 +98,10 @@ class BaseLlmConfig(ABC):
:type sarvam_base_url: Optional[str], optional
:param lmstudio_base_url: LM Studio base URL to be use, defaults to "http://localhost:1234/v1"
:type lmstudio_base_url: Optional[str], optional
:param lmstudio_response_format: LM Studio response format to be use, defaults to None
:type lmstudio_response_format: Optional[Dict], optional
:param vllm_base_url: vLLM base URL to be use, defaults to "http://localhost:8000/v1"
:type vllm_base_url: Optional[str], optional
"""
self.model = model
@@ -134,6 +141,10 @@ class BaseLlmConfig(ABC):
# LM Studio specific
self.lmstudio_base_url = lmstudio_base_url
self.lmstudio_response_format = lmstudio_response_format
# vLLM specific
self.vllm_base_url = vllm_base_url
# AWS Bedrock specific
self.aws_access_key_id = aws_access_key_id
+30
View File
@@ -0,0 +1,30 @@
from enum import Enum
from typing import Any, Dict
from pydantic import BaseModel, Field, model_validator
class BaiduDBConfig(BaseModel):
endpoint: str = Field("http://localhost:8287", description="Endpoint URL for Baidu VectorDB")
account: str = Field("root", description="Account for Baidu VectorDB")
api_key: str = Field(None, description="API Key for Baidu VectorDB")
database_name: str = Field("mem0", description="Name of the database")
table_name: str = Field("mem0", description="Name of the table")
embedding_model_dims: int = Field(1536, description="Dimensions of the embedding model")
metric_type: str = Field("L2", description="Metric type for similarity search")
@model_validator(mode="before")
@classmethod
def validate_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
allowed_fields = set(cls.model_fields.keys())
input_fields = set(values.keys())
extra_fields = input_fields - allowed_fields
if extra_fields:
raise ValueError(
f"Extra fields not allowed: {', '.join(extra_fields)}. Please input only the following fields: {', '.join(allowed_fields)}"
)
return values
model_config = {
"arbitrary_types_allowed": True,
}
+42
View File
@@ -0,0 +1,42 @@
from typing import Any, Dict, Optional, Callable, List
from pydantic import BaseModel, Field, root_validator
class MongoDBConfig(BaseModel):
"""Configuration for MongoDB vector database."""
db_name: str = Field("mem0_db", description="Name of the MongoDB database")
collection_name: str = Field("mem0", description="Name of the MongoDB collection")
embedding_model_dims: Optional[int] = Field(1536, description="Dimensions of the embedding vectors")
user: Optional[str] = Field(None, description="MongoDB user for authentication")
password: Optional[str] = Field(None, description="Password for the MongoDB user")
host: Optional[str] = Field("localhost", description="MongoDB host. Default is 'localhost'")
port: Optional[int] = Field(27017, description="MongoDB port. Default is 27017")
@root_validator(pre=True)
def check_auth_and_connection(cls, values):
user = values.get("user")
password = values.get("password")
if (user is None) != (password is None):
raise ValueError("Both 'user' and 'password' must be provided together or omitted together.")
host = values.get("host")
port = values.get("port")
if host is None:
raise ValueError("The 'host' must be provided.")
if port is None:
raise ValueError("The 'port' must be provided.")
return values
@root_validator(pre=True)
def validate_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
allowed_fields = set(cls.__fields__)
input_fields = set(values.keys())
extra_fields = input_fields - allowed_fields
if extra_fields:
raise ValueError(
f"Extra fields not allowed: {', '.join(extra_fields)}. "
f"Please provide only the following fields: {', '.join(allowed_fields)}."
)
return values
+2
View File
@@ -27,6 +27,7 @@ class AWSBedrockEmbedding(EmbeddingBase):
# Get AWS config from environment variables or use defaults
aws_access_key = os.environ.get("AWS_ACCESS_KEY_ID", "")
aws_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
aws_session_token = os.environ.get("AWS_SESSION_TOKEN", "")
aws_region = os.environ.get("AWS_REGION", "us-west-2")
# Check if AWS config is provided in the config
@@ -42,6 +43,7 @@ class AWSBedrockEmbedding(EmbeddingBase):
region_name=aws_region,
aws_access_key_id=aws_access_key if aws_access_key else None,
aws_secret_access_key=aws_secret_key if aws_secret_key else None,
aws_session_token=aws_session_token if aws_session_token else None,
)
def _normalize_vector(self, embeddings):
+12 -7
View File
@@ -1,7 +1,8 @@
import os
from typing import Literal, Optional
import google.generativeai as genai
from google import genai
from google.genai import types
from mem0.configs.embeddings.base import BaseEmbedderConfig
from mem0.embeddings.base import EmbeddingBase
@@ -12,11 +13,11 @@ class GoogleGenAIEmbedding(EmbeddingBase):
super().__init__(config)
self.config.model = self.config.model or "models/text-embedding-004"
self.config.embedding_dims = self.config.embedding_dims or 768
self.config.embedding_dims = self.config.embedding_dims or self.config.output_dimensionality or 768
api_key = self.config.api_key or os.getenv("GOOGLE_API_KEY")
genai.configure(api_key=api_key)
self.client = genai.Client(api_key=api_key)
def embed(self, text, memory_action: Optional[Literal["add", "search", "update"]] = None):
"""
@@ -28,7 +29,11 @@ class GoogleGenAIEmbedding(EmbeddingBase):
list: The embedding vector.
"""
text = text.replace("\n", " ")
response = genai.embed_content(
model=self.config.model, content=text, output_dimensionality=self.config.embedding_dims
)
return response["embedding"]
# Create config for embedding parameters
config = types.EmbedContentConfig(output_dimensionality=self.config.embedding_dims)
# Call the embed_content method with the correct parameters
response = self.client.models.embed_content(model=self.config.model, contents=text, config=config)
return response.embeddings[0].values
+6 -10
View File
@@ -11,6 +11,7 @@ except ImportError:
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
PROVIDERS = ["ai21", "amazon", "anthropic", "cohere", "meta", "mistral", "stability", "writer"]
@@ -91,12 +92,10 @@ class AWSBedrockLLM(LLMBase):
if response["output"]["message"]["content"]:
for item in response["output"]["message"]["content"]:
if "toolUse" in item:
processed_response["tool_calls"].append(
{
"name": item["toolUse"]["name"],
"arguments": item["toolUse"]["input"],
}
)
processed_response["tool_calls"].append({
"name": item["toolUse"]["name"],
"arguments": item["toolUse"]["input"],
})
return processed_response
@@ -189,10 +188,7 @@ class AWSBedrockLLM(LLMBase):
}
for prop, details in function["parameters"].get("properties", {}).items():
new_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop] = {
"type": details.get("type", "string"),
"description": details.get("description", ""),
}
new_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop] = details
new_tools.append(new_tool)
+2 -1
View File
@@ -6,6 +6,7 @@ from openai import AzureOpenAI
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class AzureOpenAILLM(LLMBase):
@@ -53,7 +54,7 @@ class AzureOpenAILLM(LLMBase):
processed_response["tool_calls"].append(
{
"name": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
"arguments": json.loads(extract_json(tool_call.function.arguments)),
}
)
+1
View File
@@ -26,6 +26,7 @@ class LlmConfig(BaseModel):
"xai",
"sarvam",
"lmstudio",
"vllm",
"langchain",
):
return v
+2 -1
View File
@@ -6,6 +6,7 @@ from openai import OpenAI
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class DeepSeekLLM(LLMBase):
@@ -41,7 +42,7 @@ class DeepSeekLLM(LLMBase):
processed_response["tool_calls"].append(
{
"name": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
"arguments": json.loads(extract_json(tool_call.function.arguments)),
}
)
+87 -55
View File
@@ -2,13 +2,10 @@ import os
from typing import Dict, List, Optional
try:
import google.generativeai as genai
from google.generativeai import GenerativeModel, protos
from google.generativeai.types import content_types
from google import genai
from google.genai import types
except ImportError:
raise ImportError(
"The 'google-generativeai' library is required. Please install it using 'pip install google-generativeai'."
)
raise ImportError("The 'google-genai' library is required. Please install it using 'pip install google-genai'.")
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
@@ -19,11 +16,10 @@ class GeminiLLM(LLMBase):
super().__init__(config)
if not self.config.model:
self.config.model = "gemini-1.5-flash-latest"
self.config.model = "gemini-2.0-flash"
api_key = self.config.api_key or os.getenv("GEMINI_API_KEY")
genai.configure(api_key=api_key)
self.client = GenerativeModel(model_name=self.config.model)
api_key = self.config.api_key or os.getenv("GOOGLE_API_KEY")
self.client = genai.Client(api_key=api_key)
def _parse_response(self, response, tools):
"""
@@ -38,21 +34,36 @@ class GeminiLLM(LLMBase):
"""
if tools:
processed_response = {
"content": (content if (content := response.candidates[0].content.parts[0].text) else None),
"content": None,
"tool_calls": [],
}
for part in response.candidates[0].content.parts:
if fn := part.function_call:
if isinstance(fn, protos.FunctionCall):
fn_call = type(fn).to_dict(fn)
processed_response["tool_calls"].append({"name": fn_call["name"], "arguments": fn_call["args"]})
continue
processed_response["tool_calls"].append({"name": fn.name, "arguments": fn.args})
# Extract content from the first candidate
if response.candidates and response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if hasattr(part, "text") and part.text:
processed_response["content"] = part.text
break
# Extract function calls
if response.candidates and response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if hasattr(part, "function_call") and part.function_call:
fn = part.function_call
processed_response["tool_calls"].append(
{
"name": fn.name,
"arguments": dict(fn.args) if fn.args else {},
}
)
return processed_response
else:
return response.candidates[0].content.parts[0].text
if response.candidates and response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if hasattr(part, "text") and part.text:
return part.text
return ""
def _reformat_messages(self, messages: List[Dict[str, str]]):
"""
@@ -62,25 +73,22 @@ class GeminiLLM(LLMBase):
messages: The list of messages provided in the request.
Returns:
list: The list of messages in the required format.
tuple: (system_instruction, contents_list)
"""
new_messages = []
system_instruction = None
contents = []
for message in messages:
if message["role"] == "system":
content = "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: " + message["content"]
system_instruction = message["content"]
else:
content = message["content"]
content = types.Content(
parts=[types.Part(text=message["content"])],
role=message["role"],
)
contents.append(content)
new_messages.append(
{
"parts": content,
"role": "model" if message["role"] == "model" else "user",
}
)
return new_messages
return system_instruction, contents
def _reformat_tools(self, tools: Optional[List[Dict]]):
"""
@@ -95,7 +103,6 @@ class GeminiLLM(LLMBase):
def remove_additional_properties(data):
"""Recursively removes 'additionalProperties' from nested dictionaries."""
if isinstance(data, dict):
filtered_dict = {
key: remove_additional_properties(value)
@@ -106,16 +113,21 @@ class GeminiLLM(LLMBase):
else:
return data
new_tools = []
if tools:
function_declarations = []
for tool in tools:
func = tool["function"].copy()
new_tools.append({"function_declarations": [remove_additional_properties(func)]})
cleaned_func = remove_additional_properties(func)
# TODO: temporarily ignore it to pass tests, will come back to update according to standards later.
# return content_types.to_function_library(new_tools)
function_declaration = types.FunctionDeclaration(
name=cleaned_func["name"],
description=cleaned_func.get("description", ""),
parameters=cleaned_func.get("parameters", {}),
)
function_declarations.append(function_declaration)
return new_tools
tool_obj = types.Tool(function_declarations=function_declarations)
return [tool_obj]
else:
return None
@@ -139,33 +151,53 @@ class GeminiLLM(LLMBase):
str: The generated response.
"""
params = {
# Extract system instruction and reformat messages
system_instruction, contents = self._reformat_messages(messages)
# Prepare generation config
config_params = {
"temperature": self.config.temperature,
"max_output_tokens": self.config.max_tokens,
"top_p": self.config.top_p,
}
# Add system instruction to config if present
if system_instruction:
config_params["system_instruction"] = system_instruction
if response_format is not None and response_format["type"] == "json_object":
params["response_mime_type"] = "application/json"
config_params["response_mime_type"] = "application/json"
if "schema" in response_format:
params["response_schema"] = response_format["schema"]
if tool_choice:
tool_config = content_types.to_tool_config(
{
"function_calling_config": {
"mode": tool_choice,
"allowed_function_names": (
config_params["response_schema"] = response_format["schema"]
if tools:
formatted_tools = self._reformat_tools(tools)
config_params["tools"] = formatted_tools
if tool_choice:
if tool_choice == "auto":
mode = types.FunctionCallingConfigMode.AUTO
elif tool_choice == "any":
mode = types.FunctionCallingConfigMode.ANY
else:
mode = types.FunctionCallingConfigMode.NONE
tool_config = types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
mode=mode,
allowed_function_names=(
[tool["function"]["name"] for tool in tools] if tool_choice == "any" else None
),
}
}
)
)
)
config_params["tool_config"] = tool_config
response = self.client.generate_content(
contents=self._reformat_messages(messages),
tools=self._reformat_tools(tools),
generation_config=genai.GenerationConfig(**params),
tool_config=tool_config,
generation_config = types.GenerateContentConfig(**config_params)
response = self.client.models.generate_content(
model=self.config.model, contents=contents, config=generation_config
)
return self._parse_response(response, tools)
+2 -1
View File
@@ -9,6 +9,7 @@ except ImportError:
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class GroqLLM(LLMBase):
@@ -43,7 +44,7 @@ class GroqLLM(LLMBase):
processed_response["tool_calls"].append(
{
"name": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
"arguments": json.loads(extract_json(tool_call.function.arguments)),
}
)
+2 -1
View File
@@ -8,6 +8,7 @@ except ImportError:
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class LiteLLM(LLMBase):
@@ -39,7 +40,7 @@ class LiteLLM(LLMBase):
processed_response["tool_calls"].append(
{
"name": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
"arguments": json.loads(extract_json(tool_call.function.arguments)),
}
)
+2
View File
@@ -46,6 +46,8 @@ class LMStudioLLM(LLMBase):
}
if response_format:
params["response_format"] = response_format
if self.config.lmstudio_response_format is not None:
params["response_format"] = self.config.lmstudio_response_format
response = self.client.chat.completions.create(**params)
return response.choices[0].message.content
+2 -1
View File
@@ -7,6 +7,7 @@ from openai import OpenAI
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class OpenAILLM(LLMBase):
@@ -62,7 +63,7 @@ class OpenAILLM(LLMBase):
processed_response["tool_calls"].append(
{
"name": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
"arguments": json.loads(extract_json(tool_call.function.arguments)),
}
)
+2 -1
View File
@@ -9,6 +9,7 @@ except ImportError:
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class TogetherLLM(LLMBase):
@@ -43,7 +44,7 @@ class TogetherLLM(LLMBase):
processed_response["tool_calls"].append(
{
"name": tool_call.function.name,
"arguments": json.loads(tool_call.function.arguments),
"arguments": json.loads(extract_json(tool_call.function.arguments)),
}
)
View File
View File
+85
View File
@@ -0,0 +1,85 @@
import json
import os
from typing import Dict, List, Optional
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class VllmLLM(LLMBase):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config)
if not self.config.model:
self.config.model = "Qwen/Qwen2.5-32B-Instruct"
self.config.api_key = self.config.api_key or os.getenv("VLLM_API_KEY") or "vllm-api-key"
base_url = self.config.vllm_base_url or os.getenv("VLLM_BASE_URL")
self.client = OpenAI(base_url=base_url, api_key=self.config.api_key)
def _parse_response(self, response, tools):
"""
Process the response based on whether tools are used or not.
Args:
response: The raw response from API.
tools: The list of tools provided in the request.
Returns:
str or dict: The processed response.
"""
if tools:
processed_response = {
"content": response.choices[0].message.content,
"tool_calls": [],
}
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
processed_response["tool_calls"].append({
"name": tool_call.function.name,
"arguments": json.loads(extract_json(tool_call.function.arguments)),
})
return processed_response
else:
return response.choices[0].message.content
def generate_response(
self,
messages: List[Dict[str, str]],
response_format=None,
tools: Optional[List[Dict]] = None,
tool_choice: str = "auto",
):
"""
Generate a response based on the given messages using vLLM.
Args:
messages (list): List of message dicts containing 'role' and 'content'.
response_format (str or object, optional): Format of the response. Defaults to "text".
tools (list, optional): List of tools that the model can call. Defaults to None.
tool_choice (str, optional): Tool choice method. Defaults to "auto".
Returns:
str: The generated response.
"""
params = {
"model": self.config.model,
"messages": messages,
"temperature": self.config.temperature,
"max_tokens": self.config.max_tokens,
"top_p": self.config.top_p,
}
if response_format:
params["response_format"] = response_format
if tools:
params["tools"] = tools
params["tool_choice"] = tool_choice
response = self.client.chat.completions.create(**params)
return self._parse_response(response, tools)
+227 -129
View File
@@ -80,8 +80,8 @@ class MemoryGraph:
# TODO: Batch queries with APOC plugin
# TODO: Add more filter support
deleted_entities = self._delete_entities(to_be_deleted, filters["user_id"])
added_entities = self._add_entities(to_be_added, filters["user_id"], entity_type_map)
deleted_entities = self._delete_entities(to_be_deleted, filters)
added_entities = self._add_entities(to_be_added, filters, entity_type_map)
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
@@ -122,18 +122,25 @@ class MemoryGraph:
return search_results
def delete_all(self, filters):
cypher = f"""
MATCH (n {self.node_label} {{user_id: $user_id}})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"]}
if filters.get("agent_id"):
cypher = f"""
MATCH (n {self.node_label} {{user_id: $user_id, agent_id: $agent_id}})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"], "agent_id": filters["agent_id"]}
else:
cypher = f"""
MATCH (n {self.node_label} {{user_id: $user_id}})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"]}
self.graph.query(cypher, params=params)
def get_all(self, filters, limit=100):
"""
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.
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
Returns:
@@ -141,13 +148,19 @@ class MemoryGraph:
- 'contexts': The base data store response for each memory.
- 'entities': A list of strings representing the nodes and relationships
"""
# return all nodes and relationships
agent_filter = ""
params = {"user_id": filters["user_id"], "limit": limit}
if filters.get("agent_id"):
agent_filter = "AND n.agent_id = $agent_id AND m.agent_id = $agent_id"
params["agent_id"] = filters["agent_id"]
query = f"""
MATCH (n {self.node_label} {{user_id: $user_id}})-[r]->(m {self.node_label} {{user_id: $user_id}})
WHERE 1=1 {agent_filter}
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit
"""
results = self.graph.query(query, params={"user_id": filters["user_id"], "limit": limit})
results = self.graph.query(query, params=params)
final_results = []
for result in results:
@@ -163,6 +176,7 @@ class MemoryGraph:
return final_results
def _retrieve_nodes_from_data(self, data, filters):
"""Extracts all the entities mentioned in the query."""
_tools = [EXTRACT_ENTITIES_TOOL]
@@ -197,23 +211,27 @@ class MemoryGraph:
return entity_type_map
def _establish_nodes_relations_from_data(self, data, filters, entity_type_map):
"""Eshtablish relations among the extracted nodes."""
"""Establish relations among the extracted nodes."""
# Compose user identification string for prompt
user_identity = f"user_id: {filters['user_id']}"
if filters.get("agent_id"):
user_identity += f", agent_id: {filters['agent_id']}"
if self.config.graph_store.custom_prompt:
system_content = EXTRACT_RELATIONS_PROMPT.replace("USER_ID", user_identity)
# Add the custom prompt line if configured
system_content = system_content.replace(
"CUSTOM_PROMPT", f"4. {self.config.graph_store.custom_prompt}"
)
messages = [
{
"role": "system",
"content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]).replace(
"CUSTOM_PROMPT", f"4. {self.config.graph_store.custom_prompt}"
),
},
{"role": "system", "content": system_content},
{"role": "user", "content": data},
]
else:
system_content = EXTRACT_RELATIONS_PROMPT.replace("USER_ID", user_identity)
messages = [
{
"role": "system",
"content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]),
},
{"role": "system", "content": system_content},
{"role": "user", "content": f"List of entities: {list(entity_type_map.keys())}. \n\nText: {data}"},
]
@@ -227,8 +245,8 @@ class MemoryGraph:
)
entities = []
if extracted_entities["tool_calls"]:
entities = extracted_entities["tool_calls"][0]["arguments"]["entities"]
if extracted_entities.get("tool_calls"):
entities = extracted_entities["tool_calls"][0].get("arguments", {}).get("entities", [])
entities = self._remove_spaces_from_entities(entities)
logger.debug(f"Extracted entities: {entities}")
@@ -237,32 +255,43 @@ class MemoryGraph:
def _search_graph_db(self, node_list, filters, limit=100):
"""Search similar nodes among and their respective incoming and outgoing relations."""
result_relations = []
agent_filter = ""
if filters.get("agent_id"):
agent_filter = "AND n.agent_id = $agent_id AND m.agent_id = $agent_id"
for node in node_list:
n_embedding = self.embedding_model.embed(node)
cypher_query = f"""
MATCH (n {self.node_label})
WHERE n.embedding IS NOT NULL AND n.user_id = $user_id
{agent_filter}
WITH n, round(2 * vector.similarity.cosine(n.embedding, $n_embedding) - 1, 4) AS similarity // denormalize for backward compatibility
WHERE similarity >= $threshold
CALL (n) {{
MATCH (n)-[r]->(m)
CALL {{
MATCH (n)-[r]->(m)
WHERE m.user_id = $user_id {agent_filter.replace("n.", "m.")}
RETURN n.name AS source, elementId(n) AS source_id, type(r) AS relationship, elementId(r) AS relation_id, m.name AS destination, elementId(m) AS destination_id
UNION
MATCH (m)-[r]->(n)
MATCH (m)-[r]->(n)
WHERE m.user_id = $user_id {agent_filter.replace("n.", "m.")}
RETURN m.name AS source, elementId(m) AS source_id, type(r) AS relationship, elementId(r) AS relation_id, n.name AS destination, elementId(n) AS destination_id
}}
WITH distinct source, source_id, relationship, relation_id, destination, destination_id, similarity //deduplicate
WITH distinct source, source_id, relationship, relation_id, destination, destination_id, similarity
RETURN source, source_id, relationship, relation_id, destination, destination_id, similarity
ORDER BY similarity DESC
LIMIT $limit
"""
params = {
"n_embedding": n_embedding,
"threshold": self.threshold,
"user_id": filters["user_id"],
"limit": limit,
}
if filters.get("agent_id"):
params["agent_id"] = filters["agent_id"]
ans = self.graph.query(cypher_query, params=params)
result_relations.extend(ans)
@@ -271,7 +300,13 @@ class MemoryGraph:
def _get_delete_entities_from_search_output(self, search_output, data, filters):
"""Get the entities to be deleted from the search output."""
search_output_string = format_entities(search_output)
system_prompt, user_prompt = get_delete_messages(search_output_string, data, filters["user_id"])
# Compose user identification string for prompt
user_identity = f"user_id: {filters['user_id']}"
if filters.get("agent_id"):
user_identity += f", agent_id: {filters['agent_id']}"
system_prompt, user_prompt = get_delete_messages(search_output_string, data, user_identity)
_tools = [DELETE_MEMORY_TOOL_GRAPH]
if self.llm_provider in ["azure_openai_structured", "openai_structured"]:
@@ -288,44 +323,59 @@ class MemoryGraph:
)
to_be_deleted = []
for item in memory_updates["tool_calls"]:
if item["name"] == "delete_graph_memory":
to_be_deleted.append(item["arguments"])
# in case if it is not in the correct format
for item in memory_updates.get("tool_calls", []):
if item.get("name") == "delete_graph_memory":
to_be_deleted.append(item.get("arguments"))
# Clean entities formatting
to_be_deleted = self._remove_spaces_from_entities(to_be_deleted)
logger.debug(f"Deleted relationships: {to_be_deleted}")
return to_be_deleted
def _delete_entities(self, to_be_deleted, user_id):
def _delete_entities(self, to_be_deleted, filters):
"""Delete the entities from the graph."""
user_id = filters["user_id"]
agent_id = filters.get("agent_id", None)
results = []
for item in to_be_deleted:
source = item["source"]
destination = item["destination"]
relationship = item["relationship"]
# Build the agent filter for the query
agent_filter = ""
params = {
"source_name": source,
"dest_name": destination,
"user_id": user_id,
}
if agent_id:
agent_filter = "AND n.agent_id = $agent_id AND m.agent_id = $agent_id"
params["agent_id"] = agent_id
# Delete the specific relationship between nodes
cypher = f"""
MATCH (n {self.node_label} {{name: $source_name, user_id: $user_id}})
-[r:{relationship}]->
(m {self.node_label} {{name: $dest_name, user_id: $user_id}})
WHERE 1=1 {agent_filter}
DELETE r
RETURN
n.name AS source,
m.name AS target,
type(r) AS relationship
"""
params = {
"source_name": source,
"dest_name": destination,
"user_id": user_id,
}
result = self.graph.query(cypher, params=params)
results.append(result)
return results
def _add_entities(self, to_be_added, user_id, entity_type_map):
def _add_entities(self, to_be_added, filters, entity_type_map):
"""Add the new entities to the graph. Merge the nodes if they already exist."""
user_id = filters["user_id"]
agent_id = filters.get("agent_id", None)
results = []
for item in to_be_added:
# entities
@@ -346,65 +396,80 @@ class MemoryGraph:
dest_embedding = self.embedding_model.embed(destination)
# search for the nodes with the closest embeddings
source_node_search_result = self._search_source_node(source_embedding, user_id, threshold=0.9)
destination_node_search_result = self._search_destination_node(dest_embedding, user_id, threshold=0.9)
source_node_search_result = self._search_source_node(source_embedding, filters, threshold=0.9)
destination_node_search_result = self._search_destination_node(dest_embedding, filters, threshold=0.9)
# TODO: Create a cypher query and common params for all the cases
if not destination_node_search_result and source_node_search_result:
cypher = f"""
MATCH (source)
WHERE elementId(source) = $source_id
SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
MERGE (destination {destination_label} {{name: $destination_name, user_id: $user_id}})
ON CREATE SET
destination.created = timestamp(),
destination.mentions = 1
{destination_extra_set}
ON MATCH SET
destination.mentions = coalesce(destination.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(destination, 'embedding', $destination_embedding)
WITH source, destination
MERGE (source)-[r:{relationship}]->(destination)
ON CREATE SET
r.created = timestamp(),
r.mentions = 1
ON MATCH SET
r.mentions = coalesce(r.mentions, 0) + 1
RETURN source.name AS source, type(r) AS relationship, destination.name AS target
"""
# Build destination MERGE properties
merge_props = ["name: $destination_name", "user_id: $user_id"]
if agent_id:
merge_props.append("agent_id: $agent_id")
merge_props_str = ", ".join(merge_props)
cypher = f"""
MATCH (source)
WHERE elementId(source) = $source_id
SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
MERGE (destination {destination_label} {{{merge_props_str}}})
ON CREATE SET
destination.created = timestamp(),
destination.mentions = 1
{destination_extra_set}
ON MATCH SET
destination.mentions = coalesce(destination.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(destination, 'embedding', $destination_embedding)
WITH source, destination
MERGE (source)-[r:{relationship}]->(destination)
ON CREATE SET
r.created = timestamp(),
r.mentions = 1
ON MATCH SET
r.mentions = coalesce(r.mentions, 0) + 1
RETURN source.name AS source, type(r) AS relationship, destination.name AS target
"""
params = {
"source_id": source_node_search_result[0]["elementId(source_candidate)"],
"destination_name": destination,
"destination_embedding": dest_embedding,
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
elif destination_node_search_result and not source_node_search_result:
# Build source MERGE properties
merge_props = ["name: $source_name", "user_id: $user_id"]
if agent_id:
merge_props.append("agent_id: $agent_id")
merge_props_str = ", ".join(merge_props)
cypher = f"""
MATCH (destination)
WHERE elementId(destination) = $destination_id
SET destination.mentions = coalesce(destination.mentions, 0) + 1
WITH destination
MERGE (source {source_label} {{name: $source_name, user_id: $user_id}})
ON CREATE SET
source.created = timestamp(),
source.mentions = 1
{source_extra_set}
ON MATCH SET
source.mentions = coalesce(source.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(source, 'embedding', $source_embedding)
WITH source, destination
MERGE (source)-[r:{relationship}]->(destination)
ON CREATE SET
r.created = timestamp(),
r.mentions = 1
ON MATCH SET
r.mentions = coalesce(r.mentions, 0) + 1
RETURN source.name AS source, type(r) AS relationship, destination.name AS target
"""
MATCH (destination)
WHERE elementId(destination) = $destination_id
SET destination.mentions = coalesce(destination.mentions, 0) + 1
WITH destination
MERGE (source {source_label} {{{merge_props_str}}})
ON CREATE SET
source.created = timestamp(),
source.mentions = 1
{source_extra_set}
ON MATCH SET
source.mentions = coalesce(source.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(source, 'embedding', $source_embedding)
WITH source, destination
MERGE (source)-[r:{relationship}]->(destination)
ON CREATE SET
r.created = timestamp(),
r.mentions = 1
ON MATCH SET
r.mentions = coalesce(r.mentions, 0) + 1
RETURN source.name AS source, type(r) AS relationship, destination.name AS target
"""
params = {
"destination_id": destination_node_search_result[0]["elementId(destination_candidate)"],
@@ -412,53 +477,68 @@ class MemoryGraph:
"source_embedding": source_embedding,
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
elif source_node_search_result and destination_node_search_result:
cypher = f"""
MATCH (source)
WHERE elementId(source) = $source_id
SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
MATCH (destination)
WHERE elementId(destination) = $destination_id
SET destination.mentions = coalesce(destination.mentions) + 1
MERGE (source)-[r:{relationship}]->(destination)
ON CREATE SET
r.created_at = timestamp(),
r.updated_at = timestamp(),
r.mentions = 1
ON MATCH SET r.mentions = coalesce(r.mentions, 0) + 1
RETURN source.name AS source, type(r) AS relationship, destination.name AS target
"""
MATCH (source)
WHERE elementId(source) = $source_id
SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
MATCH (destination)
WHERE elementId(destination) = $destination_id
SET destination.mentions = coalesce(destination.mentions, 0) + 1
MERGE (source)-[r:{relationship}]->(destination)
ON CREATE SET
r.created_at = timestamp(),
r.updated_at = timestamp(),
r.mentions = 1
ON MATCH SET r.mentions = coalesce(r.mentions, 0) + 1
RETURN source.name AS source, type(r) AS relationship, destination.name AS target
"""
params = {
"source_id": source_node_search_result[0]["elementId(source_candidate)"],
"destination_id": destination_node_search_result[0]["elementId(destination_candidate)"],
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
else:
# Build dynamic MERGE props for both source and destination
source_props = ["name: $source_name", "user_id: $user_id"]
dest_props = ["name: $dest_name", "user_id: $user_id"]
if agent_id:
source_props.append("agent_id: $agent_id")
dest_props.append("agent_id: $agent_id")
source_props_str = ", ".join(source_props)
dest_props_str = ", ".join(dest_props)
cypher = f"""
MERGE (source {source_label} {{name: $source_name, user_id: $user_id}})
ON CREATE SET source.created = timestamp(),
source.mentions = 1
{source_extra_set}
ON MATCH SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
CALL db.create.setNodeVectorProperty(source, 'embedding', $source_embedding)
WITH source
MERGE (destination {destination_label} {{name: $dest_name, user_id: $user_id}})
ON CREATE SET destination.created = timestamp(),
destination.mentions = 1
{destination_extra_set}
ON MATCH SET destination.mentions = coalesce(destination.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(destination, 'embedding', $source_embedding)
WITH source, destination
MERGE (source)-[rel:{relationship}]->(destination)
ON CREATE SET rel.created = timestamp(), rel.mentions = 1
ON MATCH SET rel.mentions = coalesce(rel.mentions, 0) + 1
RETURN source.name AS source, type(rel) AS relationship, destination.name AS target
"""
MERGE (source {source_label} {{{source_props_str}}})
ON CREATE SET source.created = timestamp(),
source.mentions = 1
{source_extra_set}
ON MATCH SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
CALL db.create.setNodeVectorProperty(source, 'embedding', $source_embedding)
WITH source
MERGE (destination {destination_label} {{{dest_props_str}}})
ON CREATE SET destination.created = timestamp(),
destination.mentions = 1
{destination_extra_set}
ON MATCH SET destination.mentions = coalesce(destination.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(destination, 'embedding', $dest_embedding)
WITH source, destination
MERGE (source)-[rel:{relationship}]->(destination)
ON CREATE SET rel.created = timestamp(), rel.mentions = 1
ON MATCH SET rel.mentions = coalesce(rel.mentions, 0) + 1
RETURN source.name AS source, type(rel) AS relationship, destination.name AS target
"""
params = {
"source_name": source,
"dest_name": destination,
@@ -466,6 +546,8 @@ class MemoryGraph:
"dest_embedding": dest_embedding,
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
result = self.graph.query(cypher, params=params)
results.append(result)
return results
@@ -477,11 +559,16 @@ class MemoryGraph:
item["destination"] = item["destination"].lower().replace(" ", "_")
return entity_list
def _search_source_node(self, source_embedding, user_id, threshold=0.9):
def _search_source_node(self, source_embedding, filters, threshold=0.9):
agent_filter = ""
if filters.get("agent_id"):
agent_filter = "AND source_candidate.agent_id = $agent_id"
cypher = f"""
MATCH (source_candidate {self.node_label})
WHERE source_candidate.embedding IS NOT NULL
AND source_candidate.user_id = $user_id
{agent_filter}
WITH source_candidate,
round(2 * vector.similarity.cosine(source_candidate.embedding, $source_embedding) - 1, 4) AS source_similarity // denormalize for backward compatibility
@@ -496,18 +583,26 @@ class MemoryGraph:
params = {
"source_embedding": source_embedding,
"user_id": user_id,
"user_id": filters["user_id"],
"threshold": threshold,
}
if filters.get("agent_id"):
params["agent_id"] = filters["agent_id"]
result = self.graph.query(cypher, params=params)
return result
def _search_destination_node(self, destination_embedding, user_id, threshold=0.9):
def _search_destination_node(self, destination_embedding, filters, threshold=0.9):
agent_filter = ""
if filters.get("agent_id"):
agent_filter = "AND destination_candidate.agent_id = $agent_id"
cypher = f"""
MATCH (destination_candidate {self.node_label})
WHERE destination_candidate.embedding IS NOT NULL
AND destination_candidate.user_id = $user_id
{agent_filter}
WITH destination_candidate,
round(2 * vector.similarity.cosine(destination_candidate.embedding, $destination_embedding) - 1, 4) AS destination_similarity // denormalize for backward compatibility
@@ -520,11 +615,14 @@ class MemoryGraph:
RETURN elementId(destination_candidate)
"""
params = {
"destination_embedding": destination_embedding,
"user_id": user_id,
"user_id": filters["user_id"],
"threshold": threshold,
}
if filters.get("agent_id"):
params["agent_id"] = filters["agent_id"]
result = self.graph.query(cypher, params=params)
return result
+45 -49
View File
@@ -28,8 +28,8 @@ from mem0.memory.utils import (
get_fact_retrieval_messages,
parse_messages,
parse_vision_messages,
remove_code_blocks,
process_telemetry_filters,
remove_code_blocks,
)
from mem0.utils.factory import EmbedderFactory, LlmFactory, VectorStoreFactory
@@ -338,10 +338,9 @@ class Memory(MemoryBase):
except Exception as e:
logging.error(f"Error in new_retrieved_facts: {e}")
new_retrieved_facts = []
if not new_retrieved_facts:
logger.debug("No new facts retrieved from input. Skipping memory update LLM call.")
return []
retrieved_old_memory = []
new_message_embeddings = {}
@@ -369,24 +368,27 @@ class Memory(MemoryBase):
temp_uuid_mapping[str(idx)] = item["id"]
retrieved_old_memory[idx]["id"] = str(idx)
function_calling_prompt = get_update_memory_messages(
retrieved_old_memory, new_retrieved_facts, self.config.custom_update_memory_prompt
)
try:
response: str = self.llm.generate_response(
messages=[{"role": "user", "content": function_calling_prompt}],
response_format={"type": "json_object"},
if new_retrieved_facts:
function_calling_prompt = get_update_memory_messages(
retrieved_old_memory, new_retrieved_facts, self.config.custom_update_memory_prompt
)
except Exception as e:
logging.error(f"Error in new memory actions response: {e}")
response = ""
try:
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response)
except Exception as e:
logging.error(f"Invalid JSON response: {e}")
try:
response: str = self.llm.generate_response(
messages=[{"role": "user", "content": function_calling_prompt}],
response_format={"type": "json_object"},
)
except Exception as e:
logging.error(f"Error in new memory actions response: {e}")
response = ""
try:
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response)
except Exception as e:
logging.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
else:
new_memories_with_actions = {}
returned_memories = []
@@ -1162,13 +1164,11 @@ class AsyncMemory(MemoryBase):
response = remove_code_blocks(response)
new_retrieved_facts = json.loads(response)["facts"]
except Exception as e:
new_retrieved_facts = []
if not new_retrieved_facts:
logger.info("No new facts retrieved from input. Skipping memory update LLM call.")
return []
logging.error(f"Error in new_retrieved_facts: {e}")
new_retrieved_facts = []
if not new_retrieved_facts:
logger.debug("No new facts retrieved from input. Skipping memory update LLM call.")
retrieved_old_memory = []
new_message_embeddings = {}
@@ -1200,31 +1200,25 @@ class AsyncMemory(MemoryBase):
temp_uuid_mapping[str(idx)] = item["id"]
retrieved_old_memory[idx]["id"] = str(idx)
function_calling_prompt = get_update_memory_messages(
retrieved_old_memory, new_retrieved_facts, self.config.custom_update_memory_prompt
)
try:
response = await asyncio.to_thread(
self.llm.generate_response,
messages=[{"role": "user", "content": function_calling_prompt}],
response_format={"type": "json_object"},
if new_retrieved_facts:
function_calling_prompt = get_update_memory_messages(
retrieved_old_memory, new_retrieved_facts, self.config.custom_update_memory_prompt
)
except Exception as e:
response = ""
logging.error(f"Error in new memory actions response: {e}")
response = ""
try:
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response)
except Exception as e:
new_memories_with_actions = {}
if not new_memories_with_actions:
logger.info("No new facts retrieved from input (async). Skipping memory update LLM call.")
return []
logging.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
try:
response = await asyncio.to_thread(
self.llm.generate_response,
messages=[{"role": "user", "content": function_calling_prompt}],
response_format={"type": "json_object"},
)
except Exception as e:
logging.error(f"Error in new memory actions response: {e}")
response = ""
try:
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response)
except Exception as e:
logging.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
returned_memories = []
try:
@@ -1421,7 +1415,9 @@ class AsyncMemory(MemoryBase):
async def _get_all_from_vector_store(self, filters, limit):
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit)
actual_memories = (
memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result
memories_result[0]
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0
else memories_result
)
promoted_payload_keys = [
+211 -86
View File
@@ -118,11 +118,19 @@ class MemoryGraph:
return search_results
def delete_all(self, filters):
cypher = """
MATCH (n {user_id: $user_id})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"]}
"""Delete all nodes and relationships for a user or specific agent."""
if filters.get("agent_id"):
cypher = """
MATCH (n:Entity {user_id: $user_id, agent_id: $agent_id})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"], "agent_id": filters["agent_id"]}
else:
cypher = """
MATCH (n:Entity {user_id: $user_id})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"]}
self.graph.query(cypher, params=params)
def get_all(self, filters, limit=100):
@@ -131,20 +139,31 @@ class MemoryGraph:
Args:
filters (dict): A dictionary containing filters to be applied during the retrieval.
Supports 'user_id' (required) and 'agent_id' (optional).
limit (int): The maximum number of nodes and relationships to retrieve. Defaults to 100.
Returns:
list: A list of dictionaries, each containing:
- 'contexts': The base data store response for each memory.
- 'entities': A list of strings representing the nodes and relationships
- 'source': The source node name.
- 'relationship': The relationship type.
- 'target': The target node name.
"""
# return all nodes and relationships
query = """
MATCH (n:Entity {user_id: $user_id})-[r]->(m:Entity {user_id: $user_id})
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit
"""
results = self.graph.query(query, params={"user_id": filters["user_id"], "limit": limit})
# Build query based on whether agent_id is provided
if filters.get("agent_id"):
query = """
MATCH (n:Entity {user_id: $user_id, agent_id: $agent_id})-[r]->(m:Entity {user_id: $user_id, agent_id: $agent_id})
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit
"""
params = {"user_id": filters["user_id"], "agent_id": filters["agent_id"], "limit": limit}
else:
query = """
MATCH (n:Entity {user_id: $user_id})-[r]->(m:Entity {user_id: $user_id})
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit
"""
params = {"user_id": filters["user_id"], "limit": limit}
results = self.graph.query(query, params=params)
final_results = []
for result in results:
@@ -241,33 +260,65 @@ class MemoryGraph:
for node in node_list:
n_embedding = self.embedding_model.embed(node)
cypher_query = """
MATCH (n:Entity {user_id: $user_id})-[r]->(m:Entity)
WHERE n.embedding IS NOT NULL
WITH collect(n) AS nodes1, collect(m) AS nodes2, r
CALL node_similarity.cosine_pairwise("embedding", nodes1, nodes2)
YIELD node1, node2, similarity
WITH node1, node2, similarity, r
WHERE similarity >= $threshold
RETURN node1.user_id AS source, id(node1) AS source_id, type(r) AS relationship, id(r) AS relation_id, node2.user_id AS destination, id(node2) AS destination_id, similarity
UNION
MATCH (n:Entity {user_id: $user_id})<-[r]-(m:Entity)
WHERE n.embedding IS NOT NULL
WITH collect(n) AS nodes1, collect(m) AS nodes2, r
CALL node_similarity.cosine_pairwise("embedding", nodes1, nodes2)
YIELD node1, node2, similarity
WITH node1, node2, similarity, r
WHERE similarity >= $threshold
RETURN node2.name AS source, id(node2) AS source_id, type(r) AS relationship, id(r) AS relation_id, node1.name AS destination, id(node1) AS destination_id, similarity
ORDER BY similarity DESC
LIMIT $limit;
"""
params = {
"n_embedding": n_embedding,
"threshold": self.threshold,
"user_id": filters["user_id"],
"limit": limit,
}
# Build query based on whether agent_id is provided
if filters.get("agent_id"):
cypher_query = """
MATCH (n:Entity {user_id: $user_id, agent_id: $agent_id})-[r]->(m:Entity)
WHERE n.embedding IS NOT NULL
WITH collect(n) AS nodes1, collect(m) AS nodes2, r
CALL node_similarity.cosine_pairwise("embedding", nodes1, nodes2)
YIELD node1, node2, similarity
WITH node1, node2, similarity, r
WHERE similarity >= $threshold
RETURN node1.name AS source, id(node1) AS source_id, type(r) AS relationship, id(r) AS relation_id, node2.name AS destination, id(node2) AS destination_id, similarity
UNION
MATCH (n:Entity {user_id: $user_id, agent_id: $agent_id})<-[r]-(m:Entity)
WHERE n.embedding IS NOT NULL
WITH collect(n) AS nodes1, collect(m) AS nodes2, r
CALL node_similarity.cosine_pairwise("embedding", nodes1, nodes2)
YIELD node1, node2, similarity
WITH node1, node2, similarity, r
WHERE similarity >= $threshold
RETURN node2.name AS source, id(node2) AS source_id, type(r) AS relationship, id(r) AS relation_id, node1.name AS destination, id(node1) AS destination_id, similarity
ORDER BY similarity DESC
LIMIT $limit;
"""
params = {
"n_embedding": n_embedding,
"threshold": self.threshold,
"user_id": filters["user_id"],
"agent_id": filters["agent_id"],
"limit": limit,
}
else:
cypher_query = """
MATCH (n:Entity {user_id: $user_id})-[r]->(m:Entity)
WHERE n.embedding IS NOT NULL
WITH collect(n) AS nodes1, collect(m) AS nodes2, r
CALL node_similarity.cosine_pairwise("embedding", nodes1, nodes2)
YIELD node1, node2, similarity
WITH node1, node2, similarity, r
WHERE similarity >= $threshold
RETURN node1.name AS source, id(node1) AS source_id, type(r) AS relationship, id(r) AS relation_id, node2.name AS destination, id(node2) AS destination_id, similarity
UNION
MATCH (n:Entity {user_id: $user_id})<-[r]-(m:Entity)
WHERE n.embedding IS NOT NULL
WITH collect(n) AS nodes1, collect(m) AS nodes2, r
CALL node_similarity.cosine_pairwise("embedding", nodes1, nodes2)
YIELD node1, node2, similarity
WITH node1, node2, similarity, r
WHERE similarity >= $threshold
RETURN node2.name AS source, id(node2) AS source_id, type(r) AS relationship, id(r) AS relation_id, node1.name AS destination, id(node1) AS destination_id, similarity
ORDER BY similarity DESC
LIMIT $limit;
"""
params = {
"n_embedding": n_embedding,
"threshold": self.threshold,
"user_id": filters["user_id"],
"limit": limit,
}
ans = self.graph.query(cypher_query, params=params)
result_relations.extend(ans)
@@ -300,38 +351,54 @@ class MemoryGraph:
logger.debug(f"Deleted relationships: {to_be_deleted}")
return to_be_deleted
def _delete_entities(self, to_be_deleted, user_id):
def _delete_entities(self, to_be_deleted, filters):
"""Delete the entities from the graph."""
user_id = filters["user_id"]
agent_id = filters.get("agent_id", None)
results = []
for item in to_be_deleted:
source = item["source"]
destination = item["destination"]
relationship = item["relationship"]
# Build the agent filter for the query
agent_filter = ""
params = {
"source_name": source,
"dest_name": destination,
"user_id": user_id,
}
if agent_id:
agent_filter = "AND n.agent_id = $agent_id AND m.agent_id = $agent_id"
params["agent_id"] = agent_id
# Delete the specific relationship between nodes
cypher = f"""
MATCH (n:Entity {{name: $source_name, user_id: $user_id}})
-[r:{relationship}]->
(m {{name: $dest_name, user_id: $user_id}})
(m:Entity {{name: $dest_name, user_id: $user_id}})
WHERE 1=1 {agent_filter}
DELETE r
RETURN
n.name AS source,
m.name AS target,
type(r) AS relationship
"""
params = {
"source_name": source,
"dest_name": destination,
"user_id": user_id,
}
result = self.graph.query(cypher, params=params)
results.append(result)
return results
# added Entity label to all nodes for vector search to work
def _add_entities(self, to_be_added, user_id, entity_type_map):
def _add_entities(self, to_be_added, filters, entity_type_map):
"""Add the new entities to the graph. Merge the nodes if they already exist."""
user_id = filters["user_id"]
agent_id = filters.get("agent_id", None)
results = []
for item in to_be_added:
# entities
source = item["source"]
@@ -346,18 +413,21 @@ class MemoryGraph:
source_embedding = self.embedding_model.embed(source)
dest_embedding = self.embedding_model.embed(destination)
# search for the nodes with the closest embeddings; this is basically
# comparison of one embedding to all embeddings in a graph -> vector
# search with cosine similarity metric
source_node_search_result = self._search_source_node(source_embedding, user_id, threshold=0.9)
destination_node_search_result = self._search_destination_node(dest_embedding, user_id, threshold=0.9)
# search for the nodes with the closest embeddings
source_node_search_result = self._search_source_node(source_embedding, filters, threshold=0.9)
destination_node_search_result = self._search_destination_node(dest_embedding, filters, threshold=0.9)
# Prepare agent_id for node creation
agent_id_clause = ""
if agent_id:
agent_id_clause = ", agent_id: $agent_id"
# TODO: Create a cypher query and common params for all the cases
if not destination_node_search_result and source_node_search_result:
cypher = f"""
MATCH (source:Entity)
WHERE id(source) = $source_id
MERGE (destination:{destination_type}:Entity {{name: $destination_name, user_id: $user_id}})
MERGE (destination:{destination_type}:Entity {{name: $destination_name, user_id: $user_id{agent_id_clause}}})
ON CREATE SET
destination.created = timestamp(),
destination.embedding = $destination_embedding,
@@ -374,11 +444,14 @@ class MemoryGraph:
"destination_embedding": dest_embedding,
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
elif destination_node_search_result and not source_node_search_result:
cypher = f"""
MATCH (destination:Entity)
WHERE id(destination) = $destination_id
MERGE (source:{source_type}:Entity {{name: $source_name, user_id: $user_id}})
MERGE (source:{source_type}:Entity {{name: $source_name, user_id: $user_id{agent_id_clause}}})
ON CREATE SET
source.created = timestamp(),
source.embedding = $source_embedding,
@@ -395,6 +468,9 @@ class MemoryGraph:
"source_embedding": source_embedding,
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
elif source_node_search_result and destination_node_search_result:
cypher = f"""
MATCH (source:Entity)
@@ -412,12 +488,15 @@ class MemoryGraph:
"destination_id": destination_node_search_result[0]["id(destination_candidate)"],
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
else:
cypher = f"""
MERGE (n:{source_type}:Entity {{name: $source_name, user_id: $user_id}})
MERGE (n:{source_type}:Entity {{name: $source_name, user_id: $user_id{agent_id_clause}}})
ON CREATE SET n.created = timestamp(), n.embedding = $source_embedding, n:Entity
ON MATCH SET n.embedding = $source_embedding
MERGE (m:{destination_type}:Entity {{name: $dest_name, user_id: $user_id}})
MERGE (m:{destination_type}:Entity {{name: $dest_name, user_id: $user_id{agent_id_clause}}})
ON CREATE SET m.created = timestamp(), m.embedding = $dest_embedding, m:Entity
ON MATCH SET m.embedding = $dest_embedding
MERGE (n)-[rel:{relationship}]->(m)
@@ -431,6 +510,9 @@ class MemoryGraph:
"dest_embedding": dest_embedding,
"user_id": user_id,
}
if agent_id:
params["agent_id"] = agent_id
result = self.graph.query(cypher, params=params)
results.append(result)
return results
@@ -442,37 +524,80 @@ class MemoryGraph:
item["destination"] = item["destination"].lower().replace(" ", "_")
return entity_list
def _search_source_node(self, source_embedding, user_id, threshold=0.9):
cypher = """
CALL vector_search.search("memzero", 1, $source_embedding)
YIELD distance, node, similarity
WITH node AS source_candidate, similarity
WHERE source_candidate.user_id = $user_id AND similarity >= $threshold
RETURN id(source_candidate);
"""
params = {
"source_embedding": source_embedding,
"user_id": user_id,
"threshold": threshold,
}
def _search_source_node(self, source_embedding, filters, threshold=0.9):
"""Search for source nodes with similar embeddings."""
user_id = filters["user_id"]
agent_id = filters.get("agent_id", None)
if agent_id:
cypher = """
CALL vector_search.search("memzero", 1, $source_embedding)
YIELD distance, node, similarity
WITH node AS source_candidate, similarity
WHERE source_candidate.user_id = $user_id
AND source_candidate.agent_id = $agent_id
AND similarity >= $threshold
RETURN id(source_candidate);
"""
params = {
"source_embedding": source_embedding,
"user_id": user_id,
"agent_id": agent_id,
"threshold": threshold,
}
else:
cypher = """
CALL vector_search.search("memzero", 1, $source_embedding)
YIELD distance, node, similarity
WITH node AS source_candidate, similarity
WHERE source_candidate.user_id = $user_id
AND similarity >= $threshold
RETURN id(source_candidate);
"""
params = {
"source_embedding": source_embedding,
"user_id": user_id,
"threshold": threshold,
}
result = self.graph.query(cypher, params=params)
return result
def _search_destination_node(self, destination_embedding, user_id, threshold=0.9):
cypher = """
CALL vector_search.search("memzero", 1, $destination_embedding)
YIELD distance, node, similarity
WITH node AS destination_candidate, similarity
WHERE node.user_id = $user_id AND similarity >= $threshold
RETURN id(destination_candidate);
"""
params = {
"destination_embedding": destination_embedding,
"user_id": user_id,
"threshold": threshold,
}
def _search_destination_node(self, destination_embedding, filters, threshold=0.9):
"""Search for destination nodes with similar embeddings."""
user_id = filters["user_id"]
agent_id = filters.get("agent_id", None)
if agent_id:
cypher = """
CALL vector_search.search("memzero", 1, $destination_embedding)
YIELD distance, node, similarity
WITH node AS destination_candidate, similarity
WHERE node.user_id = $user_id
AND node.agent_id = $agent_id
AND similarity >= $threshold
RETURN id(destination_candidate);
"""
params = {
"destination_embedding": destination_embedding,
"user_id": user_id,
"agent_id": agent_id,
"threshold": threshold,
}
else:
cypher = """
CALL vector_search.search("memzero", 1, $destination_embedding)
YIELD distance, node, similarity
WITH node AS destination_candidate, similarity
WHERE node.user_id = $user_id
AND similarity >= $threshold
RETURN id(destination_candidate);
"""
params = {
"destination_embedding": destination_embedding,
"user_id": user_id,
"threshold": threshold,
}
result = self.graph.query(cypher, params=params)
return result
+14
View File
@@ -46,6 +46,20 @@ def remove_code_blocks(content: str) -> str:
return match.group(1).strip() if match else content.strip()
def extract_json(text):
"""
Extracts JSON content from a string, removing enclosing triple backticks and optional 'json' tag if present.
If no code block is found, returns the text as-is.
"""
text = text.strip()
match = re.search(r"```(?:json)?\s*(.*?)\s*```", text, re.DOTALL)
if match:
json_str = match.group(1)
else:
json_str = text # assume it's raw JSON
return json_str
def get_image_description(image_obj, llm, vision_details):
"""
Get the description of the image
+2
View File
@@ -29,6 +29,7 @@ class LlmFactory:
"xai": "mem0.llms.xai.XAILLM",
"sarvam": "mem0.llms.sarvam.SarvamLLM",
"lmstudio": "mem0.llms.lmstudio.LMStudioLLM",
"vllm": "mem0.llms.vllm.VllmLLM",
"langchain": "mem0.llms.langchain.LangchainLLM",
}
@@ -79,6 +80,7 @@ class VectorStoreFactory:
"upstash_vector": "mem0.vector_stores.upstash_vector.UpstashVector",
"azure_ai_search": "mem0.vector_stores.azure_ai_search.AzureAISearch",
"pinecone": "mem0.vector_stores.pinecone.PineconeDB",
"mongodb": "mem0.vector_stores.mongodb.MongoDB",
"redis": "mem0.vector_stores.redis.RedisDB",
"elasticsearch": "mem0.vector_stores.elasticsearch.ElasticsearchDB",
"vertex_ai_vector_search": "mem0.vector_stores.vertex_ai_vector_search.GoogleMatchingEngine",
+5 -3
View File
@@ -6,6 +6,7 @@ from typing import List, Optional
from pydantic import BaseModel
from mem0.vector_stores.base import VectorStoreBase
from mem0.memory.utils import extract_json
try:
from azure.core.credentials import AzureKeyCredential
@@ -233,7 +234,7 @@ class AzureAISearch(VectorStoreBase):
results = []
for result in search_results:
payload = json.loads(result["payload"])
payload = json.loads(extract_json(result["payload"]))
results.append(OutputData(id=result["id"], score=result["@search.score"], payload=payload))
return results
@@ -288,7 +289,8 @@ class AzureAISearch(VectorStoreBase):
result = self.search_client.get_document(key=vector_id)
except ResourceNotFoundError:
return None
return OutputData(id=result["id"], score=None, payload=json.loads(result["payload"]))
payload = json.loads(extract_json(result["payload"]))
return OutputData(id=result["id"], score=None, payload=payload)
def list_cols(self) -> List[str]:
"""
@@ -335,7 +337,7 @@ class AzureAISearch(VectorStoreBase):
search_results = self.search_client.search(search_text="*", filter=filter_expression, top=limit)
results = []
for result in search_results:
payload = json.loads(result["payload"])
payload = json.loads(extract_json(result["payload"]))
results.append(OutputData(id=result["id"], score=result["@search.score"], payload=payload))
return [results]
+349
View File
@@ -0,0 +1,349 @@
import logging
import time
from typing import Dict, Optional
from pydantic import BaseModel
from mem0.vector_stores.base import VectorStoreBase
try:
import pymochow
from pymochow.configuration import Configuration
from pymochow.auth.bce_credentials import BceCredentials
from pymochow.model.enum import FieldType, MetricType, IndexType, TableState, ServerErrCode
from pymochow.model.schema import Field, Schema, VectorIndex, FilteringIndex, HNSWParams, AutoBuildRowCountIncrement
from pymochow.model.table import Partition, Row, VectorSearchConfig, VectorTopkSearchRequest, FloatVector
from pymochow.exception import ServerError
except ImportError:
raise ImportError("The 'pymochow' library is required. Please install it using 'pip install pymochow'.")
logger = logging.getLogger(__name__)
class OutputData(BaseModel):
id: Optional[str] # memory id
score: Optional[float] # distance
payload: Optional[Dict] # metadata
class BaiduDB(VectorStoreBase):
def __init__(
self,
endpoint: str,
account: str,
api_key: str,
database_name: str,
table_name: str,
embedding_model_dims: int,
metric_type: MetricType,
) -> None:
"""Initialize the BaiduDB database.
Args:
endpoint (str): Endpoint URL for Baidu VectorDB.
account (str): Account for Baidu VectorDB.
api_key (str): API Key for Baidu VectorDB.
database_name (str): Name of the database.
table_name (str): Name of the table.
embedding_model_dims (int): Dimensions of the embedding model.
metric_type (MetricType): Metric type for similarity search.
"""
self.endpoint = endpoint
self.account = account
self.api_key = api_key
self.database_name = database_name
self.table_name = table_name
self.embedding_model_dims = embedding_model_dims
self.metric_type = metric_type
# Initialize Mochow client
config = Configuration(credentials=BceCredentials(account, api_key), endpoint=endpoint)
self.client = pymochow.MochowClient(config)
# Ensure database and table exist
self._create_database_if_not_exists()
self.create_col(
name=self.table_name,
vector_size=self.embedding_model_dims,
distance=self.metric_type,
)
def _create_database_if_not_exists(self):
"""Create database if it doesn't exist."""
try:
# Check if database exists
databases = self.client.list_databases()
db_exists = any(db.database_name == self.database_name for db in databases)
if not db_exists:
self._database = self.client.create_database(self.database_name)
logger.info(f"Created database: {self.database_name}")
else:
self._database = self.client.database(self.database_name)
logger.info(f"Database {self.database_name} already exists")
except Exception as e:
logger.error(f"Error creating database: {e}")
raise
def create_col(self, name, vector_size, distance):
"""Create a new table.
Args:
name (str): Name of the table to create.
vector_size (int): Dimension of the vector.
distance (str): Metric type for similarity search.
"""
# Check if table already exists
try:
tables = self._database.list_table()
table_exists = any(table.table_name == name for table in tables)
if table_exists:
logger.info(f"Table {name} already exists. Skipping creation.")
self._table = self._database.describe_table(name)
return
# Convert distance string to MetricType enum
metric_type = None
for k, v in MetricType.__members__.items():
if k == distance:
metric_type = v
if metric_type is None:
raise ValueError(f"Unsupported metric_type: {distance}")
# Define table schema
fields = [
Field(
"id", FieldType.STRING, primary_key=True, partition_key=True, auto_increment=False, not_null=True
),
Field("vector", FieldType.FLOAT_VECTOR, dimension=vector_size),
Field("metadata", FieldType.JSON),
]
# Create vector index
indexes = [
VectorIndex(
index_name="vector_idx",
index_type=IndexType.HNSW,
field="vector",
metric_type=metric_type,
params=HNSWParams(m=16, efconstruction=200),
auto_build=True,
auto_build_index_policy=AutoBuildRowCountIncrement(row_count_increment=10000),
),
FilteringIndex(index_name="metadata_filtering_idx", fields=["metadata"]),
]
schema = Schema(fields=fields, indexes=indexes)
# Create table
self._table = self._database.create_table(
table_name=name, replication=3, partition=Partition(partition_num=1), schema=schema
)
logger.info(f"Created table: {name}")
# Wait for table to be ready
while True:
time.sleep(2)
table = self._database.describe_table(name)
if table.state == TableState.NORMAL:
logger.info(f"Table {name} is ready.")
break
logger.info(f"Waiting for table {name} to be ready, current state: {table.state}")
self._table = table
except Exception as e:
logger.error(f"Error creating table: {e}")
raise
def insert(self, vectors, payloads=None, ids=None):
"""Insert vectors into the table.
Args:
vectors (List[List[float]]): List of vectors to insert.
payloads (List[Dict], optional): List of payloads corresponding to vectors.
ids (List[str], optional): List of IDs corresponding to vectors.
"""
# Prepare data for insertion
for idx, vector, metadata in zip(ids, vectors, payloads):
row = Row(id=idx, vector=vector, metadata=metadata)
self._table.upsert(rows=[row])
def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None) -> list:
"""
Search for similar vectors.
Args:
query (str): Query string.
vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None.
Returns:
list: Search results.
"""
# Add filters if provided
search_filter = None
if filters:
search_filter = self._create_filter(filters)
# Create AnnSearch for vector search
request = VectorTopkSearchRequest(
vector_field="vector",
vector=FloatVector(vectors),
limit=limit,
filter=search_filter,
config=VectorSearchConfig(ef=200),
)
# Perform search
projections = ["id", "metadata"]
res = self._table.vector_search(request=request, projections=projections)
# Parse results
output = []
for row in res.rows:
row_data = row.get("row", {})
output_data = OutputData(
id=row_data.get("id"), score=row.get("score", 0.0), payload=row_data.get("metadata", {})
)
output.append(output_data)
return output
def delete(self, vector_id):
"""
Delete a vector by ID.
Args:
vector_id (str): ID of the vector to delete.
"""
self._table.delete(primary_key={"id": vector_id})
def update(self, vector_id=None, vector=None, payload=None):
"""
Update a vector and its payload.
Args:
vector_id (str): ID of the vector to update.
vector (List[float], optional): Updated vector.
payload (Dict, optional): Updated payload.
"""
row = Row(id=vector_id, vector=vector, metadata=payload)
self._table.upsert(rows=[row])
def get(self, vector_id):
"""
Retrieve a vector by ID.
Args:
vector_id (str): ID of the vector to retrieve.
Returns:
OutputData: Retrieved vector.
"""
projections = ["id", "metadata"]
result = self._table.query(primary_key={"id": vector_id}, projections=projections)
row = result.row
return OutputData(id=row.get("id"), score=None, payload=row.get("metadata", {}))
def list_cols(self):
"""
List all tables (collections).
Returns:
List[str]: List of table names.
"""
tables = self._database.list_table()
return [table.table_name for table in tables]
def delete_col(self):
"""Delete the table."""
try:
tables = self._database.list_table()
# skip drop table if table not exists
table_exists = any(table.table_name == self.table_name for table in tables)
if not table_exists:
logger.info(f"Table {self.table_name} does not exist, skipping deletion")
return
# Delete the table
self._database.drop_table(self.table_name)
logger.info(f"Initiated deletion of table {self.table_name}")
# Wait for table to be completely deleted
while True:
time.sleep(2)
try:
self._database.describe_table(self.table_name)
logger.info(f"Waiting for table {self.table_name} to be deleted...")
except ServerError as e:
if e.code == ServerErrCode.TABLE_NOT_EXIST:
logger.info(f"Table {self.table_name} has been completely deleted")
break
logger.error(f"Error checking table status: {e}")
raise
except Exception as e:
logger.error(f"Error deleting table: {e}")
raise
def col_info(self):
"""
Get information about the table.
Returns:
Dict[str, Any]: Table information.
"""
return self._table.stats()
def list(self, filters: dict = None, limit: int = 100) -> list:
"""
List all vectors in the table.
Args:
filters (Dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return. Defaults to 100.
Returns:
List[OutputData]: List of vectors.
"""
projections = ["id", "metadata"]
list_filter = self._create_filter(filters) if filters else None
result = self._table.select(filter=list_filter, projections=projections, limit=limit)
memories = []
for row in result.rows:
obj = OutputData(id=row.get("id"), score=None, payload=row.get("metadata", {}))
memories.append(obj)
return [memories]
def reset(self):
"""Reset the table by deleting and recreating it."""
logger.warning(f"Resetting table {self.table_name}...")
try:
self.delete_col()
self.create_col(
name=self.table_name,
vector_size=self.embedding_model_dims,
distance=self.metric_type,
)
except Exception as e:
logger.warning(f"Error resetting table: {e}")
raise
def _create_filter(self, filters: dict) -> str:
"""
Create filter expression for queries.
Args:
filters (dict): Filter conditions.
Returns:
str: Filter expression.
"""
conditions = []
for key, value in filters.items():
if isinstance(value, str):
conditions.append(f'metadata["{key}"] = "{value}"')
else:
conditions.append(f'metadata["{key}"] = {value}')
return " AND ".join(conditions)
+2
View File
@@ -15,7 +15,9 @@ class VectorStoreConfig(BaseModel):
"chroma": "ChromaDbConfig",
"pgvector": "PGVectorConfig",
"pinecone": "PineconeConfig",
"mongodb": "MongoDBConfig",
"milvus": "MilvusDBConfig",
"baidu": "BaiduDBConfig",
"upstash_vector": "UpstashVectorConfig",
"azure_ai_search": "AzureAISearchConfig",
"redis": "RedisDBConfig",
+299
View File
@@ -0,0 +1,299 @@
import logging
from typing import List, Optional, Dict, Any, Callable
from pydantic import BaseModel
try:
from pymongo import MongoClient
from pymongo.operations import SearchIndexModel
from pymongo.errors import PyMongoError
except ImportError:
raise ImportError("The 'pymongo' library is required. Please install it using 'pip install pymongo'.")
from mem0.vector_stores.base import VectorStoreBase
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
class OutputData(BaseModel):
id: Optional[str]
score: Optional[float]
payload: Optional[dict]
class MongoVector(VectorStoreBase):
VECTOR_TYPE = "knnVector"
SIMILARITY_METRIC = "cosine"
def __init__(
self,
db_name: str,
collection_name: str,
embedding_model_dims: int,
mongo_uri: str
):
"""
Initialize the MongoDB vector store with vector search capabilities.
Args:
db_name (str): Database name
collection_name (str): Collection name
embedding_model_dims (int): Dimension of the embedding vector
mongo_uri (str): MongoDB connection URI
"""
self.collection_name = collection_name
self.embedding_model_dims = embedding_model_dims
self.db_name = db_name
self.client = MongoClient(
mongo_uri
)
self.db = self.client[db_name]
self.collection = self.create_col()
def create_col(self):
"""Create new collection with vector search index."""
try:
database = self.client[self.db_name]
collection_names = database.list_collection_names()
if self.collection_name not in collection_names:
logger.info(f"Collection '{self.collection_name}' does not exist. Creating it now.")
collection = database[self.collection_name]
# Insert and remove a placeholder document to create the collection
collection.insert_one({"_id": 0, "placeholder": True})
collection.delete_one({"_id": 0})
logger.info(f"Collection '{self.collection_name}' created successfully.")
else:
collection = database[self.collection_name]
self.index_name = f"{self.collection_name}_vector_index"
found_indexes = list(collection.list_search_indexes(name=self.index_name))
if found_indexes:
logger.info(f"Search index '{self.index_name}' already exists in collection '{self.collection_name}'.")
else:
search_index_model = SearchIndexModel(
name=self.index_name,
definition={
"mappings": {
"dynamic": False,
"fields": {
"embedding": {
"type": self.VECTOR_TYPE,
"dimensions": self.embedding_model_dims,
"similarity": self.SIMILARITY_METRIC,
}
},
}
},
)
collection.create_search_index(search_index_model)
logger.info(
f"Search index '{self.index_name}' created successfully for collection '{self.collection_name}'."
)
return collection
except PyMongoError as e:
logger.error(f"Error creating collection and search index: {e}")
return None
def insert(
self, vectors: List[List[float]], payloads: Optional[List[Dict]] = None, ids: Optional[List[str]] = None
) -> None:
"""
Insert vectors into the collection.
Args:
vectors (List[List[float]]): List of vectors to insert.
payloads (List[Dict], optional): List of payloads corresponding to vectors.
ids (List[str], optional): List of IDs corresponding to vectors.
"""
logger.info(f"Inserting {len(vectors)} vectors into collection '{self.collection_name}'.")
data = []
for vector, payload, _id in zip(vectors, payloads or [{}] * len(vectors), ids or [None] * len(vectors)):
document = {"_id": _id, "embedding": vector, "payload": payload}
data.append(document)
try:
self.collection.insert_many(data)
logger.info(f"Inserted {len(data)} documents into '{self.collection_name}'.")
except PyMongoError as e:
logger.error(f"Error inserting data: {e}")
def search(self, query: str, query_vector: List[float], limit=5, filters: Optional[Dict] = None) -> List[OutputData]:
"""
Search for similar vectors using the vector search index.
Args:
query (str): Query string
query_vector (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search.
Returns:
List[OutputData]: Search results.
"""
found_indexes = list(self.collection.list_search_indexes(name=self.index_name))
if not found_indexes:
logger.error(f"Index '{self.index_name}' does not exist.")
return []
results = []
try:
collection = self.client[self.db_name][self.collection_name]
pipeline = [
{
"$vectorSearch": {
"index": self.index_name,
"limit": limit,
"numCandidates": limit,
"queryVector": query_vector,
"path": "embedding",
}
},
{"$set": {"score": {"$meta": "vectorSearchScore"}}},
{"$project": {"embedding": 0}},
]
results = list(collection.aggregate(pipeline))
logger.info(f"Vector search completed. Found {len(results)} documents.")
except Exception as e:
logger.error(f"Error during vector search for query {query}: {e}")
return []
output = [OutputData(id=str(doc["_id"]), score=doc.get("score"), payload=doc.get("payload")) for doc in results]
return output
def delete(self, vector_id: str) -> None:
"""
Delete a vector by ID.
Args:
vector_id (str): ID of the vector to delete.
"""
try:
result = self.collection.delete_one({"_id": vector_id})
if result.deleted_count > 0:
logger.info(f"Deleted document with ID '{vector_id}'.")
else:
logger.warning(f"No document found with ID '{vector_id}' to delete.")
except PyMongoError as e:
logger.error(f"Error deleting document: {e}")
def update(self, vector_id: str, vector: Optional[List[float]] = None, payload: Optional[Dict] = None) -> None:
"""
Update a vector and its payload.
Args:
vector_id (str): ID of the vector to update.
vector (List[float], optional): Updated vector.
payload (Dict, optional): Updated payload.
"""
update_fields = {}
if vector is not None:
update_fields["embedding"] = vector
if payload is not None:
update_fields["payload"] = payload
if update_fields:
try:
result = self.collection.update_one({"_id": vector_id}, {"$set": update_fields})
if result.matched_count > 0:
logger.info(f"Updated document with ID '{vector_id}'.")
else:
logger.warning(f"No document found with ID '{vector_id}' to update.")
except PyMongoError as e:
logger.error(f"Error updating document: {e}")
def get(self, vector_id: str) -> Optional[OutputData]:
"""
Retrieve a vector by ID.
Args:
vector_id (str): ID of the vector to retrieve.
Returns:
Optional[OutputData]: Retrieved vector or None if not found.
"""
try:
doc = self.collection.find_one({"_id": vector_id})
if doc:
logger.info(f"Retrieved document with ID '{vector_id}'.")
return OutputData(id=str(doc["_id"]), score=None, payload=doc.get("payload"))
else:
logger.warning(f"Document with ID '{vector_id}' not found.")
return None
except PyMongoError as e:
logger.error(f"Error retrieving document: {e}")
return None
def list_cols(self) -> List[str]:
"""
List all collections in the database.
Returns:
List[str]: List of collection names.
"""
try:
collections = self.db.list_collection_names()
logger.info(f"Listing collections in database '{self.db_name}': {collections}")
return collections
except PyMongoError as e:
logger.error(f"Error listing collections: {e}")
return []
def delete_col(self) -> None:
"""Delete the collection."""
try:
self.collection.drop()
logger.info(f"Deleted collection '{self.collection_name}'.")
except PyMongoError as e:
logger.error(f"Error deleting collection: {e}")
def col_info(self) -> Dict[str, Any]:
"""
Get information about the collection.
Returns:
Dict[str, Any]: Collection information.
"""
try:
stats = self.db.command("collstats", self.collection_name)
info = {"name": self.collection_name, "count": stats.get("count"), "size": stats.get("size")}
logger.info(f"Collection info: {info}")
return info
except PyMongoError as e:
logger.error(f"Error getting collection info: {e}")
return {}
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]:
"""
List vectors in the collection.
Args:
filters (Dict, optional): Filters to apply to the list.
limit (int, optional): Number of vectors to return.
Returns:
List[OutputData]: List of vectors.
"""
try:
query = filters or {}
cursor = self.collection.find(query).limit(limit)
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}'.")
return results
except PyMongoError as e:
logger.error(f"Error listing documents: {e}")
return []
def reset(self):
"""Reset the index by deleting and recreating it."""
logger.warning(f"Resetting index {self.collection_name}...")
self.delete_col()
self.collection = self.create_col(self.collection_name)
def __del__(self) -> None:
"""Close the database connection when the object is deleted."""
if hasattr(self, "client"):
self.client.close()
logger.info("MongoClient connection closed.")
+3 -3
View File
@@ -84,18 +84,18 @@ class OpenSearchDB(VectorStoreBase):
}
if not self.client.indices.exists(index=name):
logger.warning(f"Creating index {name}, it might take 1-2 minutes...")
self.client.indices.create(index=name, body=index_settings)
logger.info(f"Created index {name}")
# Wait for index to be ready
max_retries = 60 # 60 seconds timeout
max_retries = 180 # 3 minutes timeout
retry_count = 0
while retry_count < max_retries:
try:
# Check if index is ready by attempting a simple search
self.client.search(index=name, body={"query": {"match_all": {}}})
logger.info(f"Index {name} is ready")
time.sleep(1)
logger.info(f"Index {name} is ready")
return
except Exception:
retry_count += 1
+1 -2
View File
@@ -5,8 +5,7 @@ from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel
try:
from pinecone import Pinecone, PodSpec, ServerlessSpec
from pinecone.data.dataclasses.vector import Vector
from pinecone import Pinecone, PodSpec, ServerlessSpec, Vector
except ImportError:
raise ImportError(
"Pinecone requires extra dependencies. Install with `pip install pinecone pinecone-text`"
+4 -3
View File
@@ -12,6 +12,7 @@ from redisvl.query import VectorQuery
from redisvl.query.filter import Tag
from mem0.vector_stores.base import VectorStoreBase
from mem0.memory.utils import extract_json
logger = logging.getLogger(__name__)
@@ -175,7 +176,7 @@ class RedisDB(VectorStoreBase):
else {}
),
**{field: result[field] for field in ["agent_id", "run_id", "user_id"] if field in result},
**{k: v for k, v in json.loads(result["metadata"]).items()},
**{k: v for k, v in json.loads(extract_json(result["metadata"])).items()},
},
)
for result in results
@@ -219,7 +220,7 @@ class RedisDB(VectorStoreBase):
else {}
),
**{field: result[field] for field in ["agent_id", "run_id", "user_id"] if field in result},
**{k: v for k, v in json.loads(result["metadata"]).items()},
**{k: v for k, v in json.loads(extract_json(result["metadata"])).items()},
}
return MemoryResult(id=result["memory_id"], payload=payload)
@@ -286,7 +287,7 @@ class RedisDB(VectorStoreBase):
for field in ["agent_id", "run_id", "user_id"]
if field in result.__dict__
},
**{k: v for k, v in json.loads(result["metadata"]).items()},
**{k: v for k, v in json.loads(extract_json(result["metadata"])).items()},
},
)
for result in results.docs
+2 -1
View File
@@ -10,4 +10,5 @@ node_modules/
*.log
api/.openmemory*
**/.next
.openmemory/
.openmemory/
ui/package-lock.json
+10
View File
@@ -96,6 +96,16 @@ pnpm install
pnpm dev
```
### MCP Client Setup
Use the following one step command to configure OpenMemory Local MCP to a client. The general command format is as follows:
```bash
npx @openmemory/install local http://localhost:8765/mcp/<client-name>/sse/<user-id> --client <client-name>
```
Replace `<client-name>` with the desired client name and `<user-id>` with the value specified in your environment variables.
## Project Structure
+2 -3
View File
@@ -24,11 +24,10 @@ def get_categories_for_memory(memory: str) -> List[str]:
]
# Let OpenAI handle the pydantic parsing directly
completion = openai_client.chat.completions.with_response_format(
response_format=MemoryCategories
).create(
completion = openai_client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=messages,
response_format=MemoryCategories,
temperature=0
)
+1
View File
@@ -7,6 +7,7 @@ psycopg2-binary>=2.9.0
python-multipart>=0.0.5
fastapi-pagination>=0.12.0
mem0ai>=0.1.92
openai>=1.40.0
mcp[cli]>=1.3.0
pytest>=7.0.0
pytest-asyncio>=0.21.0
Executable → Regular
View File
@@ -14,6 +14,7 @@ const clientTabs = [
{ key: "windsurf", label: "Windsurf", icon: "/images/windsurf.png" },
{ key: "witsy", label: "Witsy", icon: "/images/witsy.png" },
{ key: "enconvo", label: "Enconvo", icon: "/images/enconvo.png" },
{ key: "augment", label: "Augment", icon: "/images/augment.png" },
];
const colorGradientMap: { [key: string]: string } = {
@@ -51,7 +52,7 @@ export const Install = () => {
const handleCopy = async (tab: string, isMcp: boolean = false) => {
const text = isMcp
? `${URL}/mcp/openmemory/sse/${user}`
: `npx install-mcp i ${URL}/mcp/${tab}/sse/${user} --client ${tab}`;
: `npx @openmemory/install local ${URL}/mcp/${tab}/sse/${user} --client ${tab}`;
try {
// Try using the Clipboard API first
@@ -95,7 +96,7 @@ export const Install = () => {
</div>
<Tabs defaultValue="claude" className="w-full">
<TabsList className="bg-transparent border-b border-zinc-800 rounded-none w-full justify-start gap-0 p-0 grid grid-cols-8">
<TabsList className="bg-transparent border-b border-zinc-800 rounded-none w-full justify-start gap-0 p-0 grid grid-cols-9">
{allTabs.map(({ key, label, icon }) => (
<TabsTrigger
key={key}
@@ -167,7 +168,7 @@ export const Install = () => {
<div className="relative">
<pre className="bg-zinc-800 px-4 py-3 rounded-md overflow-x-auto text-sm">
<code className="text-gray-300">
{`npx install-mcp i ${URL}/mcp/${key}/sse/${user} --client ${key}`}
{`npx @openmemory/install local ${URL}/mcp/${key}/sse/${user} --client ${key}`}
</code>
</pre>
<div>
+33 -33
View File
@@ -94,36 +94,36 @@ export function FormView({ settings, onChange }: FormViewProps) {
const isLlmOllama = settings.mem0?.llm?.provider?.toLowerCase() === "ollama"
const isEmbedderOllama = settings.mem0?.embedder?.provider?.toLowerCase() === "ollama"
const LLM_PROVIDERS = [
"OpenAI",
"Anthropic",
"Azure OpenAI",
"Ollama",
"Together",
"Groq",
"Litellm",
"Mistral AI",
"Google AI",
"AWS Bedrock",
"Gemini",
"DeepSeek",
"xAI",
"LM Studio",
"LangChain",
]
const LLM_PROVIDERS = {
"OpenAI": "openai",
"Anthropic": "anthropic",
"Azure OpenAI": "azure_openai",
"Ollama": "ollama",
"Together": "together",
"Groq": "groq",
"Litellm": "litellm",
"Mistral AI": "mistralai",
"Google AI": "google_ai",
"AWS Bedrock": "aws_bedrock",
"Gemini": "gemini",
"DeepSeek": "deepseek",
"xAI": "xai",
"LM Studio": "lmstudio",
"LangChain": "langchain",
}
const EMBEDDER_PROVIDERS = [
"OpenAI",
"Azure OpenAI",
"Ollama",
"Hugging Face",
"Vertexai",
"Gemini",
"Lmstudio",
"Together",
"LangChain",
"AWS Bedrock",
]
const EMBEDDER_PROVIDERS = {
"OpenAI": "openai",
"Azure OpenAI": "azure_openai",
"Ollama": "ollama",
"Hugging Face": "huggingface",
"Vertex AI": "vertexai",
"Gemini": "gemini",
"LM Studio": "lmstudio",
"Together": "together",
"LangChain": "langchain",
"AWS Bedrock": "aws_bedrock",
}
return (
<div className="space-y-8">
@@ -167,8 +167,8 @@ export function FormView({ settings, onChange }: FormViewProps) {
<SelectValue placeholder="Select a provider" />
</SelectTrigger>
<SelectContent>
{LLM_PROVIDERS.map((provider) => (
<SelectItem key={provider} value={provider.toLowerCase()}>
{Object.entries(LLM_PROVIDERS).map(([provider, value]) => (
<SelectItem key={value} value={value}>
{provider}
</SelectItem>
))}
@@ -281,8 +281,8 @@ export function FormView({ settings, onChange }: FormViewProps) {
<SelectValue placeholder="Select a provider" />
</SelectTrigger>
<SelectContent>
{EMBEDDER_PROVIDERS.map((provider) => (
<SelectItem key={provider} value={provider.toLowerCase()}>
{Object.entries(EMBEDDER_PROVIDERS).map(([provider, value]) => (
<SelectItem key={value} value={value}>
{provider}
</SelectItem>
))}
@@ -51,6 +51,11 @@ export const constants = {
icon: <Icon source="/images/enconvo.png" />,
iconImage: "/images/enconvo.png",
},
augment: {
name: "Augment",
icon: <Icon source="/images/augment.png" />,
iconImage: "/images/augment.png",
},
default: {
name: "Default",
icon: <BiEdit size={18} className="ml-1" />,
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Generated
+1 -1
View File
@@ -2200,4 +2200,4 @@ graph = ["langchain-neo4j", "neo4j", "rank-bm25"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.9,<4.0"
content-hash = "07f2aee9c596c2d2470df085b92551b7b7e3c19cabe61ae5bee7505395601417"
content-hash = "07f2aee9c596c2d2470df085b92551b7b7e3c19cabe61ae5bee7505395601417"
+59 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mem0ai"
version = "0.1.107"
version = "0.1.112"
description = "Long-term memory for AI Agents"
authors = [
{ name = "Mem0", email = "founders@mem0.ai" }
@@ -26,6 +26,34 @@ graph = [
"neo4j>=5.23.1",
"rank-bm25>=0.2.2",
]
vector_stores = [
"vecs>=0.4.0",
"chromadb>=0.4.24",
"weaviate-client>=4.4.0",
"pinecone<7.0.0",
"pinecone-text>=0.1.1",
"faiss-cpu>=1.7.4",
"upstash-vector>=0.1.0",
"azure-search-documents>=11.4.0b8",
]
llms = [
"groq>=0.3.0",
"together>=0.2.10",
"litellm>=0.1.0",
"ollama>=0.1.0",
"vertexai>=0.1.0",
"google-generativeai>=0.3.0",
"google-genai>=1.0.0",
]
extras = [
"boto3>=1.34.0",
"langchain-community>=0.0.0",
"sentence-transformers>=2.2.2",
"elasticsearch>=8.0.0",
"opensearch-py>=2.0.0",
"langchain-memgraph>=0.1.0",
]
test = [
"pytest>=8.2.2",
"pytest-mock>=3.14.0",
@@ -53,6 +81,36 @@ only-include = ["mem0"]
[tool.hatch.build.targets.wheel.shared-data]
"README.md" = "README.md"
[tool.hatch.envs.dev_py_3_9]
python = "3.9"
features = [
"test",
"graph",
"vector_stores",
"llms",
"extras",
]
[tool.hatch.envs.dev_py_3_10]
python = "3.10"
features = [
"test",
"graph",
"vector_stores",
"llms",
"extras",
]
[tool.hatch.envs.dev_py_3_11]
python = "3.11"
features = [
"test",
"graph",
"vector_stores",
"llms",
"extras",
]
[tool.hatch.envs.default.scripts]
format = [
"ruff format",
@@ -28,3 +28,29 @@ def test_embed_query(mock_genai, config):
assert embedding == [0.1, 0.2, 0.3, 0.4]
mock_genai.assert_called_once_with(model="test_model", content="Hello, world!", output_dimensionality=786)
def test_embed_returns_empty_list_if_none(mock_genai, config):
mock_genai.return_value = None
embedder = GoogleGenAIEmbedding(config)
result = embedder.embed("test")
assert result == []
mock_genai.assert_called_once()
def test_embed_raises_on_error(mock_genai, config):
mock_genai.side_effect = RuntimeError("Embedding failed")
embedder = GoogleGenAIEmbedding(config)
with pytest.raises(RuntimeError, match="Embedding failed"):
embedder.embed("some input")
def test_config_initialization(config):
embedder = GoogleGenAIEmbedding(config)
assert embedder.config.api_key == "dummy_api_key"
assert embedder.config.model == "test_model"
assert embedder.config.embedding_dims == 786
@@ -1,8 +1,7 @@
from unittest.mock import Mock, patch
import pytest
from google.generativeai import GenerationConfig
from google.generativeai.types import content_types
from google.genai import types
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.gemini import GeminiLLM
@@ -10,14 +9,14 @@ from mem0.llms.gemini import GeminiLLM
@pytest.fixture
def mock_gemini_client():
with patch("mem0.llms.gemini.GenerativeModel") as mock_gemini:
with patch("mem0.llms.gemini.genai") as mock_client_class:
mock_client = Mock()
mock_gemini.return_value = mock_client
mock_client_class.return_value = mock_client
yield mock_client
def test_generate_response_without_tools(mock_gemini_client: Mock):
config = BaseLlmConfig(model="gemini-1.5-flash-latest", temperature=0.7, max_tokens=100, top_p=1.0)
config = BaseLlmConfig(model="gemini-2.0-flash-latest", temperature=0.7, max_tokens=100, top_p=1.0)
llm = GeminiLLM(config)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
@@ -25,6 +24,15 @@ def test_generate_response_without_tools(mock_gemini_client: Mock):
]
mock_part = Mock(text="I'm doing well, thank you for asking!")
mock_embedding = Mock()
mock_embedding.values = [0.1, 0.2, 0.3]
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [Mock()]
mock_response.candidates[0].content.parts[0].text = "I'm doing well, thank you for asking!"
mock_gemini_client.models.generate_content.return_value = mock_response
mock_content = Mock(parts=[mock_part])
mock_message = Mock(content=mock_content)
mock_response = Mock(candidates=[mock_message])
@@ -37,15 +45,24 @@ def test_generate_response_without_tools(mock_gemini_client: Mock):
{"parts": "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: You are a helpful assistant.", "role": "user"},
{"parts": "Hello, how are you?", "role": "user"},
],
generation_config=GenerationConfig(temperature=0.7, max_output_tokens=100, top_p=1.0),
tools=None,
tool_config=content_types.to_tool_config(
{"function_calling_config": {"mode": "auto", "allowed_function_names": None}}
),
)
config=types.GenerateContentConfig(
temperature=0.7,
max_output_tokens=100,
top_p=1.0,
tools=None,
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
allowed_function_names=None,
mode="auto"
)
)
) )
assert response == "I'm doing well, thank you for asking!"
def test_generate_response_with_tools(mock_gemini_client: Mock):
config = BaseLlmConfig(model="gemini-1.5-flash-latest", temperature=0.7, max_tokens=100, top_p=1.0)
llm = GeminiLLM(config)
@@ -89,28 +106,46 @@ def test_generate_response_with_tools(mock_gemini_client: Mock):
mock_gemini_client.generate_content.assert_called_once_with(
contents=[
{"parts": "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: You are a helpful assistant.", "role": "user"},
{"parts": "Add a new memory: Today is a sunny day.", "role": "user"},
],
generation_config=GenerationConfig(temperature=0.7, max_output_tokens=100, top_p=1.0),
tools=[
{
"function_declarations": [
{
"name": "add_memory",
"description": "Add a memory",
"parameters": {
"type": "object",
"properties": {"data": {"type": "string", "description": "Data to add to memory"}},
"required": ["data"],
},
}
]
}
"parts": "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: You are a helpful assistant.",
"role": "user"
},
{
"parts": "Add a new memory: Today is a sunny day.",
"role": "user"
},
],
tool_config=content_types.to_tool_config(
{"function_calling_config": {"mode": "auto", "allowed_function_names": None}}
),
config=types.GenerateContentConfig(
temperature=0.7,
max_output_tokens=100,
top_p=1.0,
tools=[
types.Tool(
function_declarations=[
types.FunctionDeclaration(
name="add_memory",
description="Add a memory",
parameters={
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "Data to add to memory"
}
},
"required": ["data"]
}
)
]
)
],
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
allowed_function_names=None,
mode="auto"
)
)
)
)
assert response["content"] == "I've added the memory for you."
+27
View File
@@ -42,3 +42,30 @@ def test_generate_response_without_tools(mock_lm_studio_client):
)
assert response == "I'm doing well, thank you for asking!"
def test_generate_response_specifying_response_format(mock_lm_studio_client):
config = BaseLlmConfig(
model="lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
temperature=0.7,
max_tokens=100,
top_p=1.0,
lmstudio_response_format={"type": "json_schema"}, # Specifying the response format in config
)
llm = LMStudioLLM(config)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"},
]
response = llm.generate_response(messages)
mock_lm_studio_client.chat.completions.create.assert_called_once_with(
model="lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
messages=messages,
temperature=0.7,
max_tokens=100,
top_p=1.0,
response_format={"type": "json_schema"},
)
assert response == "I'm doing well, thank you for asking!"
+80
View File
@@ -0,0 +1,80 @@
from unittest.mock import Mock, patch
import pytest
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.vllm import VllmLLM
@pytest.fixture
def mock_vllm_client():
with patch("mem0.llms.vllm.OpenAI") as mock_openai:
mock_client = Mock()
mock_openai.return_value = mock_client
yield mock_client
def test_generate_response_without_tools(mock_vllm_client):
config = BaseLlmConfig(model="Qwen/Qwen2.5-32B-Instruct", temperature=0.7, max_tokens=100, top_p=1.0)
llm = VllmLLM(config)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"},
]
mock_response = Mock()
mock_response.choices = [Mock(message=Mock(content="I'm doing well, thank you for asking!"))]
mock_vllm_client.chat.completions.create.return_value = mock_response
response = llm.generate_response(messages)
mock_vllm_client.chat.completions.create.assert_called_once_with(
model="Qwen/Qwen2.5-32B-Instruct", messages=messages, temperature=0.7, max_tokens=100, top_p=1.0
)
assert response == "I'm doing well, thank you for asking!"
def test_generate_response_with_tools(mock_vllm_client):
config = BaseLlmConfig(model="Qwen/Qwen2.5-32B-Instruct", temperature=0.7, max_tokens=100, top_p=1.0)
llm = VllmLLM(config)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Add a new memory: Today is a sunny day."},
]
tools = [
{
"type": "function",
"function": {
"name": "add_memory",
"description": "Add a memory",
"parameters": {
"type": "object",
"properties": {"data": {"type": "string", "description": "Data to add to memory"}},
"required": ["data"],
},
},
}
]
mock_response = Mock()
mock_message = Mock()
mock_message.content = "I've added the memory for you."
mock_tool_call = Mock()
mock_tool_call.function.name = "add_memory"
mock_tool_call.function.arguments = '{"data": "Today is a sunny day."}'
mock_message.tool_calls = [mock_tool_call]
mock_response.choices = [Mock(message=mock_message)]
mock_vllm_client.chat.completions.create.return_value = mock_response
response = llm.generate_response(messages, tools=tools)
mock_vllm_client.chat.completions.create.assert_called_once_with(
model="Qwen/Qwen2.5-32B-Instruct", messages=messages, temperature=0.7, max_tokens=100, top_p=1.0, tools=tools, tool_choice="auto"
)
assert response["content"] == "I've added the memory for you."
assert len(response["tool_calls"]) == 1
assert response["tool_calls"][0]["name"] == "add_memory"
assert response["tool_calls"][0]["arguments"] == {"data": "Today is a sunny day."}
+14 -5
View File
@@ -40,10 +40,12 @@ class TestAddToVectorStoreErrors:
return memory
def test_empty_llm_response_fact_extraction(self, mock_memory, caplog):
def test_empty_llm_response_fact_extraction(self, mocker, mock_memory, caplog):
"""Test empty response from LLM during fact extraction"""
# Setup
mock_memory.llm.generate_response.return_value = ""
mock_capture_event = mocker.MagicMock()
mocker.patch("mem0.memory.main.capture_event", mock_capture_event)
# Execute
with caplog.at_level(logging.ERROR):
@@ -52,9 +54,10 @@ class TestAddToVectorStoreErrors:
)
# Verify
assert mock_memory.llm.generate_response.call_count == 2
assert mock_memory.llm.generate_response.call_count == 1
assert result == [] # Should return empty list when no memories processed
assert "Error in new_retrieved_facts" in caplog.text
assert mock_capture_event.call_count == 1
def test_empty_llm_response_memory_actions(self, mock_memory, caplog):
"""Test empty response from LLM during memory actions"""
@@ -94,25 +97,31 @@ class TestAsyncAddToVectorStoreErrors:
"""Test empty response in AsyncMemory._add_to_vector_store"""
mocker.patch("mem0.utils.factory.EmbedderFactory.create", return_value=MagicMock())
mock_async_memory.llm.generate_response.return_value = ""
mock_capture_event = mocker.MagicMock()
mocker.patch("mem0.memory.main.capture_event", mock_capture_event)
with caplog.at_level(logging.ERROR):
result = await mock_async_memory._add_to_vector_store(
messages=[{"role": "user", "content": "test"}], metadata={}, filters={}, infer=True
messages=[{"role": "user", "content": "test"}], metadata={}, effective_filters={}, infer=True
)
assert mock_async_memory.llm.generate_response.call_count == 1
assert result == []
assert "Error in new_retrieved_facts" in caplog.text
assert mock_capture_event.call_count == 1
@pytest.mark.asyncio
async def test_async_empty_llm_response_memory_actions(self, mock_async_memory, caplog, mocker):
"""Test empty response in AsyncMemory._add_to_vector_store"""
mocker.patch("mem0.utils.factory.EmbedderFactory.create", return_value=MagicMock())
mock_async_memory.llm.generate_response.side_effect = ['{"facts": ["test fact"]}', ""]
mock_capture_event = mocker.MagicMock()
mocker.patch("mem0.memory.main.capture_event", mock_capture_event)
with caplog.at_level(logging.ERROR):
result = await mock_async_memory._add_to_vector_store(
messages=[{"role": "user", "content": "test"}], metadata={}, filters={}, infer=True
messages=[{"role": "user", "content": "test"}], metadata={}, effective_filters={}, infer=True
)
assert result == []
assert "Invalid JSON response" in caplog.text
assert mock_capture_event.call_count == 1
+9 -3
View File
@@ -19,13 +19,14 @@ def mock_openai():
def memory_instance():
with (
patch("mem0.utils.factory.EmbedderFactory") as mock_embedder,
patch("mem0.utils.factory.VectorStoreFactory") as mock_vector_store,
patch("mem0.memory.main.VectorStoreFactory") as mock_vector_store,
patch("mem0.utils.factory.LlmFactory") as mock_llm,
patch("mem0.memory.telemetry.capture_event"),
patch("mem0.memory.graph_memory.MemoryGraph"),
):
mock_embedder.create.return_value = Mock()
mock_vector_store.create.return_value = Mock()
mock_vector_store.create.return_value.search.return_value = []
mock_llm.create.return_value = Mock()
config = MemoryConfig(version="v1.1")
@@ -37,13 +38,14 @@ def memory_instance():
def memory_custom_instance():
with (
patch("mem0.utils.factory.EmbedderFactory") as mock_embedder,
patch("mem0.utils.factory.VectorStoreFactory") as mock_vector_store,
patch("mem0.memory.main.VectorStoreFactory") as mock_vector_store,
patch("mem0.utils.factory.LlmFactory") as mock_llm,
patch("mem0.memory.telemetry.capture_event"),
patch("mem0.memory.graph_memory.MemoryGraph"),
):
mock_embedder.create.return_value = Mock()
mock_vector_store.create.return_value = Mock()
mock_vector_store.create.return_value.search.return_value = []
mock_llm.create.return_value = Mock()
config = MemoryConfig(
@@ -250,7 +252,11 @@ def test_get_all(memory_instance, version, enable_graph, expected_result):
def test_custom_prompts(memory_custom_instance):
messages = [{"role": "user", "content": "Test message"}]
from mem0.embeddings.mock import MockEmbeddings
memory_custom_instance.llm.generate_response = Mock()
memory_custom_instance.llm.generate_response.return_value = '{"facts": ["fact1", "fact2"]}'
memory_custom_instance.embedding_model = MockEmbeddings()
with patch("mem0.memory.main.parse_messages", return_value="Test message") as mock_parse_messages:
with patch(
@@ -273,7 +279,7 @@ def test_custom_prompts(memory_custom_instance):
## custom update memory prompt
##
mock_get_update_memory_messages.assert_called_once_with(
[], [], memory_custom_instance.config.custom_update_memory_prompt
[], ["fact1", "fact2"], memory_custom_instance.config.custom_update_memory_prompt
)
memory_custom_instance.llm.generate_response.assert_any_call(
+233
View File
@@ -0,0 +1,233 @@
from unittest.mock import Mock, patch, PropertyMock
import pytest
from mem0.vector_stores.baidu import BaiduDB, OutputData
from pymochow.model.enum import MetricType, TableState, ServerErrCode
from pymochow.model.schema import Field, Schema, VectorIndex, FilteringIndex, HNSWParams, AutoBuildRowCountIncrement
from pymochow.model.table import Partition, Row, VectorSearchConfig, VectorTopkSearchRequest, FloatVector, Table
from pymochow.exception import ServerError
@pytest.fixture
def mock_mochow_client():
with patch("pymochow.MochowClient") as mock_client:
yield mock_client
@pytest.fixture
def mock_configuration():
with patch("pymochow.configuration.Configuration") as mock_config:
yield mock_config
@pytest.fixture
def mock_bce_credentials():
with patch("pymochow.auth.bce_credentials.BceCredentials") as mock_creds:
yield mock_creds
@pytest.fixture
def mock_table():
mock_table = Mock(spec=Table)
# 设置 Table 类的属性
type(mock_table).database_name = PropertyMock(return_value="test_db")
type(mock_table).table_name = PropertyMock(return_value="test_table")
type(mock_table).schema = PropertyMock(return_value=Mock())
type(mock_table).replication = PropertyMock(return_value=1)
type(mock_table).partition = PropertyMock(return_value=Mock())
type(mock_table).enable_dynamic_field = PropertyMock(return_value=False)
type(mock_table).description = PropertyMock(return_value="")
type(mock_table).create_time = PropertyMock(return_value="")
type(mock_table).state = PropertyMock(return_value=TableState.NORMAL)
type(mock_table).aliases = PropertyMock(return_value=[])
return mock_table
@pytest.fixture
def mochow_instance(mock_mochow_client, mock_configuration, mock_bce_credentials, mock_table):
mock_database = Mock()
mock_client_instance = Mock()
# Mock the client creation
mock_mochow_client.return_value = mock_client_instance
# Mock database operations
mock_client_instance.list_databases.return_value = []
mock_client_instance.create_database.return_value = mock_database
mock_client_instance.database.return_value = mock_database
# Mock table operations
mock_database.list_table.return_value = []
mock_database.create_table.return_value = mock_table
mock_database.describe_table.return_value = Mock(state=TableState.NORMAL)
mock_database.table.return_value = mock_table
return BaiduDB(
endpoint="http://localhost:8287",
account="test_account",
api_key="test_api_key",
database_name="test_db",
table_name="test_table",
embedding_model_dims=128,
metric_type="COSINE",
)
def test_insert(mochow_instance, mock_mochow_client):
vectors = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
payloads = [{"name": "vector1"}, {"name": "vector2"}]
ids = ["id1", "id2"]
mochow_instance.insert(vectors=vectors, payloads=payloads, ids=ids)
# Verify table.upsert was called with correct data
assert mochow_instance._table.upsert.call_count == 2
calls = mochow_instance._table.upsert.call_args_list
# Check first call
first_row = calls[0][1]["rows"][0]
assert first_row._data["id"] == "id1"
assert first_row._data["vector"] == [0.1, 0.2, 0.3]
assert first_row._data["metadata"] == {"name": "vector1"}
# Check second call
second_row = calls[1][1]["rows"][0]
assert second_row._data["id"] == "id2"
assert second_row._data["vector"] == [0.4, 0.5, 0.6]
assert second_row._data["metadata"] == {"name": "vector2"}
def test_search(mochow_instance, mock_mochow_client):
# Mock search results
mock_search_results = Mock()
mock_search_results.rows = [
{"row": {"id": "id1", "metadata": {"name": "vector1"}}, "score": 0.1},
{"row": {"id": "id2", "metadata": {"name": "vector2"}}, "score": 0.2},
]
mochow_instance._table.vector_search.return_value = mock_search_results
vectors = [0.1, 0.2, 0.3]
results = mochow_instance.search(query="test", vectors=vectors, limit=2)
# Verify search was called with correct parameters
mochow_instance._table.vector_search.assert_called_once()
call_args = mochow_instance._table.vector_search.call_args
request = call_args[0][0] if call_args[0] else call_args[1]["request"]
assert isinstance(request, VectorTopkSearchRequest)
assert request._vector_field == "vector"
assert isinstance(request._vector, FloatVector)
assert request._vector._floats == vectors
assert request._limit == 2
assert isinstance(request._config, VectorSearchConfig)
assert request._config._ef == 200
# Verify results
assert len(results) == 2
assert results[0].id == "id1"
assert results[0].score == 0.1
assert results[0].payload == {"name": "vector1"}
assert results[1].id == "id2"
assert results[1].score == 0.2
assert results[1].payload == {"name": "vector2"}
def test_search_with_filters(mochow_instance, mock_mochow_client):
mochow_instance._table.vector_search.return_value = Mock(rows=[])
vectors = [0.1, 0.2, 0.3]
filters = {"user_id": "user123", "agent_id": "agent456"}
mochow_instance.search(query="test", vectors=vectors, limit=2, filters=filters)
# Verify search was called with filter
call_args = mochow_instance._table.vector_search.call_args
request = call_args[0][0] if call_args[0] else call_args[1]["request"]
assert request._filter == 'metadata["user_id"] = "user123" AND metadata["agent_id"] = "agent456"'
def test_delete(mochow_instance, mock_mochow_client):
vector_id = "id1"
mochow_instance.delete(vector_id=vector_id)
mochow_instance._table.delete.assert_called_once_with(primary_key={"id": vector_id})
def test_update(mochow_instance, mock_mochow_client):
vector_id = "id1"
new_vector = [0.7, 0.8, 0.9]
new_payload = {"name": "updated_vector"}
mochow_instance.update(vector_id=vector_id, vector=new_vector, payload=new_payload)
mochow_instance._table.upsert.assert_called_once()
call_args = mochow_instance._table.upsert.call_args
row = call_args[0][0] if call_args[0] else call_args[1]["rows"][0]
assert row._data["id"] == vector_id
assert row._data["vector"] == new_vector
assert row._data["metadata"] == new_payload
def test_get(mochow_instance, mock_mochow_client):
# Mock query result
mock_result = Mock()
mock_result.row = {"id": "id1", "metadata": {"name": "vector1"}}
mochow_instance._table.query.return_value = mock_result
result = mochow_instance.get(vector_id="id1")
mochow_instance._table.query.assert_called_once_with(primary_key={"id": "id1"}, projections=["id", "metadata"])
assert result.id == "id1"
assert result.score is None
assert result.payload == {"name": "vector1"}
def test_list(mochow_instance, mock_mochow_client):
# Mock select result
mock_result = Mock()
mock_result.rows = [{"id": "id1", "metadata": {"name": "vector1"}}, {"id": "id2", "metadata": {"name": "vector2"}}]
mochow_instance._table.select.return_value = mock_result
results = mochow_instance.list(limit=2)
mochow_instance._table.select.assert_called_once_with(filter=None, projections=["id", "metadata"], limit=2)
assert len(results[0]) == 2
assert results[0][0].id == "id1"
assert results[0][1].id == "id2"
def test_list_cols(mochow_instance, mock_mochow_client):
# Mock table list
mock_tables = [
Mock(spec=Table, database_name="test_db", table_name="table1"),
Mock(spec=Table, database_name="test_db", table_name="table2"),
]
mochow_instance._database.list_table.return_value = mock_tables
result = mochow_instance.list_cols()
assert result == ["table1", "table2"]
def test_delete_col_not_exists(mochow_instance, mock_mochow_client):
# 使用正确的 ServerErrCode 枚举值
mochow_instance._database.drop_table.side_effect = ServerError(
"Table not exists", code=ServerErrCode.TABLE_NOT_EXIST
)
# Should not raise exception
mochow_instance.delete_col()
def test_col_info(mochow_instance, mock_mochow_client):
mock_table_info = {"table_name": "test_table", "fields": []}
mochow_instance._table.stats.return_value = mock_table_info
result = mochow_instance.col_info()
assert result == mock_table_info
+176
View File
@@ -0,0 +1,176 @@
import time
import pytest
from unittest.mock import MagicMock, patch
from mem0.vector_stores.mongodb import MongoVector
from pymongo.operations import SearchIndexModel
@pytest.fixture
@patch("mem0.vector_stores.mongodb.MongoClient")
def mongo_vector_fixture(mock_mongo_client):
mock_client = mock_mongo_client.return_value
mock_db = mock_client["test_db"]
mock_collection = mock_db["test_collection"]
mock_collection.list_search_indexes.return_value = []
mock_collection.aggregate.return_value = []
mock_collection.find_one.return_value = None
mock_collection.find.return_value = []
mock_db.list_collection_names.return_value = []
mongo_vector = MongoVector(
db_name="test_db",
collection_name="test_collection",
embedding_model_dims=1536,
user="username",
password="password",
)
return mongo_vector, mock_collection, mock_db
def test_initalize_create_col(mongo_vector_fixture):
mongo_vector, mock_collection, mock_db = mongo_vector_fixture
assert mongo_vector.collection_name == "test_collection"
assert mongo_vector.embedding_model_dims == 1536
assert mongo_vector.db_name == "test_db"
# Verify create_col being called
mock_db.list_collection_names.assert_called_once()
mock_collection.insert_one.assert_called_once_with({"_id": 0, "placeholder": True})
mock_collection.delete_one.assert_called_once_with({"_id": 0})
assert mongo_vector.index_name == "test_collection_vector_index"
mock_collection.list_search_indexes.assert_called_once_with(name="test_collection_vector_index")
mock_collection.create_search_index.assert_called_once()
args, _ = mock_collection.create_search_index.call_args
search_index_model = args[0].document
assert search_index_model == {
"name": "test_collection_vector_index",
"definition": {
"mappings": {
"dynamic": False,
"fields": {
"embedding": {
"type": "knnVector",
"d": 1536,
"similarity": "cosine",
}
}
}
}
}
assert mongo_vector.collection == mock_collection
def test_insert(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
vectors = [[0.1] * 1536, [0.2] * 1536]
payloads = [{"name": "vector1"}, {"name": "vector2"}]
ids = ["id1", "id2"]
mongo_vector.insert(vectors, payloads, ids)
expected_records=[
({"_id": ids[0], "embedding": vectors[0], "payload": payloads[0]}),
({"_id": ids[1], "embedding": vectors[1], "payload": payloads[1]})
]
mock_collection.insert_many.assert_called_once_with(expected_records)
def test_search(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
query_vector = [0.1] * 1536
mock_collection.aggregate.return_value = [
{"_id": "id1", "score": 0.9, "payload": {"key": "value1"}},
{"_id": "id2", "score": 0.8, "payload": {"key": "value2"}},
]
mock_collection.list_search_indexes.return_value = ["test_collection_vector_index"]
results = mongo_vector.search("query_str", query_vector, limit=2)
mock_collection.list_search_indexes.assert_called_with(name="test_collection_vector_index")
mock_collection.aggregate.assert_called_once_with([
{
"$vectorSearch": {
"index": "test_collection_vector_index",
"limit": 2,
"numCandidates": 2,
"queryVector": query_vector,
"path": "embedding",
},
},
{"$set": {"score": {"$meta": "vectorSearchScore"}}},
{"$project": {"embedding": 0}},
])
assert len(results) == 2
assert results[0].id == "id1"
assert results[0].score == 0.9
assert results[1].id == "id2"
assert results[1].score == 0.8
def test_delete(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
mock_delete_result = MagicMock()
mock_delete_result.deleted_count = 1
mock_collection.delete_one.return_value = mock_delete_result
mongo_vector.delete("id1")
mock_collection.delete_one.assert_called_with({"_id": "id1"})
def test_update(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
mock_update_result = MagicMock()
mock_update_result.matched_count = 1
mock_collection.update_one.return_value = mock_update_result
idValue = "id1"
vectorValue = [0.2] * 1536
payloadValue = {"key": "updated"}
mongo_vector.update(idValue, vector=vectorValue, payload=payloadValue)
mock_collection.update_one.assert_called_once_with(
{"_id": idValue},
{"$set": {"embedding": vectorValue, "payload": payloadValue}},
)
def test_get(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
mock_collection.find_one.return_value = {"_id": "id1", "payload": {"key": "value1"}}
result = mongo_vector.get("id1")
assert result is not None
assert result.id == "id1"
assert result.payload == {"key": "value1"}
def test_list_cols(mongo_vector_fixture):
mongo_vector, _, mock_db = mongo_vector_fixture
mock_db.list_collection_names.return_value = ["col1", "col2"]
collections = mongo_vector.list_cols()
assert collections == ["col1", "col2"]
def test_delete_col(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
mongo_vector.delete_col()
mock_collection.drop.assert_called_once()
def test_col_info(mongo_vector_fixture):
mongo_vector, _, mock_db = mongo_vector_fixture
mock_db.command.return_value = {"count": 10, "size": 1024}
info = mongo_vector.col_info()
mock_db.command.assert_called_once_with("collstats", "test_collection")
assert info["name"] == "test_collection"
assert info["count"] == 10
assert info["size"] == 1024
def test_list(mongo_vector_fixture):
mongo_vector, mock_collection, _ = mongo_vector_fixture
mock_cursor = MagicMock()
mock_cursor.limit.return_value = [
{"_id": "id1", "payload": {"key": "value1"}},
{"_id": "id2", "payload": {"key": "value2"}},
]
mock_collection.find.return_value = mock_cursor
query_filters = {"_id": {"$in": ["id1", "id2"]}}
results = mongo_vector.list(filters=query_filters, limit=2)
mock_collection.find.assert_called_once_with(query_filters)
mock_cursor.limit.assert_called_once_with(2)
assert len(results) == 2
assert results[0].id == "id1"
assert results[0].payload == {"key": "value1"}
assert results[1].id == "id2"
assert results[1].payload == {"key": "value2"}
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/vercel-ai-provider",
"version": "1.0.4",
"version": "1.0.6",
"description": "Vercel AI Provider for providing memory to LLMs",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -28,6 +28,7 @@
"dependencies": {
"@ai-sdk/anthropic": "1.1.12",
"@ai-sdk/cohere": "1.1.12",
"@ai-sdk/google": "1.2.18",
"@ai-sdk/groq": "1.1.11",
"@ai-sdk/openai": "1.1.15",
"@ai-sdk/provider": "1.0.9",
+36
View File
@@ -14,6 +14,9 @@ importers:
'@ai-sdk/cohere':
specifier: 1.1.12
version: 1.1.12(zod@3.24.2)
'@ai-sdk/google':
specifier: 1.2.18
version: 1.2.18(zod@3.24.2)
'@ai-sdk/groq':
specifier: 1.1.11
version: 1.1.11(zod@3.24.2)
@@ -84,6 +87,12 @@ packages:
peerDependencies:
zod: ^3.0.0
'@ai-sdk/google@1.2.18':
resolution: {integrity: sha512-8B70+i+uB12Ae6Sn6B9Oc6W0W/XorGgc88Nx0pyUrcxFOdytHBaAVhTPqYsO3LLClfjYN8pQ9GMxd5cpGEnUcA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
'@ai-sdk/groq@1.1.11':
resolution: {integrity: sha512-Y5WUyWuxkQarl4AVGeIMbNSp4/XiwW/mxp9SKeagfDhflVnQHd2ggISVD6HiOBQhznusITjWYYC66DJeBn0v6A==}
engines: {node: '>=18'}
@@ -105,10 +114,20 @@ packages:
zod:
optional: true
'@ai-sdk/provider-utils@2.2.8':
resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.23.8
'@ai-sdk/provider@1.0.9':
resolution: {integrity: sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA==}
engines: {node: '>=18'}
'@ai-sdk/provider@1.1.3':
resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==}
engines: {node: '>=18'}
'@ai-sdk/react@1.1.18':
resolution: {integrity: sha512-2wlWug6NVAc8zh3pgqtvwPkSNTdA6Q4x9CmrNXCeHcXfJkJ+MuHFQz/I7Wb7mLRajf0DAxsFLIhHyBCEuTkDNw==}
engines: {node: '>=18'}
@@ -2751,6 +2770,12 @@ snapshots:
'@ai-sdk/provider-utils': 2.1.10(zod@3.24.2)
zod: 3.24.2
'@ai-sdk/google@1.2.18(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.24.2)
zod: 3.24.2
'@ai-sdk/groq@1.1.11(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.0.9
@@ -2772,10 +2797,21 @@ snapshots:
optionalDependencies:
zod: 3.24.2
'@ai-sdk/provider-utils@2.2.8(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.1.3
nanoid: 3.3.11
secure-json-parse: 2.7.0
zod: 3.24.2
'@ai-sdk/provider@1.0.9':
dependencies:
json-schema: 0.4.0
'@ai-sdk/provider@1.1.3':
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.1.18(react@19.1.0)(zod@3.24.2)':
dependencies:
'@ai-sdk/provider-utils': 2.1.10(zod@3.24.2)

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