Compare commits

..
23 Commits
Author SHA1 Message Date
Dev KhantandGitHub b6684b96f7 version bump -> 0.1.110 (#3001) 2025-06-20 20:30:51 +05:30
Akarsha SehwagandGitHub 1fa0f0a157 fix(opensearch): update logger warning (#2999) 2025-06-20 20:28:51 +05:30
Saket AryanandGitHub 2754f45387 Make V2 Add as Default (#2997) 2025-06-20 16:56:42 +05:30
Parshva DaftariandGitHub ecd4d91046 Fix failing CI pipeline (#2979) 2025-06-20 15:19:11 +05:30
Prateek ChhikaraandGitHub a5a247b161 Update client.update() method documentation in OpenAPI specification (#2990) 2025-06-19 14:04:12 -07:00
Dev KhantandGitHub d47cb8d284 Doc: Fix example in quickstart page (#2986) 2025-06-19 13:51:20 +05:30
Dev KhantandGitHub fa15db089d Update Changelog (#2985) 2025-06-19 12:32:33 +05:30
Shili CaoandGitHub d35065c887 Feature: baidu vector db integration (#2929) 2025-06-19 11:12:12 +05:30
Prateek ChhikaraandGitHub cdee6a4ff0 Enhance update method to support metadata (#2976) 2025-06-18 10:07:18 -07:00
Dev KhantandGitHub 9eb4e77c75 Fix pinecone for async memory (#2975) 2025-06-18 01:37:45 +05:30
Akshat JainandGitHub c700d790db Fix Build CI Failure (#2973) 2025-06-17 09:39:19 -07:00
Antaripa SahaandGitHub a90b572389 Memory agent powered by voice (Cartesia + Agno) (#2970) 2025-06-17 18:54:53 +05:30
i-sunandGitHub 62c330e5b3 feat(LM Studio): Add response_format param for LM Studio to config (#2502) 2025-06-17 17:55:18 +05:30
Akshat JainandGitHub c70dc7614b Fix: Add Google Genai library support (#2941) 2025-06-17 17:47:09 +05:30
Fenil FalduandGitHub e0003247c3 feat: add AgentOps integration (#2898) 2025-06-17 11:38:39 +05:30
Saket AryanandGitHub 888ee766c5 TS SDK - filter memories param (#2971) 2025-06-17 10:54:35 +05:30
Saket AryanandGitHub c7e91171a0 Added Param output_format in AI SDK (#2960) 2025-06-15 06:42:56 +05:30
Dev KhantandGitHub 18c870ec79 version bump -> 0.1.108 (#2958) 2025-06-14 21:58:12 +05:30
Dev KhantandGitHub 3e5f68ee90 Add logger in Opensearch (#2957) 2025-06-14 21:55:22 +05:30
a0cd4065d9 +MongoDB Vector Support (#2367)
Co-authored-by: Divya Gupta <divya.gupta@mongodb.com>
2025-06-14 17:57:06 +05:30
John LockwoodandGitHub 7c0c4a03c4 Feat/add python version test envs (#2774) 2025-06-14 17:43:16 +05:30
John LockwoodandGitHub a8ace18607 Fix/pin pinecone issue #2772 (#2773) 2025-06-14 17:38:32 +05:30
Dev KhantandGitHub df43f904d1 deploy minor version -> 0.1.107rc2 (#2953) 2025-06-13 12:09:54 +05:30
47 changed files with 3159 additions and 725 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
+234 -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,16 @@ mode: "wide"
<Tab title="TypeScript">
<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 +568,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 +673,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.
+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": {}}},
}
}
}
+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>
+4 -1
View File
@@ -137,6 +137,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 +146,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"
]
}
]
@@ -235,6 +237,7 @@
"icon": "plug",
"pages": [
"integrations",
"integrations/agentops",
"integrations/vercel-ai-sdk",
"integrations/flowise",
"integrations/crewai",
+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" />
+74 -4
View File
@@ -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,42 @@ 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" />
+6 -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"
+8 -6
View File
@@ -1780,24 +1780,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 '{
+14 -2
View File
@@ -333,11 +333,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
+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"]
@@ -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!
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "2.1.30",
"version": "2.1.32",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
+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);
+1
View File
@@ -24,6 +24,7 @@ export interface MemoryOptions {
timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
filter_memories?: 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.32";
// Safely check for process.env in different environments
let MEM0_TELEMETRY = true;
+497 -104
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -43,6 +43,7 @@ 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,
# AWS Bedrock specific
aws_access_key_id: Optional[str] = None,
aws_secret_access_key: Optional[str] = None,
@@ -95,6 +96,8 @@ 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
"""
self.model = model
@@ -134,6 +137,7 @@ class BaseLlmConfig(ABC):
# LM Studio specific
self.lmstudio_base_url = lmstudio_base_url
self.lmstudio_response_format = lmstudio_response_format
# 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 MongoVectorConfig(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
+59 -39
View File
@@ -2,9 +2,9 @@ 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'."
@@ -22,66 +22,71 @@ class GeminiLLM(LLMBase):
self.config.model = "gemini-1.5-flash-latest"
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)
self.client_gemini = genai.Client(
api_key=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.
response: The raw response from the API.
tools: The list of tools provided in the request.
Returns:
str or dict: The processed response.
"""
candidate = response.candidates[0]
content = candidate.content.parts[0].text if candidate.content.parts else None
if tools:
processed_response = {
"content": (content if (content := response.candidates[0].content.parts[0].text) else None),
"content": content,
"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})
for part in candidate.content.parts:
fn = getattr(part, "function_call", None)
if fn:
processed_response["tool_calls"].append({
"name": fn.name,
"arguments": fn.args,
})
return processed_response
else:
return response.candidates[0].content.parts[0].text
def _reformat_messages(self, messages: List[Dict[str, str]]):
return content
def _reformat_messages(self, messages: List[Dict[str, str]]) -> List[types.Content]:
"""
Reformat messages for Gemini.
Reformat messages for Gemini using google.genai.types.
Args:
messages: The list of messages provided in the request.
Returns:
list: The list of messages in the required format.
list: A list of types.Content objects with proper role and parts.
"""
new_messages = []
for message in messages:
if message["role"] == "system":
content = "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: " + message["content"]
else:
content = message["content"]
new_messages.append(
{
"parts": content,
"role": "model" if message["role"] == "model" else "user",
}
types.Content(
role="model" if message["role"] == "model" else "user",
parts=[types.Part(text=content)]
)
)
return new_messages
def _reformat_tools(self, tools: Optional[List[Dict]]):
"""
Reformat tools for Gemini.
@@ -126,6 +131,7 @@ class GeminiLLM(LLMBase):
tools: Optional[List[Dict]] = None,
tool_choice: str = "auto",
):
"""
Generate a response based on the given messages using Gemini.
@@ -149,23 +155,37 @@ class GeminiLLM(LLMBase):
params["response_mime_type"] = "application/json"
if "schema" in response_format:
params["response_schema"] = response_format["schema"]
tool_config = None
if tool_choice:
tool_config = content_types.to_tool_config(
{
"function_calling_config": {
"mode": tool_choice,
"allowed_function_names": (
[tool["function"]["name"] for tool in tools] if tool_choice == "any" else None
),
}
}
tool_config = types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
mode=tool_choice.upper(), # Assuming 'any' should become 'ANY', etc.
allowed_function_names=[
tool["function"]["name"] for tool in tools
] if tool_choice == "any" else None
)
)
response = self.client.generate_content(
contents=self._reformat_messages(messages),
tools=self._reformat_tools(tools),
generation_config=genai.GenerationConfig(**params),
tool_config=tool_config,
)
print(f"Tool config: {tool_config}")
print(f"Params: {params}" )
print(f"Messages: {messages}")
print(f"Tools: {tools}")
print(f"Reformatted messages: {self._reformat_messages(messages)}")
print(f"Reformatted tools: {self._reformat_tools(tools)}")
response = self.client_gemini.models.generate_content(
model=self.config.model,
contents=self._reformat_messages(messages),
config=types.GenerateContentConfig(
temperature= self.config.temperature,
max_output_tokens= self.config.max_tokens,
top_p= self.config.top_p,
tools=self._reformat_tools(tools),
tool_config=tool_config,
),
)
print(f"Response test: {response}")
return self._parse_response(response, tools)
+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
+226 -138
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,32 +122,35 @@ 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:
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:
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
"""
# return all nodes and relationships
def get_all(self, filters, limit=100):
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 +166,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 +201,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 +235,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 +245,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 +290,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 +313,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 +386,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 +467,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 +536,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 +549,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 +573,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 +605,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
+1
View File
@@ -79,6 +79,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",
+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`"
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.107r1"
version = "0.1.110"
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",
+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!"
+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)
+1
View File
@@ -28,6 +28,7 @@ export interface Mem0ConfigSettings {
rerank?: boolean;
enable_graph?: boolean;
output_format?: string;
filter_memories?: boolean;
}
export interface Mem0ChatConfig extends Mem0ConfigSettings, Mem0ProviderSettings {}