Compare commits

..
36 Commits
Author SHA1 Message Date
Dev KhantandGitHub 884b597312 bump version -> 0.1.79 (#2463) 2025-03-29 13:46:00 +05:30
Dev KhantandGitHub f1471bcc55 update changelog (#2462) 2025-03-29 13:39:11 +05:30
Dev KhantandGitHub 9ae23f9c88 Add Faiss Support (#2461) 2025-03-29 13:35:36 +05:30
Dev KhantandGitHub cbecbb7b64 doc: update email example (#2459) 2025-03-29 00:11:02 +05:30
Dev KhantandGitHub f8ad0c2b2c Doc: Add example for email processing (#2458) 2025-03-29 00:09:22 +05:30
Dev KhantandGitHub d0da9a1ae0 Doc: Add changelog (#2455) 2025-03-28 12:18:17 +05:30
Saket AryanandGitHub bc4ab3db77 Add Infer Property (#2452) 2025-03-27 21:18:42 +05:30
Dev KhantandGitHub 0eb53b9f27 doc: update API reference for expiration_date (#2450) 2025-03-27 12:24:26 +05:30
Prateek ChhikaraandGitHub 3cef3ed95e Added Evaluation folder (#2448) 2025-03-26 12:37:54 -07:00
Dev KhantandGitHub 45e5f2af93 Doc: Update API reference section and Add elevenlabs example (#2447) 2025-03-27 00:13:09 +05:30
Prateek ChhikaraandGitHub 32ba13b3ea Updated multimodal docs (#2446) 2025-03-26 09:56:16 -07:00
Parshva DaftariandGitHub 69a91d5cbb Mem0 livekit example (#2442) 2025-03-26 16:06:23 +05:30
Dev KhantandGitHub 2004427acd tools fix and formatting (#2441) 2025-03-26 11:25:03 +05:30
Saket AryanandGitHub 2517ccd489 fix(deployments): Add package.json file to fix deployment errors (#2440) 2025-03-26 10:31:09 +05:30
Saket AryanandGitHub 9d0300f774 Update Vercel AI SDK to support tools call (#2383) 2025-03-26 10:30:44 +05:30
Saket AryanandGitHub 366d263e0b docs(supabase-ts): Update Docs for Supabase TS (#2439) 2025-03-26 10:11:01 +05:30
Pranav PuranikandGitHub 4321d24284 Open AI env var fix (#2384) 2025-03-26 08:43:33 +05:30
Dev KhantandGitHub 9cb2a13f3b fix for Azure AI and version bump -> 0.1.76 (#2438) 2025-03-25 18:10:36 +05:30
Dev KhantandGitHub 5ec7889d9a embedchain version bump -> 0.1.128 (#2437) 2025-03-25 13:18:34 +05:30
Dev KhantandGitHub b54845bcc9 Add feeback method to client and doc changes (#2435) 2025-03-25 11:39:19 +05:30
Saket AryanandGitHub 1ae2747ff8 Add Supabase History DB to run Mem0 OSS on Serverless (#2429) 2025-03-24 16:14:29 -07:00
Parshva DaftariandGitHub 953a5a4a2d Azure openai fixes (#2428) 2025-03-25 00:34:21 +05:30
Saket AryanandGitHub 2b49c9eedd Supabase Vector Store (#2427) 2025-03-25 00:15:50 +05:30
Anusha YellaandGitHub 9db5f62262 fix-azure-ai-search-test-cases (#2422) 2025-03-24 15:20:02 +05:30
Dev KhantandGitHub a1bd4285db version bump -> 0.1.75 (#2426) 2025-03-24 15:17:00 +05:30
Dev KhantandGitHub e77a10a8da Add LM Studio support (#2425) 2025-03-24 13:32:26 +05:30
e4307ae420 Fix: Export ollama (#2421)
Co-authored-by: Gaurav Agerwala <ice@Gauravs-MacBook-Pro.local>
2025-03-23 02:06:23 +05:30
Saket AryanandGitHub 7c89d00079 Adds Langchain Community Package (#2417) 2025-03-22 10:44:40 +05:30
Dev KhantandGitHub 563eaae5ee Openai Agents SDK voice demo (#2416) 2025-03-22 01:09:37 +05:30
Dev KhantandGitHub 6733f78f81 Doc: Support for expiration date in ADD (#2419) 2025-03-21 23:38:25 +05:30
Saket AryanandGitHub c11637bd2f Update Node SDK Docs for Update Method (#2418) 2025-03-21 21:23:57 +05:30
Dev-Khant ff30cb8ddd version bump -> 0.1.74 2025-03-21 13:06:40 +05:30
Parshva DaftariandGitHub 2e853c3d22 Updated VDB Docs (#2409) 2025-03-20 23:47:57 +05:30
Dev KhantandGitHub 3cc7013fde fix pinecone (#2414) 2025-03-20 23:47:09 +05:30
8e6a08aa83 Support for hybrid search in Azure AI vector store (#2408)
Co-authored-by: Deshraj Yadav <deshrajdry@gmail.com>
2025-03-20 22:57:00 +05:30
Wonbin KimandGitHub 8b9a8e5825 URGENT Hotfix - update default Elasticsearch search query (#2413) 2025-03-20 20:50:18 +05:30
152 changed files with 15105 additions and 2749 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ install:
install_all: install_all:
poetry install poetry install
poetry run pip install groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \ poetry run pip install groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
google-generativeai elasticsearch opensearch-py vecs pinecone pinecone-text google-generativeai elasticsearch opensearch-py vecs pinecone pinecone-text faiss-cpu
# Format code with ruff # Format code with ruff
format: format:
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Feedback'
openapi: post /v1/feedback/
---
+90
View File
@@ -0,0 +1,90 @@
---
title: "Product Updates"
mode: "wide"
---
<Tabs>
<Tab title="Python">
<Update label="2025-04-02" description="v0.1.79">
**New Features:**
- **FAISS Support:** Added FAISS vector store support
</Update>
<Update label="2025-04-02" description="v0.1.78">
**New Features:**
- **Livekit Integration:** Added Mem0 livekit example
- **Evaluation:** Added evaluation framework and tools
**Documentation:**
- **Multimodal:** Updated multimodal documentation
- **Examples:** Added examples for email processing
- **API Reference:** Updated API reference section
- **Elevenlabs:** Added Elevenlabs integration example
**Bug Fixes:**
- **OpenAI Environment Variables:** Fixed issues with OpenAI environment variables
- **Deployment Errors:** Added `package.json` file to fix deployment errors
- **Tools:** Fixed tools issues and improved formatting
- **Docs:** Updated API reference section for `expiration date`
</Update>
<Update label="2025-03-26" description="v0.1.77">
**Bug Fixes:**
- **OpenAI Environment Variables:** Fixed issues with OpenAI environment variables
- **Deployment Errors:** Added `package.json` file to fix deployment errors
- **Tools:** Fixed tools issues and improved formatting
- **Docs:** Updated API reference section for `expiration date`
</Update>
<Update label="2025-03-19" description="v0.1.76">
**New Features:**
- **Supabase Vector Store:** Added support for Supabase Vector Store
- **Supabase History DB:** Added Supabase History DB to run Mem0 OSS on Serverless
- **Feedback Method:** Added feedback method to client
**Bug Fixes:**
- **Azure OpenAI:** Fixed issues with Azure OpenAI
- **Azure AI Search:** Fixed test cases for Azure AI Search
</Update>
</Tab>
<Tab title="TypeScript">
<Update label="2025-03-26" description="v2.1.12">
**New Features:**
- **Vercel AI SDK Update:** Support for tools call
**Improvements:**
- **Updated Supabase TS Docs**
</Update>
<Update label="2025-03-19" description="v2.1.11">
**New Features:**
- **Supabase Vector Store Integration**
- **Feedback Method**
</Update>
</Tab>
<Tab title="Platform">
<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>
</Tabs>
+1
View File
@@ -84,6 +84,7 @@ Here's a comprehensive list of all parameters that can be used across different
| `memory_add_embedding_type` | The type of embedding to use for the add memory action | VertexAI | | `memory_add_embedding_type` | The type of embedding to use for the add memory action | VertexAI |
| `memory_update_embedding_type` | The type of embedding to use for the update memory action | VertexAI | | `memory_update_embedding_type` | The type of embedding to use for the update memory action | VertexAI |
| `memory_search_embedding_type` | The type of embedding to use for the search memory action | VertexAI | | `memory_search_embedding_type` | The type of embedding to use for the search memory action | VertexAI |
| `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
</Tab> </Tab>
<Tab title="TypeScript"> <Tab title="TypeScript">
| Parameter | Description | Provider | | Parameter | Description | Provider |
@@ -0,0 +1,38 @@
You can use embedding models from LM Studio to run Mem0 locally.
### Usage
```python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your_api_key" # For LLM
config = {
"embedder": {
"provider": "lmstudio",
"config": {
"model": "nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.f16.gguf"
}
}
}
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="john")
```
### Config
Here are the parameters available for configuring Ollama embedder:
| Parameter | Description | Default Value |
| --- | --- | --- |
| `model` | The name of the OpenAI model to use | `nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.f16.gguf` |
| `embedding_dims` | Dimensions of the embedding model | `1536` |
| `lmstudio_base_url` | Base URL for LM Studio connection | `http://localhost:1234/v1` |
+1
View File
@@ -22,6 +22,7 @@ See the list of supported embedders below.
<Card title="Gemini" href="/components/embedders/models/gemini"></Card> <Card title="Gemini" href="/components/embedders/models/gemini"></Card>
<Card title="Vertex AI" href="/components/embedders/models/vertexai"></Card> <Card title="Vertex AI" href="/components/embedders/models/vertexai"></Card>
<Card title="Together" href="/components/embedders/models/together"></Card> <Card title="Together" href="/components/embedders/models/together"></Card>
<Card title="LM Studio" href="/components/embedders/models/lmstudio"></Card>
</CardGroup> </CardGroup>
## Usage ## Usage
+2 -1
View File
@@ -29,7 +29,7 @@ iconType: "solid"
Config values are applied in the following order of precedence (from highest to lowest): Config values are applied in the following order of precedence (from highest to lowest):
1. Values explicitly set in the `config` object/dictionary 1. Values explicitly set in the `config` object/dictionary
2. Environment variables (e.g., `OPENAI_API_KEY`, `OPENAI_API_BASE`) 2. Environment variables (e.g., `OPENAI_API_KEY`, `OPENAI_BASE_URL`)
3. Default values defined in the LLM implementation 3. Default values defined in the LLM implementation
This means that values specified in the `config` will override corresponding environment variables, which in turn override default values. This means that values specified in the `config` will override corresponding environment variables, which in turn override default values.
@@ -108,6 +108,7 @@ Here's a comprehensive list of all parameters that can be used across different
| `azure_kwargs` | Azure LLM args for initialization | AzureOpenAI | | `azure_kwargs` | Azure LLM args for initialization | AzureOpenAI |
| `deepseek_base_url` | Base URL for DeepSeek API | DeepSeek | | `deepseek_base_url` | Base URL for DeepSeek API | DeepSeek |
| `xai_base_url` | Base URL for XAI API | XAI | | `xai_base_url` | Base URL for XAI API | XAI |
| `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
</Tab> </Tab>
<Tab title="TypeScript"> <Tab title="TypeScript">
| Parameter | Description | Provider | | Parameter | Description | Provider |
@@ -4,6 +4,9 @@ title: Azure OpenAI
To use Azure OpenAI models, you have to set the `LLM_AZURE_OPENAI_API_KEY`, `LLM_AZURE_ENDPOINT`, `LLM_AZURE_DEPLOYMENT` and `LLM_AZURE_API_VERSION` environment variables. You can obtain the Azure API key from the [Azure](https://azure.microsoft.com/). To use Azure OpenAI models, you have to set the `LLM_AZURE_OPENAI_API_KEY`, `LLM_AZURE_ENDPOINT`, `LLM_AZURE_DEPLOYMENT` and `LLM_AZURE_API_VERSION` environment variables. You can obtain the Azure API key from the [Azure](https://azure.microsoft.com/).
> **Note**: The following are currently unsupported with reasoning models `Parallel tool calling`,`temperature`, `top_p`, `presence_penalty`, `frequency_penalty`, `logprobs`, `top_logprobs`, `logit_bias`, `max_tokens`
## Usage ## Usage
```python ```python
+82
View File
@@ -0,0 +1,82 @@
---
title: LM Studio
---
To use LM Studio with Mem0, you'll need to have LM Studio running locally with its server enabled. LM Studio provides a way to run local LLMs with an OpenAI-compatible API.
## Usage
<CodeGroup>
```python Python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your-api-key" # used for embedding model
config = {
"llm": {
"provider": "lmstudio",
"config": {
"model": "lmstudio-community/Meta-Llama-3.1-70B-Instruct-GGUF/Meta-Llama-3.1-70B-Instruct-IQ2_M.gguf",
"temperature": 0.2,
"max_tokens": 2000,
"lmstudio_base_url": "http://localhost:1234/v1", # default LM Studio API URL
}
}
}
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"})
```
</CodeGroup>
### Running Completely Locally
You can also use LM Studio for both LLM and embedding to run Mem0 entirely locally:
```python
from mem0 import Memory
# No external API keys needed!
config = {
"llm": {
"provider": "lmstudio"
},
"embedder": {
"provider": "lmstudio"
}
}
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="alice123", metadata={"category": "movies"})
```
<Note>
When using LM Studio for both LLM and embedding, make sure you have:
1. An LLM model loaded for generating responses
2. An embedding model loaded for vector embeddings
3. The server enabled with the correct endpoints accessible
</Note>
<Note>
To use LM Studio, you need to:
1. Download and install [LM Studio](https://lmstudio.ai/)
2. Start a local server from the "Server" tab
3. Set the appropriate `lmstudio_base_url` in your configuration (default is usually http://localhost:1234/v1)
</Note>
## Config
All available parameters for the `lmstudio` config are present in [Master List of All Params in Config](../config).
+1
View File
@@ -32,6 +32,7 @@ To view all supported llms, visit the [Supported LLMs](./models).
<Card title="Gemini" href="/components/llms/models/gemini" /> <Card title="Gemini" href="/components/llms/models/gemini" />
<Card title="DeepSeek" href="/components/llms/models/deepseek" /> <Card title="DeepSeek" href="/components/llms/models/deepseek" />
<Card title="xAI" href="/components/llms/models/xAI" /> <Card title="xAI" href="/components/llms/models/xAI" />
<Card title="LM Studio" href="/components/llms/models/lmstudio" />
</CardGroup> </CardGroup>
## Structured vs Unstructured Outputs ## Structured vs Unstructured Outputs
@@ -1,5 +1,3 @@
# Azure AI Search
[Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-azure-search/) (formerly known as "Azure Cognitive Search") provides secure information retrieval at scale over user-owned content in traditional and generative AI search applications. [Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-azure-search/) (formerly known as "Azure Cognitive Search") provides secure information retrieval at scale over user-owned content in traditional and generative AI search applications.
## Usage ## Usage
@@ -50,6 +48,24 @@ config = {
} }
``` ```
## Using hybrid search
```python
config = {
"vector_store": {
"provider": "azure_ai_search",
"config": {
"service_name": "ai-search-test",
"api_key": "*****",
"collection_name": "mem0",
"embedding_model_dims": 1536,
"hybrid_search": True,
"vector_filter_mode": "postFilter"
}
}
}
```
## Configuration Parameters ## Configuration Parameters
| Parameter | Description | Default Value | Options | | Parameter | Description | Default Value | Options |
@@ -60,6 +76,8 @@ config = {
| `embedding_model_dims` | Dimensions of the embedding model | `1536` | Any integer value | | `embedding_model_dims` | Dimensions of the embedding model | `1536` | Any integer value |
| `compression_type` | Type of vector compression to use | `none` | `none`, `scalar`, `binary` | | `compression_type` | Type of vector compression to use | `none` | `none`, `scalar`, `binary` |
| `use_float16` | Store vectors in half precision (Edm.Half) | `False` | `True`, `False` | | `use_float16` | Store vectors in half precision (Edm.Half) | `False` | `True`, `False` |
| `vector_filter_mode` | Vector filter mode to use | `preFilter` | `postFilter`, `preFilter` |
| `hybrid_search` | Use hybrid search | `False` | `True`, `False` |
## Notes on Configuration Options ## Notes on Configuration Options
@@ -68,6 +86,10 @@ config = {
- `scalar`: Scalar quantization with reasonable balance of speed and accuracy - `scalar`: Scalar quantization with reasonable balance of speed and accuracy
- `binary`: Binary quantization for maximum compression with some accuracy trade-off - `binary`: Binary quantization for maximum compression with some accuracy trade-off
- **vector_filter_mode**:
- `preFilter`: Applies filters before vector search (faster)
- `postFilter`: Applies filters after vector search (may provide better relevance)
- **use_float16**: Using half precision (float16) reduces storage requirements but may slightly impact accuracy. Useful for very large vector collections. - **use_float16**: Using half precision (float16) reduces storage requirements but may slightly impact accuracy. Useful for very large vector collections.
- **Filterable Fields**: The implementation automatically extracts `user_id`, `run_id`, and `agent_id` fields from payloads for filtering. - **Filterable Fields**: The implementation automatically extracts `user_id`, `run_id`, and `agent_id` fields from payloads for filtering.
+72
View File
@@ -0,0 +1,72 @@
[FAISS](https://github.com/facebookresearch/faiss) is a library for efficient similarity search and clustering of dense vectors. It is designed to work with large-scale datasets and provides a high-performance search engine for vector data. FAISS is optimized for memory usage and search speed, making it an excellent choice for production environments.
### Usage
```python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "sk-xx"
config = {
"vector_store": {
"provider": "faiss",
"config": {
"collection_name": "test",
"path": "/tmp/faiss_memories",
"distance_strategy": "euclidean"
}
}
}
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"})
```
### Installation
To use FAISS in your mem0 project, you need to install the appropriate FAISS package for your environment:
```bash
# For CPU version
pip install faiss-cpu
# For GPU version (requires CUDA)
pip install faiss-gpu
```
### Config
Here are the parameters available for configuring FAISS:
| Parameter | Description | Default Value |
| --- | --- | --- |
| `collection_name` | The name of the collection | `mem0` |
| `path` | Path to store FAISS index and metadata | `/tmp/faiss/<collection_name>` |
| `distance_strategy` | Distance metric strategy to use (options: 'euclidean', 'inner_product', 'cosine') | `euclidean` |
| `normalize_L2` | Whether to normalize L2 vectors (only applicable for euclidean distance) | `False` |
### Performance Considerations
FAISS offers several advantages for vector search:
1. **Efficiency**: FAISS is optimized for memory usage and speed, making it suitable for large-scale applications.
2. **Offline Support**: FAISS works entirely locally, with no need for external servers or API calls.
3. **Storage Options**: Vectors can be stored in-memory for maximum speed or persisted to disk.
4. **Multiple Index Types**: FAISS supports different index types optimized for various use cases (though mem0 currently uses the basic flat index).
### Distance Strategies
FAISS in mem0 supports three distance strategies:
- **euclidean**: L2 distance, suitable for most embedding models
- **inner_product**: Dot product similarity, useful for some specialized embeddings
- **cosine**: Cosine similarity, best for comparing semantic similarity regardless of vector magnitude
When using `cosine` or `inner_product` with normalized vectors, you may want to set `normalize_L2=True` for better results.
+17 -13
View File
@@ -1,7 +1,7 @@
# Pinecone
[Pinecone](https://www.pinecone.io/) is a fully managed vector database designed for machine learning applications, offering high performance vector search with low latency at scale. It's particularly well-suited for semantic search, recommendation systems, and other AI-powered applications. [Pinecone](https://www.pinecone.io/) is a fully managed vector database designed for machine learning applications, offering high performance vector search with low latency at scale. It's particularly well-suited for semantic search, recommendation systems, and other AI-powered applications.
> **Note**: Before configuring Pinecone, you need to select an embedding model (e.g., OpenAI, Cohere, or custom models) and ensure the `embedding_model_dims` in your config matches your chosen model's dimensions. For example, OpenAI's text-embedding-3-small uses 1536 dimensions.
### Usage ### Usage
```python ```python
@@ -11,13 +11,17 @@ from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "sk-xx" os.environ["OPENAI_API_KEY"] = "sk-xx"
os.environ["PINECONE_API_KEY"] = "your-api-key" os.environ["PINECONE_API_KEY"] = "your-api-key"
# Example using serverless configuration
config = { config = {
"vector_store": { "vector_store": {
"provider": "pinecone", "provider": "pinecone",
"config": { "config": {
"collection_name": "memory_index", "collection_name": "testing",
"embedding_model_dims": 1536, "embedding_model_dims": 1536, # Matches OpenAI's text-embedding-3-small
"environment": "us-west1-gcp", "serverless_config": {
"cloud": "aws", # Choose between 'aws' or 'gcp' or 'azure'
"region": "us-east-1"
},
"metric": "cosine" "metric": "cosine"
} }
} }
@@ -40,28 +44,29 @@ Here are the parameters available for configuring Pinecone:
| Parameter | Description | Default Value | | Parameter | Description | Default Value |
| --- | --- | --- | | --- | --- | --- |
| `collection_name` | Name of the index/collection | Required | | `collection_name` | Name of the index/collection | Required |
| `embedding_model_dims` | Dimensions of the embedding model | Required | | `embedding_model_dims` | Dimensions of the embedding model (must match your chosen embedding model) | Required |
| `client` | Existing Pinecone client instance | `None` | | `client` | Existing Pinecone client instance | `None` |
| `api_key` | API key for Pinecone | Environment variable: `PINECONE_API_KEY` | | `api_key` | API key for Pinecone | Environment variable: `PINECONE_API_KEY` |
| `environment` | Pinecone environment | `None` | | `environment` | Pinecone environment | `None` |
| `serverless_config` | Configuration for serverless deployment | `None` | | `serverless_config` | Configuration for serverless deployment (AWS or GCP or Azure) | `None` |
| `pod_config` | Configuration for pod-based deployment | `None` | | `pod_config` | Configuration for pod-based deployment | `None` |
| `hybrid_search` | Whether to enable hybrid search | `False` | | `hybrid_search` | Whether to enable hybrid search | `False` |
| `metric` | Distance metric for vector similarity | `"cosine"` | | `metric` | Distance metric for vector similarity | `"cosine"` |
| `batch_size` | Batch size for operations | `100` | | `batch_size` | Batch size for operations | `100` |
#### Serverless Config Example > **Important**: You must choose either `serverless_config` or `pod_config` for your deployment, but not both.
#### Serverless Config Example
```python ```python
config = { config = {
"vector_store": { "vector_store": {
"provider": "pinecone", "provider": "pinecone",
"config": { "config": {
"collection_name": "memory_index", "collection_name": "memory_index",
"embedding_model_dims": 1536, "embedding_model_dims": 1536, # For OpenAI's text-embedding-3-small
"serverless_config": { "serverless_config": {
"cloud": "aws", "cloud": "aws", # or "gcp" or "azure"
"region": "us-west-2" "region": "us-east-1" # Choose appropriate region
} }
} }
} }
@@ -69,14 +74,13 @@ config = {
``` ```
#### Pod Config Example #### Pod Config Example
```python ```python
config = { config = {
"vector_store": { "vector_store": {
"provider": "pinecone", "provider": "pinecone",
"config": { "config": {
"collection_name": "memory_index", "collection_name": "memory_index",
"embedding_model_dims": 1536, "embedding_model_dims": 1536, # For OpenAI's text-embedding-ada-002
"pod_config": { "pod_config": {
"environment": "gcp-starter", "environment": "gcp-starter",
"replicas": 1, "replicas": 1,
+93 -1
View File
@@ -4,7 +4,8 @@ Create a [Supabase](https://supabase.com/dashboard/projects) account and project
### Usage ### Usage
```python <CodeGroup>
```python Python
import os import os
from mem0 import Memory from mem0 import Memory
@@ -32,10 +33,90 @@ messages = [
m.add(messages, user_id="alice", metadata={"category": "movies"}) m.add(messages, user_id="alice", metadata={"category": "movies"})
``` ```
```typescript Typescript
import { Memory } from "mem0ai/oss";
const config = {
vectorStore: {
provider: "supabase",
config: {
collectionName: "memories",
embeddingModelDims: 1536,
supabaseUrl: process.env.SUPABASE_URL || "",
supabaseKey: process.env.SUPABASE_KEY || "",
tableName: "memories",
},
},
}
const memory = new Memory(config);
const 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."}
]
await memory.add(messages, { userId: "alice", metadata: { category: "movies" } });
```
</CodeGroup>
### SQL Migrations for TypeScript Implementation
The following SQL migrations are required to enable the vector extension and create the memories table:
```sql
-- Enable the vector extension
create extension if not exists vector;
-- Create the memories table
create table if not exists memories (
id text primary key,
embedding vector(1536),
metadata jsonb,
created_at timestamp with time zone default timezone('utc', now()),
updated_at timestamp with time zone default timezone('utc', now())
);
-- Create the vector similarity search function
create or replace function match_vectors(
query_embedding vector(1536),
match_count int,
filter jsonb default '{}'::jsonb
)
returns table (
id text,
similarity float,
metadata jsonb
)
language plpgsql
as $$
begin
return query
select
t.id::text,
1 - (t.embedding <=> query_embedding) as similarity,
t.metadata
from memories t
where case
when filter::text = '{}'::text then true
else t.metadata @> filter
end
order by t.embedding <=> query_embedding
limit match_count;
end;
$$;
```
Goto [Supabase](https://supabase.com/dashboard/projects) and run the above SQL migrations inside the SQL Editor.
### Config ### Config
Here are the parameters available for configuring Supabase: Here are the parameters available for configuring Supabase:
<Tabs>
<Tab title="Python">
| Parameter | Description | Default Value | | Parameter | Description | Default Value |
| --- | --- | --- | | --- | --- | --- |
| `connection_string` | PostgreSQL connection string (required) | None | | `connection_string` | PostgreSQL connection string (required) | None |
@@ -43,6 +124,17 @@ Here are the parameters available for configuring Supabase:
| `embedding_model_dims` | Dimensions of the embedding model | `1536` | | `embedding_model_dims` | Dimensions of the embedding model | `1536` |
| `index_method` | Vector index method to use | `auto` | | `index_method` | Vector index method to use | `auto` |
| `index_measure` | Distance measure for similarity search | `cosine_distance` | | `index_measure` | Distance measure for similarity search | `cosine_distance` |
</Tab>
<Tab title="TypeScript">
| Parameter | Description | Default Value |
| --- | --- | --- |
| `collectionName` | Name for the vector collection | `mem0` |
| `embeddingModelDims` | Dimensions of the embedding model | `1536` |
| `supabaseUrl` | Supabase URL | None |
| `supabaseKey` | Supabase key | None |
| `tableName` | Name for the vector table | `memories` |
</Tab>
</Tabs>
### Index Methods ### Index Methods
+1
View File
@@ -27,6 +27,7 @@ See the list of supported vector databases below.
<Card title="Supabase" href="/components/vectordbs/dbs/supabase"></Card> <Card title="Supabase" href="/components/vectordbs/dbs/supabase"></Card>
<Card title="Vertex AI Vector Search" href="/components/vectordbs/dbs/vertex_ai_vector_search"></Card> <Card title="Vertex AI Vector Search" href="/components/vectordbs/dbs/vertex_ai_vector_search"></Card>
<Card title="Weaviate" href="/components/vectordbs/dbs/weaviate"></Card> <Card title="Weaviate" href="/components/vectordbs/dbs/weaviate"></Card>
<Card title="FAISS" href="/components/vectordbs/dbs/faiss"></Card>
</CardGroup> </CardGroup>
## Usage ## Usage
+34 -3
View File
@@ -193,7 +193,10 @@
"examples/multimodal-demo", "examples/multimodal-demo",
"examples/personalized-deep-research", "examples/personalized-deep-research",
"examples/mem0-agentic-tool", "examples/mem0-agentic-tool",
"examples/openai-inbuilt-tools" "examples/openai-inbuilt-tools",
"examples/mem0-openai-voice-demo",
"examples/mem0-livekit-voice-agent",
"examples/email_processing"
] ]
} }
] ]
@@ -214,7 +217,9 @@
"integrations/llama-index", "integrations/llama-index",
"integrations/langchain-tools", "integrations/langchain-tools",
"integrations/dify", "integrations/dify",
"integrations/mcp-server" "integrations/mcp-server",
"integrations/livekit",
"integrations/elevenlabs"
] ]
} }
] ]
@@ -245,7 +250,8 @@
"api-reference/memory/batch-delete", "api-reference/memory/batch-delete",
"api-reference/memory/delete-memories", "api-reference/memory/delete-memories",
"api-reference/memory/create-memory-export", "api-reference/memory/create-memory-export",
"api-reference/memory/get-memory-export" "api-reference/memory/get-memory-export",
"api-reference/memory/feedback"
] ]
}, },
{ {
@@ -268,6 +274,18 @@
"api-reference/organization/delete-org" "api-reference/organization/delete-org"
] ]
}, },
{
"group": "Project APIs",
"icon": "folder",
"pages": [
"api-reference/project/create-project",
"api-reference/project/get-projects",
"api-reference/project/get-project",
"api-reference/project/get-project-members",
"api-reference/project/add-project-member",
"api-reference/project/delete-project"
]
},
{ {
"group": "Webhook APIs", "group": "Webhook APIs",
"icon": "webhook", "icon": "webhook",
@@ -281,6 +299,19 @@
] ]
} }
] ]
},
{
"tab": "Changelog",
"icon": "clock",
"groups": [
{
"group": "Product Updates",
"icon": "rocket",
"pages": [
"changelog/overview"
]
}
]
} }
] ]
}, },
+186
View File
@@ -0,0 +1,186 @@
---
title: Email Processing with Mem0
---
This guide demonstrates how to build an intelligent email processing system using Mem0's memory capabilities. You'll learn how to store, categorize, retrieve, and analyze emails to create a smart email management solution.
## Overview
Email overload is a common challenge for many professionals. By leveraging Mem0's memory capabilities, you can build an intelligent system that:
- Stores emails as searchable memories
- Categorizes emails automatically
- Retrieves relevant past conversations
- Prioritizes messages based on importance
- Generates summaries and action items
## Setup
Before you begin, ensure you have the required dependencies installed:
```bash
pip install mem0ai openai
```
## Implementation
### Basic Email Memory System
The following example shows how to create a basic email processing system with Mem0:
```python
import os
from mem0 import MemoryClient
from email.parser import Parser
# Configure API keys
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
# Initialize Mem0 client
client = MemoryClient()
class EmailProcessor:
def __init__(self):
"""Initialize the Email Processor with Mem0 memory client"""
self.client = client
def process_email(self, email_content, user_id):
"""
Process an email and store it in Mem0 memory
Args:
email_content (str): Raw email content
user_id (str): User identifier for memory association
"""
# Parse email
parser = Parser()
email = parser.parsestr(email_content)
# Extract email details
sender = email['from']
recipient = email['to']
subject = email['subject']
date = email['date']
body = self._get_email_body(email)
# Create message object for Mem0
message = {
"role": "user",
"content": f"Email from {sender}: {subject}\n\n{body}"
}
# Create metadata for better retrieval
metadata = {
"email_type": "incoming",
"sender": sender,
"recipient": recipient,
"subject": subject,
"date": date
}
# Store in Mem0 with appropriate categories
response = self.client.add(
messages=[message],
user_id=user_id,
metadata=metadata,
categories=["email", "correspondence"],
version="v2"
)
return response
def _get_email_body(self, email):
"""Extract the body content from an email"""
# Simplified extraction - in real-world, handle multipart emails
if email.is_multipart():
for part in email.walk():
if part.get_content_type() == "text/plain":
return part.get_payload(decode=True).decode()
else:
return email.get_payload(decode=True).decode()
def search_emails(self, query, user_id):
"""
Search through stored emails
Args:
query (str): Search query
user_id (str): User identifier
"""
# Search Mem0 for relevant emails
results = self.client.search(
query=query,
user_id=user_id,
categories=["email"],
output_format="v1.1",
version="v2"
)
return results
def get_email_thread(self, subject, user_id):
"""
Retrieve all emails in a thread based on subject
Args:
subject (str): Email subject to match
user_id (str): User identifier
"""
filters = {
"AND": [
{"user_id": user_id},
{"categories": {"contains": "email"}},
{"metadata": {"subject": {"contains": subject}}}
]
}
thread = self.client.get_all(
version="v2",
filters=filters,
output_format="v1.1"
)
return thread
# Initialize the processor
processor = EmailProcessor()
# Example raw email
sample_email = """From: alice@example.com
To: bob@example.com
Subject: Meeting Schedule Update
Date: Mon, 15 Jul 2024 14:22:05 -0700
Hi Bob,
I wanted to update you on the schedule for our upcoming project meeting.
We'll be meeting this Thursday at 2pm instead of Friday.
Could you please prepare your section of the presentation?
Thanks,
Alice
"""
# Process and store the email
user_id = "bob@example.com"
processor.process_email(sample_email, user_id)
# Later, search for emails about meetings
meeting_emails = processor.search_emails("meeting schedule", user_id)
print(f"Found {len(meeting_emails['results'])} relevant emails")
```
## Key Features and Benefits
- **Long-term Email Memory**: Store and retrieve email conversations across long periods
- **Semantic Search**: Find relevant emails even if they don't contain exact keywords
- **Intelligent Categorization**: Automatically sort emails into meaningful categories
- **Action Item Extraction**: Identify and track tasks mentioned in emails
- **Priority Management**: Focus on important emails based on AI-determined priority
- **Context Awareness**: Maintain thread context for more relevant interactions
## Conclusion
By combining Mem0's memory capabilities with email processing, you can create intelligent email management systems that help users organize, prioritize, and act on their inbox effectively. The advanced capabilities like automatic categorization, action item extraction, and priority management can significantly reduce the time spent on email management, allowing users to focus on more important tasks.
+538
View File
@@ -0,0 +1,538 @@
---
title: 'Mem0 with OpenAI Agents SDK for Voice'
description: 'Integrate memory capabilities into your voice agents using Mem0 and OpenAI Agents SDK'
---
# Building Voice Agents with Memory using Mem0 and OpenAI Agents SDK
This guide demonstrates how to combine OpenAI's Agents SDK for voice applications with Mem0's memory capabilities to create a voice assistant that remembers user preferences and past interactions.
## Prerequisites
Before you begin, make sure you have:
1. Installed OpenAI Agents SDK with voice dependencies:
```bash
pip install 'openai-agents[voice]'
```
2. Installed Mem0 SDK:
```bash
pip install mem0ai
```
3. Installed other required dependencies:
```bash
pip install numpy sounddevice pydantic
```
4. Set up your API keys:
- OpenAI API key for the Agents SDK
- Mem0 API key from the Mem0 Platform
## Code Breakdown
Let's break down the key components of this implementation:
### 1. Setting Up Dependencies and Environment
```python
# OpenAI Agents SDK imports
from agents import (
Agent,
function_tool
)
from agents.voice import (
AudioInput,
SingleAgentVoiceWorkflow,
VoicePipeline
)
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
# Mem0 imports
from mem0 import AsyncMemoryClient
# Set up API keys (replace with your actual keys)
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
# Define a global user ID for simplicity
USER_ID = "voice_user"
# Initialize Mem0 client
mem0_client = AsyncMemoryClient()
```
This section handles:
- Importing required modules from OpenAI Agents SDK and Mem0
- Setting up environment variables for API keys
- Defining a simple user identification system (using a global variable)
- Initializing the Mem0 client that will handle memory operations
### 2. Memory Tools with Function Decorators
The `@function_tool` decorator transforms Python functions into callable tools for the OpenAI agent. Here are the key memory tools:
#### Storing User Memories
```python
import logging
# Set up logging at the top of your file
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
force=True
)
logger = logging.getLogger("memory_voice_agent")
# Then use logger in your function tools
@function_tool
async def save_memories(
memory: str
) -> str:
"""Store a user memory in memory."""
# This will be visible in your console
logger.debug(f"Saving memory: {memory} for user {USER_ID}")
# Store the preference in Mem0
memory_content = f"User memory - {memory}"
await mem0_client.add(
memory_content,
user_id=USER_ID,
)
return f"I've saved your memory: {memory}"
```
This function:
- Takes a memory string
- Creates a formatted memory string
- Stores it in Mem0 using the `add()` method
- Includes metadata to categorize the memory for easier retrieval
- Returns a confirmation message that the agent will speak
#### Finding Relevant Memories
```python
@function_tool
async def search_memories(
query: str
) -> str:
"""
Find memories relevant to the current conversation.
Args:
query: The search query to find relevant memories
"""
print(f"Finding memories related to: {query}")
results = await mem0_client.search(
query,
user_id=USER_ID,
limit=5,
threshold=0.7, # Higher threshold for more relevant results
output_format="v1.1"
)
# Format and return the results
if not results.get('results', []):
return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results.get('results', [])]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
```
This tool:
- Takes a search query string
- Passes it to Mem0's semantic search to find related memories
- Sets a threshold for relevance to ensure quality results
- Returns a formatted list of relevant memories or a default message
### 3. Creating the Voice Agent
```python
def create_memory_voice_agent():
# Create the agent with memory-enabled tools
agent = Agent(
name="Memory Assistant",
instructions=prompt_with_handoff_instructions(
"""You're speaking to a human, so be polite and concise.
Always respond in clear, natural English.
You have the ability to remember information about the user.
Use the save_memories tool when the user shares an important information worth remembering.
Use the search_memories tool when you need context from past conversations or user asks you to recall something.
""",
),
model="gpt-4o",
tools=[save_memories, search_memories],
)
return agent
```
This function:
- Creates an OpenAI Agent with specific instructions
- Configures it to use gpt-4o (you can use other models)
- Registers the memory-related tools with the agent
- Uses `prompt_with_handoff_instructions` to include standard voice agent behaviors
### 4. Microphone Recording Functionality
```python
async def record_from_microphone(duration=5, samplerate=24000):
"""Record audio from the microphone for a specified duration."""
print(f"Recording for {duration} seconds...")
# Create a buffer to store the recorded audio
frames = []
# Callback function to store audio data
def callback(indata, frames_count, time_info, status):
frames.append(indata.copy())
# Start recording
with sd.InputStream(samplerate=samplerate, channels=1, callback=callback, dtype=np.int16):
await asyncio.sleep(duration)
# Combine all frames into a single numpy array
audio_data = np.concatenate(frames)
return audio_data
```
This function:
- Creates a simple asynchronous microphone recording function
- Uses the sounddevice library to capture audio input
- Stores frames in a buffer during recording
- Combines frames into a single numpy array when complete
- Returns the audio data for processing
### 5. Main Loop and Voice Processing
```python
async def main():
# Create the agent
agent = create_memory_voice_agent()
# Set up the voice pipeline
pipeline = VoicePipeline(
workflow=SingleAgentVoiceWorkflow(agent)
)
# Configure TTS settings
pipeline.config.tts_settings.voice = "alloy"
pipeline.config.tts_settings.speed = 1.0
try:
while True:
# Get user input
print("\nPress Enter to start recording (or 'q' to quit)...")
user_input = input()
if user_input.lower() == 'q':
break
# Record and process audio
audio_data = await record_from_microphone(duration=5)
audio_input = AudioInput(buffer=audio_data)
result = await pipeline.run(audio_input)
# Play response and handle events
player = sd.OutputStream(samplerate=24000, channels=1, dtype=np.int16)
player.start()
agent_response = ""
print("\nAgent response:")
async for event in result.stream():
if event.type == "voice_stream_event_audio":
player.write(event.data)
elif event.type == "voice_stream_event_content":
content = event.data
agent_response += content
print(content, end="", flush=True)
# Save the agent's response to memory
if agent_response:
try:
await mem0_client.add(
f"Agent response: {agent_response}",
user_id=USER_ID,
metadata={"type": "agent_response"}
)
except Exception as e:
print(f"Failed to store memory: {e}")
except KeyboardInterrupt:
print("\nExiting...")
```
This main function orchestrates the entire process:
1. Creates the memory-enabled voice agent
2. Sets up the voice pipeline with TTS settings
3. Implements an interactive loop for recording and processing voice input
4. Handles streaming of response events (both audio and text)
5. Automatically saves the agent's responses to memory
6. Includes proper error handling and exit mechanisms
## Create a Memory-Enabled Voice Agent
Now that we've explained each component, here's the complete implementation that combines OpenAI Agents SDK for voice with Mem0's memory capabilities:
```python
import asyncio
import os
import logging
from typing import Optional, List, Dict, Any
import numpy as np
import sounddevice as sd
from pydantic import BaseModel
# OpenAI Agents SDK imports
from agents import (
Agent,
function_tool
)
from agents.voice import (
AudioInput,
SingleAgentVoiceWorkflow,
VoicePipeline
)
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
# Mem0 imports
from mem0 import AsyncMemoryClient
# Set up API keys (replace with your actual keys)
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
# Define a global user ID for simplicity
USER_ID = "voice_user"
# Initialize Mem0 client
mem0_client = AsyncMemoryClient()
# Create tools that utilize Mem0's memory
@function_tool
async def save_memories(
memory: str
) -> str:
"""
Store a user memory in memory.
Args:
memory: The memory to save
"""
print(f"Saving memory: {memory} for user {USER_ID}")
# Store the preference in Mem0
memory_content = f"User memory - {memory}"
await mem0_client.add(
memory_content,
user_id=USER_ID,
)
return f"I've saved your memory: {memory}"
@function_tool
async def search_memories(
query: str
) -> str:
"""
Find memories relevant to the current conversation.
Args:
query: The search query to find relevant memories
"""
print(f"Finding memories related to: {query}")
results = await mem0_client.search(
query,
user_id=USER_ID,
limit=5,
threshold=0.7, # Higher threshold for more relevant results
output_format="v1.1"
)
# Format and return the results
if not results.get('results', []):
return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results.get('results', [])]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
# Create the agent with memory-enabled tools
def create_memory_voice_agent():
# Create the agent with memory-enabled tools
agent = Agent(
name="Memory Assistant",
instructions=prompt_with_handoff_instructions(
"""You're speaking to a human, so be polite and concise.
Always respond in clear, natural English.
You have the ability to remember information about the user.
Use the save_memories tool when the user shares an important information worth remembering.
Use the search_memories tool when you need context from past conversations or user asks you to recall something.
""",
),
model="gpt-4o",
tools=[save_memories, search_memories],
)
return agent
async def record_from_microphone(duration=5, samplerate=24000):
"""Record audio from the microphone for a specified duration."""
print(f"Recording for {duration} seconds...")
# Create a buffer to store the recorded audio
frames = []
# Callback function to store audio data
def callback(indata, frames_count, time_info, status):
frames.append(indata.copy())
# Start recording
with sd.InputStream(samplerate=samplerate, channels=1, callback=callback, dtype=np.int16):
await asyncio.sleep(duration)
# Combine all frames into a single numpy array
audio_data = np.concatenate(frames)
return audio_data
async def main():
print("Starting Memory Voice Agent")
# Create the agent and context
agent = create_memory_voice_agent()
# Set up the voice pipeline
pipeline = VoicePipeline(
workflow=SingleAgentVoiceWorkflow(agent)
)
# Configure TTS settings
pipeline.config.tts_settings.voice = "alloy"
pipeline.config.tts_settings.speed = 1.0
try:
while True:
# Get user input
print("\nPress Enter to start recording (or 'q' to quit)...")
user_input = input()
if user_input.lower() == 'q':
break
# Record and process audio
audio_data = await record_from_microphone(duration=5)
audio_input = AudioInput(buffer=audio_data)
print("Processing your request...")
# Process the audio input
result = await pipeline.run(audio_input)
# Create an audio player
player = sd.OutputStream(samplerate=24000, channels=1, dtype=np.int16)
player.start()
# Store the agent's response for adding to memory
agent_response = ""
print("\nAgent response:")
# Play the audio stream as it comes in
async for event in result.stream():
if event.type == "voice_stream_event_audio":
player.write(event.data)
elif event.type == "voice_stream_event_content":
# Accumulate and print the text response
content = event.data
agent_response += content
print(content, end="", flush=True)
print("\n")
# Example of saving the conversation to Mem0 after completion
if agent_response:
try:
await mem0_client.add(
f"Agent response: {agent_response}",
user_id=USER_ID,
metadata={"type": "agent_response"}
)
except Exception as e:
print(f"Failed to store memory: {e}")
except KeyboardInterrupt:
print("\nExiting...")
if __name__ == "__main__":
asyncio.run(main())
```
## Key Features of This Implementation
This implementation offers several key features:
1. **Simplified User Management**: Uses a global `USER_ID` variable for simplicity, but can be extended to manage multiple users.
2. **Real Microphone Input**: Includes a `record_from_microphone()` function that captures actual voice input from your microphone.
3. **Interactive Voice Loop**: Implements a continuous interaction loop, allowing for multiple back-and-forth exchanges.
4. **Memory Management Tools**:
- `save_memories`: Stores user memories in Mem0
- `search_memories`: Searches for relevant past information
5. **Voice Configuration**: Demonstrates how to configure TTS settings for the voice response.
## Running the Example
To run this example:
1. Replace the placeholder API keys with your actual keys
2. Make sure your microphone is properly connected
3. Run the script with Python 3.8 or newer
4. Press Enter to start recording, then speak your request
5. Press 'q' to quit the application
The agent will listen to your request, process it through the OpenAI model, utilize Mem0 for memory operations as needed, and respond both through text output and voice speech.
## Best Practices for Voice Agents with Memory
1. **Optimizing Memory for Voice**: Keep memories concise and relevant for voice responses.
2. **Forgetting Mechanism**: Implement a way to delete or expire memories that are no longer relevant.
3. **Context Preservation**: Store enough context with each memory to make retrieval effective.
4. **Error Handling**: Implement robust error handling for memory operations, as voice interactions should continue smoothly even if memory operations fail.
## Conclusion
By combining OpenAI's Agents SDK with Mem0's memory capabilities, you can create voice agents that maintain persistent memory of user preferences and past interactions. This significantly enhances the user experience by making conversations more natural and personalized.
As you build your voice application, experiment with different memory strategies and filtering approaches to find the optimal balance between comprehensive memory and efficient retrieval for your specific use case.
## Debugging Function Tools
When working with the OpenAI Agents SDK, you might notice that regular `print()` statements inside `@function_tool` decorated functions don't appear in your console output. This is because the Agents SDK captures and redirects standard output when executing these functions.
To effectively debug your function tools, use Python's `logging` module instead:
```python
import logging
# Set up logging at the top of your file
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
force=True
)
logger = logging.getLogger("memory_voice_agent")
# Then use logger in your function tools
@function_tool
async def save_memories(
memory: str
) -> str:
"""Store a user memory in memory."""
# This will be visible in your console
logger.debug(f"Saving memory: {memory} for user {USER_ID}")
# Rest of your function...
```
+9 -1
View File
@@ -55,7 +55,7 @@ Explore how **Mem0** can power real-world applications and bring personalized, i
Supercharge AI with **Mem0's multimodal memory** — blend text, images, and more for richer, context-aware interactions. Supercharge AI with **Mem0's multimodal memory** — blend text, images, and more for richer, context-aware interactions.
</Card> </Card>
<Card title="Personalized Research Agent" icon="robot" href="/examples/personalized-deep-research"> <Card title="Personalized Research Agent" icon="magnifying-glass" href="/examples/personalized-deep-research">
Build a **Deep Research AI** that remembers your research goals and compiles insights from vast information sources. Build a **Deep Research AI** that remembers your research goals and compiles insights from vast information sources.
</Card> </Card>
@@ -66,4 +66,12 @@ Explore how **Mem0** can power real-world applications and bring personalized, i
<Card title="OpenAI Inbuilt Tools" icon="robot" href="/examples/openai-inbuilt-tools"> <Card title="OpenAI Inbuilt Tools" icon="robot" href="/examples/openai-inbuilt-tools">
Use Mem0's memory capabilities with OpenAI's Inbuilt Tools to create AI agents with persistent memory. Use Mem0's memory capabilities with OpenAI's Inbuilt Tools to create AI agents with persistent memory.
</Card> </Card>
<Card title="Mem0 OpenAI Voice Demo" icon="microphone" href="/examples/mem0-openai-voice-demo">
Use Mem0's memory capabilities with OpenAI's Inbuilt Tools to create AI agents with persistent memory.
</Card>
<Card title="Email Processing" icon="envelope" href="/examples/email_processing">
Use Mem0's memory capabilities to process emails and create AI agents with persistent memory.
</Card>
</CardGroup> </CardGroup>
+2 -2
View File
@@ -17,9 +17,9 @@ You can give feedback on a memory by calling the `feedback` method on the Mem0 c
<CodeGroup> <CodeGroup>
```python Python ```python Python
from mem0 import Mem0 from mem0 import MemoryClient
client = Mem0(api_key="your_api_key") client = MemoryClient(api_key="your_api_key")
client.feedback(memory_id="your-memory-id", feedback="NEGATIVE", feedback_reason="I don't like this memory because it is not relevant.") client.feedback(memory_id="your-memory-id", feedback="NEGATIVE", feedback_reason="I don't like this memory because it is not relevant.")
``` ```
+142 -8
View File
@@ -1,15 +1,15 @@
--- ---
title: Multimodal Support title: Multimodal Support
description: Integrate images into your interactions with Mem0 description: Integrate images and documents into your interactions with Mem0
icon: "image" icon: "image"
iconType: "solid" iconType: "solid"
--- ---
Mem0 extends its capabilities beyond text by supporting multimodal data, including images. With this feature, users can seamlessly integrate images into their interactions—allowing Mem0 to extract relevant information from visual content and enrich the memory system. Mem0 extends its capabilities beyond text by supporting multimodal data, including images and documents. With this feature, users can seamlessly integrate visual and document content into their interactions—allowing Mem0 to extract relevant information from various media types and enrich the memory system.
## How It Works ## How It Works
When a user submits an image, Mem0 processes it to extract textual information and other pertinent details. These details are then added to the user's memory, enhancing the system's ability to understand and recall visual inputs. When a user submits an image or document, Mem0 processes it to extract textual information and other pertinent details. These details are then added to the user's memory, enhancing the system's ability to understand and recall multimodal inputs.
<CodeGroup> <CodeGroup>
```python Python ```python Python
@@ -90,11 +90,18 @@ await client.add(messages, { user_id: "alice" })
``` ```
</CodeGroup> </CodeGroup>
## Image Integration Methods ## Supported Media Types
Mem0 supports incorporating images into user interactions using two primary methods: by providing an image URL or by using a Base64-encoded image. The examples below demonstrate both approaches. Mem0 currently supports the following media types:
## 1. Using an Image URL (Recommended) 1. **Images** - JPG, PNG, and other common image formats
2. **Documents** - MDX, TXT, and PDF files
## Integration Methods
### 1. Images
#### Using an Image URL (Recommended)
You can include an image by providing its direct URL. This method is simple and efficient for online images. You can include an image by providing its direct URL. This method is simple and efficient for online images.
@@ -115,7 +122,7 @@ image_message = {
client.add([image_message], user_id="alice") client.add([image_message], user_id="alice")
``` ```
## 2. Using Base64 Image Encoding for Local Files #### Using Base64 Image Encoding for Local Files
For local images—or when embedding the image directly is preferable—you can use a Base64-encoded string. For local images—or when embedding the image directly is preferable—you can use a Base64-encoded string.
@@ -164,7 +171,134 @@ const imageMessage = {
await client.add([imageMessage], { user_id: "alice" }) await client.add([imageMessage], { user_id: "alice" })
``` ```
</CodeGroup> </CodeGroup>
Using these methods, you can seamlessly incorporate images into your interactions, further enhancing Mem0's multimodal capabilities.
### 2. Text Documents (MDX/TXT)
Mem0 supports both online and local text documents in MDX or TXT format.
#### Using a Document URL
```python
# Define the document URL
document_url = "https://www.w3.org/TR/2003/REC-PNG-20031110/iso_8859-1.txt"
# Create the message dictionary with the document URL
document_message = {
"role": "user",
"content": {
"type": "mdx_url",
"mdx_url": {
"url": document_url
}
}
}
client.add([document_message], user_id="alice")
```
#### Using Base64 Encoding for Local Documents
```python
import base64
# Path to the document file
document_path = "path/to/your/document.txt"
# Function to convert file to Base64
def file_to_base64(file_path):
with open(file_path, "rb") as file:
return base64.b64encode(file.read()).decode('utf-8')
# Encode the document in Base64
base64_document = file_to_base64(document_path)
# Create the message dictionary with the Base64-encoded document
document_message = {
"role": "user",
"content": {
"type": "mdx_url",
"mdx_url": {
"url": base64_document
}
}
}
client.add([document_message], user_id="alice")
```
### 3. PDF Documents
Mem0 supports PDF documents via URL.
```python
# Define the PDF URL
pdf_url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
# Create the message dictionary with the PDF URL
pdf_message = {
"role": "user",
"content": {
"type": "pdf_url",
"pdf_url": {
"url": pdf_url
}
}
}
client.add([pdf_message], user_id="alice")
```
## Complete Example with Multiple File Types
Here's a comprehensive example showing how to work with different file types:
```python
import base64
from mem0 import MemoryClient
client = MemoryClient()
def file_to_base64(file_path):
with open(file_path, "rb") as file:
return base64.b64encode(file.read()).decode('utf-8')
# Example 1: Using an image URL
image_message = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {
"url": "https://example.com/sample-image.jpg"
}
}
}
# Example 2: Using a text document URL
text_message = {
"role": "user",
"content": {
"type": "mdx_url",
"mdx_url": {
"url": "https://www.w3.org/TR/2003/REC-PNG-20031110/iso_8859-1.txt"
}
}
}
# Example 3: Using a PDF URL
pdf_message = {
"role": "user",
"content": {
"type": "pdf_url",
"pdf_url": {
"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
}
}
}
# Add each message to the memory system
client.add([image_message], user_id="alice")
client.add([text_message], user_id="alice")
client.add([pdf_message], user_id="alice")
```
Using these methods, you can seamlessly incorporate various media types into your interactions, further enhancing Mem0's multimodal capabilities.
If you have any questions, please feel free to reach out to us using one of the following methods: If you have any questions, please feel free to reach out to us using one of the following methods:
+454
View File
@@ -0,0 +1,454 @@
---
title: ElevenLabs
---
Create voice-based conversational AI agents with memory capabilities by integrating ElevenLabs and Mem0. This integration enables persistent, context-aware voice interactions that remember past conversations.
## Overview
In this guide, we'll build a voice agent that:
1. Uses ElevenLabs Conversational AI for voice interaction
2. Leverages Mem0 to store and retrieve memories from past conversations
3. Provides personalized responses based on user history
## Setup and Configuration
Install necessary libraries:
```bash
pip install elevenlabs mem0 python-dotenv
```
Configure your environment variables:
<Note>You'll need both an ElevenLabs API key and a Mem0 API key to use this integration.</Note>
```bash
# Create a .env file with these variables
AGENT_ID=your-agent-id
USER_ID=unique-user-identifier
ELEVENLABS_API_KEY=your-elevenlabs-api-key
MEM0_API_KEY=your-mem0-api-key
```
## Integration Code Breakdown
Let's break down the implementation into manageable parts:
### 1. Imports and Environment Setup
First, we import required libraries and set up the environment:
```python
import os
import signal
import sys
from mem0 import AsyncMemoryClient
from elevenlabs.client import ElevenLabs
from elevenlabs.conversational_ai.conversation import Conversation
from elevenlabs.conversational_ai.default_audio_interface import DefaultAudioInterface
from elevenlabs.conversational_ai.conversation import ClientTools
```
These imports provide:
- Standard Python libraries for system operations and signal handling
- `AsyncMemoryClient` from Mem0 for memory operations
- ElevenLabs components for voice interaction
### 2. Environment Variables and Validation
Next, we validate the required environment variables:
```python
def main():
# Required environment variables
AGENT_ID = os.environ.get('AGENT_ID')
USER_ID = os.environ.get('USER_ID')
API_KEY = os.environ.get('ELEVENLABS_API_KEY')
MEM0_API_KEY = os.environ.get('MEM0_API_KEY')
# Validate required environment variables
if not AGENT_ID:
sys.stderr.write("AGENT_ID environment variable must be set\n")
sys.exit(1)
if not USER_ID:
sys.stderr.write("USER_ID environment variable must be set\n")
sys.exit(1)
if not API_KEY:
sys.stderr.write("ELEVENLABS_API_KEY not set, assuming the agent is public\n")
if not MEM0_API_KEY:
sys.stderr.write("MEM0_API_KEY environment variable must be set\n")
sys.exit(1)
# Set up Mem0 API key in the environment
os.environ['MEM0_API_KEY'] = MEM0_API_KEY
```
This section:
- Retrieves required environment variables
- Performs validation to ensure required variables are present
- Exits the application with an error message if required variables are missing
- Sets the Mem0 API key in the environment for the Mem0 client to use
### 3. Client Initialization
Initialize both the ElevenLabs and Mem0 clients:
```python
# Initialize ElevenLabs client
client = ElevenLabs(api_key=API_KEY)
# Initialize memory client and tools
client_tools = ClientTools()
mem0_client = AsyncMemoryClient()
```
Here we:
- Create an ElevenLabs client with the API key
- Initialize a ClientTools object for registering function tools
- Create an AsyncMemoryClient instance for Mem0 interactions
### 4. Memory Function Definitions
Define the two key memory functions that will be registered as tools:
```python
# Define memory-related functions for the agent
async def add_memories(parameters):
"""Add a message to the memory store"""
message = parameters.get("message")
await mem0_client.add(
messages=message,
user_id=USER_ID,
output_format="v1.1",
version="v2"
)
return "Memory added successfully"
async def retrieve_memories(parameters):
"""Retrieve relevant memories based on the input message"""
message = parameters.get("message")
# Set up filters to retrieve memories for this specific user
filters = {
"AND": [
{
"user_id": USER_ID
}
]
}
# Search for relevant memories using the message as a query
results = await mem0_client.search(
query=message,
version="v2",
filters=filters
)
# Extract and join the memory texts
memories = ' '.join([result["memory"] for result in results])
print("[ Memories ]", memories)
if memories:
return memories
return "No memories found"
```
These functions:
#### `add_memories`:
- Takes a message parameter containing information to remember
- Stores the message in Mem0 using the `add` method
- Associates the memory with the specific USER_ID
- Returns a success message to the agent
#### `retrieve_memories`:
- Takes a message parameter as the search query
- Sets up filters to only retrieve memories for the current user
- Uses semantic search to find relevant memories
- Joins all retrieved memories into a single text
- Prints retrieved memories to the console for debugging
- Returns the memories or a "No memories found" message if none are found
### 5. Registering Memory Functions as Tools
Register the memory functions with the ElevenLabs ClientTools system:
```python
# Register the memory functions as tools for the agent
client_tools.register("addMemories", add_memories, is_async=True)
client_tools.register("retrieveMemories", retrieve_memories, is_async=True)
```
This allows the ElevenLabs agent to:
- Access these functions through function calling
- Wait for asynchronous results (is_async=True)
- Call these functions by name ("addMemories" and "retrieveMemories")
### 6. Conversation Setup
Configure the conversation with ElevenLabs:
```python
# Initialize the conversation
conversation = Conversation(
client,
AGENT_ID,
# Assume auth is required when API_KEY is set
requires_auth=bool(API_KEY),
audio_interface=DefaultAudioInterface(),
client_tools=client_tools,
callback_agent_response=lambda response: print(f"Agent: {response}"),
callback_agent_response_correction=lambda original, corrected: print(f"Agent: {original} -> {corrected}"),
callback_user_transcript=lambda transcript: print(f"User: {transcript}"),
# callback_latency_measurement=lambda latency: print(f"Latency: {latency}ms"),
)
```
This sets up the conversation with:
- The ElevenLabs client and Agent ID
- Authentication requirements based on API key presence
- DefaultAudioInterface for handling audio I/O
- The client_tools with our memory functions
- Callback functions for:
- Displaying agent responses
- Showing corrected responses (when the agent self-corrects)
- Displaying user transcripts for debugging
- (Commented out) Latency measurements
### 7. Conversation Management
Start and manage the conversation:
```python
# Start the conversation
print(f"Starting conversation with user_id: {USER_ID}")
conversation.start_session()
# Handle Ctrl+C to gracefully end the session
signal.signal(signal.SIGINT, lambda sig, frame: conversation.end_session())
# Wait for the conversation to end and get the conversation ID
conversation_id = conversation.wait_for_session_end()
print(f"Conversation ID: {conversation_id}")
if __name__ == '__main__':
main()
```
This final section:
- Prints a message indicating the conversation has started
- Starts the conversation session
- Sets up a signal handler to gracefully end the session on Ctrl+C
- Waits for the session to end and gets the conversation ID
- Prints the conversation ID for reference
## Memory Tools Overview
This integration provides two key memory functions to your conversational AI agent:
### 1. Adding Memories (`addMemories`)
The `addMemories` tool allows your agent to store important information during a conversation, including:
- User preferences
- Important facts shared by the user
- Decisions or commitments made during the conversation
- Action items to follow up on
When the agent identifies information worth remembering, it calls this function to store it in the Mem0 database with the appropriate user ID.
#### How it works:
1. The agent identifies information that should be remembered
2. It formats the information as a message string
3. It calls the `addMemories` function with this message
4. The function stores the memory in Mem0 linked to the user's ID
5. Later conversations can retrieve this memory
#### Example usage in agent prompt:
```
When the user shares important information like preferences or personal details,
use the addMemories function to store this information for future reference.
```
### 2. Retrieving Memories (`retrieveMemories`)
The `retrieveMemories` tool allows your agent to search for and retrieve relevant memories from previous conversations. The agent can:
- Search for context related to the current topic
- Recall user preferences
- Remember previous interactions on similar topics
- Create continuity across multiple sessions
#### How it works:
1. The agent needs context for the current conversation
2. It calls `retrieveMemories` with the current conversation topic or question
3. The function performs a semantic search in Mem0
4. Relevant memories are returned to the agent
5. The agent incorporates these memories into its response
#### Example usage in agent prompt:
```
At the beginning of each conversation turn, use retrieveMemories to check if we've
discussed this topic before or if the user has shared relevant preferences.
```
## Configuring Your ElevenLabs Agent
To enable your agent to effectively use memory:
1. Add function calling capabilities to your agent in the ElevenLabs platform:
- Go to your agent settings in the ElevenLabs platform
- Navigate to the "Tools" section
- Enable function calling for your agent
- Add the memory tools as described below
2. Add the `addMemories` and `retrieveMemories` tools to your agent with these specifications:
For `addMemories`:
```json
{
"name": "addMemories",
"description": "Stores important information from the conversation to remember for future interactions",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The important information to remember"
}
},
"required": ["message"]
}
}
```
For `retrieveMemories`:
```json
{
"name": "retrieveMemories",
"description": "Retrieves relevant information from past conversations",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The query to search for in past memories"
}
},
"required": ["message"]
}
}
```
3. Update your agent's prompt to instruct it to use these memory functions. For example:
```
You are a helpful voice assistant that remembers past conversations with the user.
You have access to memory tools that allow you to remember important information:
- Use retrieveMemories at the beginning of the conversation to recall relevant context from prior conversations
- Use addMemories to store new important information such as:
* User preferences
* Personal details the user shares
* Important decisions made
* Tasks or follow-ups promised to the user
Before responding to complex questions, always check for relevant memories first.
When the user shares important information, make sure to store it for future reference.
```
## Example Conversation Flow
Here's how a typical conversation with memory might flow:
1. **User speaks**: "Hi, do you remember my favorite color?"
2. **Agent retrieves memories**:
```python
# Agent calls retrieve_memories
memories = retrieve_memories({"message": "user's favorite color"})
# If found: "The user's favorite color is blue"
```
3. **Agent processes with context**:
- If memories found: Prepares a personalized response
- If no memories: Prepares to ask and store the information
4. **Agent responds**:
- With memory: "Yes, your favorite color is blue!"
- Without memory: "I don't think you've told me your favorite color before. What is it?"
5. **User responds**: "It's actually green."
6. **Agent stores new information**:
```python
# Agent calls add_memories
add_memories({"message": "The user's favorite color is green"})
```
7. **Agent confirms**: "Thanks, I'll remember that your favorite color is green."
## Example Use Cases
- **Personal Assistant** - Remember user preferences, past requests, and important dates
```
User: "What restaurants did I say I liked last time?"
Agent: *retrieves memories* "You mentioned enjoying Bella Italia and The Golden Dragon."
```
- **Customer Support** - Recall previous issues a customer has had
```
User: "I'm having that same problem again!"
Agent: *retrieves memories* "Is this related to the login issue you reported last week?"
```
- **Educational AI** - Track student progress and tailor teaching accordingly
```
User: "Let's continue our math lesson."
Agent: *retrieves memories* "Last time we were working on quadratic equations. Would you like to continue with that?"
```
- **Healthcare Assistant** - Remember symptoms, medications, and health concerns
```
User: "Have I told you about my allergy medication?"
Agent: *retrieves memories* "Yes, you mentioned you're taking Claritin for your pollen allergies."
```
## Troubleshooting
- **Missing API Keys**:
- Error: "API_KEY environment variable must be set"
- Solution: Ensure all environment variables are set correctly in your .env file or system environment
- **Connection Issues**:
- Error: "Failed to connect to API"
- Solution: Check your network connection and API key permissions. Verify the API keys are valid and have the necessary permissions.
- **Empty Memory Results**:
- Symptom: Agent always responds with "No memories found"
- Solution: This is normal for new users. The memory database builds up over time as conversations occur. It's also possible your query isn't semantically similar to stored memories - try different phrasing.
- **Agent Not Using Memories**:
- Symptom: The agent retrieves memories but doesn't incorporate them in responses
- Solution: Update the agent's prompt to explicitly instruct it to use the retrieved memories in its responses
## Conclusion
By integrating ElevenLabs Conversational AI with Mem0, you can create voice agents that maintain context across conversations and provide personalized responses based on user history. This powerful combination enables:
- More natural, context-aware conversations
- Personalized user experiences that improve over time
- Reduced need for users to repeat information
- Long-term relationship building between users and AI agents
## Help
- For more details on ElevenLabs, visit the [ElevenLabs Conversational AI Documentation](https://elevenlabs.io/docs/api-reference/conversational-ai)
- For Mem0 documentation, refer to the [Mem0 Platform](https://app.mem0.ai/)
- If you need further assistance, please feel free to reach out to us through the following methods:
<Snippet file="get-help.mdx" />
+353
View File
@@ -0,0 +1,353 @@
---
title: Livekit
---
This guide demonstrates how to create a memory-enabled voice assistant using LiveKit, Deepgram, OpenAI, and Mem0, focusing on creating an intelligent, context-aware travel planning agent.
## Prerequisites
Before you begin, make sure you have:
1. Installed Livekit Agents SDK with voice dependencies of silero and deepgram:
```bash
pip install livekit \
livekit-agents \
livekit-plugins-silero \
livekit-plugins-deepgram \
livekit-plugins-openai
```
2. Installed Mem0 SDK:
```bash
pip install mem0ai
```
3. Set up your API keys in a `.env` file:
```sh
LIVEKIT_URL=your_livekit_url
LIVEKIT_API_KEY=your_livekit_api_key
LIVEKIT_API_SECRET=your_livekit_api_secret
DEEPGRAM_API_KEY=your_deepgram_api_key
MEM0_API_KEY=your_mem0_api_key
OPENAI_API_KEY=your_openai_api_key
```
> **Note**: Make sure to have a Livekit and Deepgram account. You can find these variables `LIVEKIT_URL` , `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET` from [LiveKit Cloud Console](https://cloud.livekit.io/) and for more information you can refer this website [LiveKit Documentation](https://docs.livekit.io/home/cloud/keys-and-tokens/). For `DEEPGRAM_API_KEY` you can get from [Deepgram Console](https://console.deepgram.com/) refer this website [Deepgram Documentation](https://developers.deepgram.com/docs/create-additional-api-keys) for more details.
## Code Breakdown
Let's break down the key components of this implementation:
### 1. Setting Up Dependencies and Environment
```python
import asyncio
import logging
import os
from typing import List, Dict, Any, Annotated
import aiohttp
from dotenv import load_dotenv
from livekit.agents import (
AutoSubscribe,
JobContext,
JobProcess,
WorkerOptions,
cli,
llm,
metrics,
)
from livekit import rtc, api
from livekit.agents.pipeline import VoicePipelineAgent
from livekit.plugins import deepgram, openai, silero
from mem0 import AsyncMemoryClient
# Load environment variables
load_dotenv()
# Configure logging
logger = logging.getLogger("memory-assistant")
logger.setLevel(logging.INFO)
# Define a global user ID for simplicity
USER_ID = "voice_user"
# Initialize Mem0 client
mem0 = AsyncMemoryClient()
```
This section handles:
- Importing required modules
- Loading environment variables
- Setting up logging
- Extracting user identification
- Initializing the Mem0 client
### 2. Memory Enrichment Function
```python
async def _enrich_with_memory(agent: VoicePipelineAgent, chat_ctx: llm.ChatContext):
"""Add memories and Augment chat context with relevant memories"""
if not chat_ctx.messages:
return
# Store user message in Mem0
user_msg = chat_ctx.messages[-1]
await mem0.add(
[{"role": "user", "content": user_msg.content}],
user_id=USER_ID
)
# Search for relevant memories
results = await mem0.search(
user_msg.content,
user_id=USER_ID,
)
# Augment context with retrieved memories
if results:
memories = ' '.join([result["memory"] for result in results])
logger.info(f"Enriching with memory: {memories}")
rag_msg = llm.ChatMessage.create(
text=f"Relevant Memory: {memories}\n",
role="assistant",
)
# Modify chat context with retrieved memories
chat_ctx.messages[-1] = rag_msg
chat_ctx.messages.append(user_msg)
```
This function:
- Stores user messages in Mem0
- Performs semantic search for relevant memories
- Augments the chat context with retrieved memories
- Enables contextually aware responses
### 3. Prewarm and Entrypoint Functions
```python
def prewarm_process(proc: JobProcess):
# Preload silero VAD in memory to speed up session start
proc.userdata["vad"] = silero.VAD.load()
async def entrypoint(ctx: JobContext):
# Connect to LiveKit room
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
# Wait for participant
participant = await ctx.wait_for_participant()
# Initialize Mem0 client
mem0 = AsyncMemoryClient()
# Define initial system context
initial_ctx = llm.ChatContext().append(
role="system",
text=(
"""
You are a helpful voice assistant.
You are a travel guide named George and will help the user to plan a travel trip of their dreams.
You should help the user plan for various adventures like work retreats, family vacations or solo backpacking trips.
You should be careful to not suggest anything that would be dangerous, illegal or inappropriate.
You can remember past interactions and use them to inform your answers.
Use semantic memory retrieval to provide contextually relevant responses.
"""
),
)
# Create VoicePipelineAgent with memory capabilities
agent = VoicePipelineAgent(
chat_ctx=initial_ctx,
vad=silero.VAD.load(),
stt=deepgram.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
before_llm_cb=_enrich_with_memory,
)
# Start agent and initial greeting
agent.start(ctx.room, participant)
await agent.say(
"Hello! I'm George. Can I help you plan an upcoming trip? ",
allow_interruptions=True
)
# Run the application
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm_process))
```
The entrypoint function:
- Connects to LiveKit room
- Initializes Mem0 memory client
- Sets up initial system context
- Creates a VoicePipelineAgent with memory enrichment
- Starts the agent with an initial greeting
## Create a Memory-Enabled Voice Agent
Now that we've explained each component, here's the complete implementation that combines OpenAI Agents SDK for voice with Mem0's memory capabilities:
```python
import asyncio
import logging
import os
from typing import List, Dict, Any, Annotated
import aiohttp
from dotenv import load_dotenv
from livekit.agents import (
AutoSubscribe,
JobContext,
JobProcess,
WorkerOptions,
cli,
llm,
metrics,
)
from livekit import rtc, api
from livekit.agents.pipeline import VoicePipelineAgent
from livekit.plugins import deepgram, openai, silero
from mem0 import AsyncMemoryClient
# Load environment variables
load_dotenv()
# Configure logging
logger = logging.getLogger("memory-assistant")
logger.setLevel(logging.INFO)
# Define a global user ID for simplicity
USER_ID = "voice_user"
# Initialize Mem0 memory client
mem0 = AsyncMemoryClient()
def prewarm_process(proc: JobProcess):
# Preload silero VAD in memory to speed up session start
proc.userdata["vad"] = silero.VAD.load()
async def entrypoint(ctx: JobContext):
# Connect to LiveKit room
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
# Wait for participant
participant = await ctx.wait_for_participant()
async def _enrich_with_memory(agent: VoicePipelineAgent, chat_ctx: llm.ChatContext):
"""Add memories and Augment chat context with relevant memories"""
if not chat_ctx.messages:
return
# Store user message in Mem0
user_msg = chat_ctx.messages[-1]
await mem0.add(
[{"role": "user", "content": user_msg.content}],
user_id=USER_ID
)
# Search for relevant memories
results = await mem0.search(
user_msg.content,
user_id=USER_ID,
)
# Augment context with retrieved memories
if results:
memories = ' '.join([result["memory"] for result in results])
logger.info(f"Enriching with memory: {memories}")
rag_msg = llm.ChatMessage.create(
text=f"Relevant Memory: {memories}\n",
role="assistant",
)
# Modify chat context with retrieved memories
chat_ctx.messages[-1] = rag_msg
chat_ctx.messages.append(user_msg)
# Define initial system context
initial_ctx = llm.ChatContext().append(
role="system",
text=(
"""
You are a helpful voice assistant.
You are a travel guide named George and will help the user to plan a travel trip of their dreams.
You should help the user plan for various adventures like work retreats, family vacations or solo backpacking trips.
You should be careful to not suggest anything that would be dangerous, illegal or inappropriate.
You can remember past interactions and use them to inform your answers.
Use semantic memory retrieval to provide contextually relevant responses.
"""
),
)
# Create VoicePipelineAgent with memory capabilities
agent = VoicePipelineAgent(
chat_ctx=initial_ctx,
vad=silero.VAD.load(),
stt=deepgram.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
before_llm_cb=_enrich_with_memory,
)
# Start agent and initial greeting
agent.start(ctx.room, participant)
await agent.say(
"Hello! I'm George. Can I help you plan an upcoming trip? ",
allow_interruptions=True
)
# Run the application
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm_process))
```
## Key Features of This Implementation
1. **Semantic Memory Retrieval**: Uses Mem0 to store and retrieve contextually relevant memories
2. **Voice Interaction**: Leverages LiveKit for voice communication
3. **Intelligent Context Management**: Augments conversations with past interactions
4. **Travel Planning Specialization**: Focused on creating a helpful travel guide assistant
## Running the Example
To run this example:
1. Install all required dependencies
2. Set up your `.env` file with the necessary API keys
3. Ensure your microphone and audio setup are configured
4. Run the script with Python 3.11 or newer and with the following command:
```sh
python mem0-livekit-voice-agent.py start
```
5. After the script starts, you can interact with the voice agent using [Livekit's Agent Platform](https://agents-playground.livekit.io/) and Connect to the agent inorder to start conversations.
## Best Practices for Voice Agents with Memory
1. **Context Preservation**: Store enough context with each memory for effective retrieval
2. **Privacy Considerations**: Implement secure memory management
3. **Relevant Memory Filtering**: Use semantic search to retrieve only the most pertinent memories
4. **Error Handling**: Implement robust error handling for memory operations
## Debugging Function Tools
- To run the script in debug mode simply start the assistant with `dev` mode:
```sh
python mem0-livekit-voice-agent.py dev
```
- When working with memory-enabled voice agents, use Python's `logging` module for effective debugging:
```python
import logging
# Set up logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("memory_voice_agent")
```
+45
View File
@@ -220,4 +220,49 @@ Here are the available integrations for Mem0:
> >
Integrate Mem0 as an MCP Server in Cursor. Integrate Mem0 as an MCP Server in Cursor.
</Card> </Card>
<Card
title="Livekit"
icon={
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
>
<text
x="12"
y="16"
fontFamily="Arial"
fontSize="12"
textAnchor="middle"
fill="currentColor"
fontWeight="bold"
>
LK
</text>
</svg>
}
href="/integrations/livekit"
>
Integrate Mem0 with Livekit for voice agents.
</Card>
<Card
title="ElevenLabs"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<rect width="24" height="24" fill="white"/>
<rect x="8" y="4" width="2" height="16" fill="black"/>
<rect x="14" y="4" width="2" height="16" fill="black"/>
</svg>
}
href="/integrations/elevenlabs"
>
Build voice agents with memory using ElevenLabs Conversational AI.
</Card>
</CardGroup> </CardGroup>
+67 -1
View File
@@ -301,6 +301,56 @@ await memory.deleteAll({ userId: "alice" });
await memory.reset(); // Reset all memories await memory.reset(); // Reset all memories
``` ```
### History Store
Mem0 TypeScript SDK support history stores to run on a serverless environment:
We recommend using `Supabase` as a history store for serverless environments or disable history store to run on a serverless environment.
<CodeGroup>
```typescript Supabase
import { Memory } from 'mem0ai/oss';
const memory = new Memory({
historyStore: {
provider: 'supabase',
config: {
supabaseUrl: process.env.SUPABASE_URL || '',
supabaseKey: process.env.SUPABASE_KEY || '',
tableName: 'memory_history',
},
},
});
```
```typescript Disable History
import { Memory } from 'mem0ai/oss';
const memory = new Memory({
disableHistory: true,
});
```
</CodeGroup>
Mem0 uses SQLite as a default history store.
#### Create Memory History Table in Supabase
You may need to create a memory history table in Supabase to store the history of memories. Use the following SQL command in `SQL Editor` on the Supabase project dashboard to create a memory history table:
```sql
create table memory_history (
id text primary key,
memory_id text not null,
previous_value text,
new_value text,
action text not null,
created_at timestamp with time zone default timezone('utc', now()),
updated_at timestamp with time zone,
is_deleted integer default 0
);
```
## Configuration Parameters ## Configuration Parameters
Mem0 offers extensive configuration options to customize its behavior according to your needs. These configurations span across different components like vector stores, language models, embedders, and graph stores. Mem0 offers extensive configuration options to customize its behavior according to your needs. These configurations span across different components like vector stores, language models, embedders, and graph stores.
@@ -352,6 +402,14 @@ Mem0 offers extensive configuration options to customize its behavior according
| `customPrompt` | Custom prompt for memory processing | None | | `customPrompt` | Custom prompt for memory processing | None |
</Accordion> </Accordion>
<Accordion title="History Table Configuration">
| Parameter | Description | Default |
|------------------|--------------------------------------|----------------------------|
| `provider` | History store provider | "sqlite" |
| `config` | History store configuration | None (Defaults to SQLite) |
| `disableHistory` | Disable history store | false |
</Accordion>
<Accordion title="Complete Configuration Example"> <Accordion title="Complete Configuration Example">
```typescript ```typescript
const config = { const config = {
@@ -377,7 +435,15 @@ const config = {
model: 'gpt-4-turbo-preview', model: 'gpt-4-turbo-preview',
}, },
}, },
historyDbPath: 'memory.db', historyStore: {
provider: 'supabase',
config: {
supabaseUrl: process.env.SUPABASE_URL || '',
supabaseKey: process.env.SUPABASE_KEY || '',
tableName: 'memories',
},
},
disableHistory: false, // This is false by default
customPrompt: "I'm a virtual assistant. I'm here to help you with your queries.", customPrompt: "I'm a virtual assistant. I'm here to help you with your queries.",
} }
``` ```
+168 -40
View File
@@ -509,33 +509,33 @@
] ]
}, },
"post": { "post": {
"tags": [ "tags": [
"exports" "exports"
], ],
"summary": "Create an export job with schema", "summary": "Create an export job with schema",
"description": "Create a structured export of memories based on a provided schema.", "description": "Create a structured export of memories based on a provided schema.",
"operationId": "exports_create", "operationId": "exports_create",
"requestBody": { "requestBody": {
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"required": ["schema"], "required": ["schema"],
"properties": { "properties": {
"schema": { "schema": {
"type": "object", "type": "object",
"description": "Schema definition for the export" "description": "Schema definition for the export"
}, },
"user_id": { "user_id": {
"type": "string", "type": "string",
"description": "Filter exports by user ID" "description": "Filter exports by user ID"
}, },
"run_id": { "run_id": {
"type": "string", "type": "string",
"description": "Filter exports by run ID" "description": "Filter exports by run ID"
}, },
"session_id": { "session_id": {
"type": "string", "type": "string",
"description": "Filter exports by session ID" "description": "Filter exports by session ID"
}, },
"app_id": { "app_id": {
@@ -549,28 +549,28 @@
"project_id": { "project_id": {
"type": "string", "type": "string",
"description": "Filter exports by project ID" "description": "Filter exports by project ID"
} }
} }
} }
} }
}, },
"required": true "required": true
}, },
"responses": { "responses": {
"201": { "201": {
"description": "Export created successfully", "description": "Export created successfully",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"example": "Memory export request received. The export will be ready in a few seconds." "example": "Memory export request received. The export will be ready in a few seconds."
}, },
"id": { "id": {
"type": "string", "type": "string",
"format": "uuid", "format": "uuid",
"example": "550e8400-e29b-41d4-a716-446655440000" "example": "550e8400-e29b-41d4-a716-446655440000"
} }
}, },
@@ -587,15 +587,15 @@
"type": "object", "type": "object",
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"example": "Schema is required and must be a valid object" "example": "Schema is required and must be a valid object"
} }
} }
} }
} }
} }
} }
}, },
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "lang": "Python",
@@ -622,7 +622,7 @@
"source": "import com.mashape.unirest.http.HttpResponse;\nimport com.mashape.unirest.http.JsonNode;\nimport com.mashape.unirest.http.Unirest;\nimport org.json.JSONObject;\n\nJSONObject data = new JSONObject()\n .put(\"schema\", new JSONObject()) // Your schema here\n .put(\"user_id\", \"your_user_id\");\n\nHttpResponse<JsonNode> response = Unirest.post(\"https://api.mem0.ai/v1/exports/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(data.toString())\n .asJson();" "source": "import com.mashape.unirest.http.HttpResponse;\nimport com.mashape.unirest.http.JsonNode;\nimport com.mashape.unirest.http.Unirest;\nimport org.json.JSONObject;\n\nJSONObject data = new JSONObject()\n .put(\"schema\", new JSONObject()) // Your schema here\n .put(\"user_id\", \"your_user_id\");\n\nHttpResponse<JsonNode> response = Unirest.post(\"https://api.mem0.ai/v1/exports/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(data.toString())\n .asJson();"
} }
] ]
} }
}, },
"/v1/memories/": { "/v1/memories/": {
"get": { "get": {
@@ -783,6 +783,14 @@
"title": "Immutable", "title": "Immutable",
"default": false "default": false
}, },
"expiration_date": {
"type": "string",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD",
"title": "Expiration date",
"nullable": true,
"default": null
},
"organization": { "organization": {
"type": "string" "type": "string"
}, },
@@ -1183,6 +1191,14 @@
"title": "Immutable", "title": "Immutable",
"default": false "default": false
}, },
"expiration_date": {
"type": "string",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD",
"title": "Expiration date",
"nullable": true,
"default": null
},
"organization": { "organization": {
"type": "string" "type": "string"
}, },
@@ -1324,6 +1340,14 @@
"title": "Immutable", "title": "Immutable",
"default": false "default": false
}, },
"expiration_date": {
"type": "string",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD",
"title": "Expiration date",
"nullable": true,
"default": null
},
"created_at": { "created_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
@@ -1451,6 +1475,14 @@
"title": "Immutable", "title": "Immutable",
"default": false "default": false
}, },
"expiration_date": {
"type": "string",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD",
"title": "Expiration date",
"nullable": true,
"default": null
},
"created_at": { "created_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
@@ -1761,7 +1793,7 @@
}, },
{ {
"lang": "JavaScript", "lang": "JavaScript",
"source": "const options = {\n method: 'PUT',\n headers: {\n 'Authorization': 'Token <api-key>',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n text: 'Your updated memory text here'\n })\n};\n\nfetch('https://api.mem0.ai/v1/memories/{memory_id}/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));" "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));"
}, },
{ {
"lang": "cURL", "lang": "cURL",
@@ -2074,6 +2106,95 @@
"x-codegen-request-body-name": "data" "x-codegen-request-body-name": "data"
} }
}, },
"/v1/feedback/": {
"post": {
"tags": [
"feedback"
],
"description": "Submit feedback for a memory.",
"operationId": "submit_feedback",
"requestBody": {
"content": {
"application/json": {
"schema": {
"required": [
"memory_id"
],
"type": "object",
"properties": {
"memory_id": {
"type": "string",
"description": "ID of the memory to provide feedback for"
},
"feedback": {
"type": "string",
"enum": ["POSITIVE", "NEGATIVE", "VERY_NEGATIVE"],
"nullable": true,
"description": "Type of feedback"
},
"feedback_reason": {
"type": "string",
"nullable": true,
"description": "Reason for the feedback"
}
}
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Feedback ID"
},
"feedback": {
"type": "string",
"enum": ["POSITIVE", "NEGATIVE", "VERY_NEGATIVE"],
"nullable": true,
"description": "Type of feedback"
},
"feedback_reason": {
"type": "string",
"nullable": true,
"description": "Reason for the feedback"
}
}
}
}
}
},
"400": {
"description": "Invalid request"
},
"401": {
"description": "Unauthorized"
}
},
"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\")\n\n# Submit feedback for a memory\nfeedback = client.feedback(memory_id=\"memory_id\", feedback=\"POSITIVE\")\nprint(feedback)"
},
{
"lang": "JavaScript",
"source": "// To use the JavaScript SDK, install the package:\n// npm install mem0ai\n\nimport MemoryClient from 'mem0ai';\n\nconst client = new MemoryClient({ apiKey: 'your-api-key'});\n\nclient.feedback({\n memory_id: \"your-memory-id\", \n feedback: \"NEGATIVE\", \n feedback_reason: \"I don't like this memory because it is not relevant.\"\n})"
},
{
"lang": "cURL",
"source": "curl --request POST \\\n --url https://api.mem0.ai/v1/feedback/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\"memory_id\": \"memory_id\", \"feedback\": \"POSITIVE\"}'"
}
]
}
},
"/api/v1/orgs/organizations/": { "/api/v1/orgs/organizations/": {
"get": { "get": {
"tags": [ "tags": [
@@ -3112,7 +3233,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"example": "Unauthorized to create projects in this organization." "example": "Unauthorized to create projects in this organization."
} }
} }
@@ -3478,7 +3599,7 @@
"description": "Unauthorized to modify this project", "description": "Unauthorized to modify this project",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"message": { "message": {
@@ -4798,6 +4919,13 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"expiration_date": {
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD",
"title": "Expiration date",
"type": "string",
"nullable": true,
"default": null
},
"org_id": { "org_id": {
"description": "The unique identifier of the organization associated with this memory.", "description": "The unique identifier of the organization associated with this memory.",
"title": "Organization id", "title": "Organization id",
+122 -35
View File
@@ -416,6 +416,89 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
</CodeGroup> </CodeGroup>
#### Setting Memory Expiration Date
You can set an expiration date for memories, after which they will no longer be retrieved in searches. This is useful for creating temporary memories or memories that are only relevant for a specific time period.
<CodeGroup>
```python Python
import datetime
messages = [
{
"role": "user",
"content": "I'll be in San Francisco until August 31st."
}
]
# Set an expiration date for this memory
client.add(messages=messages, user_id="alex", expiration_date=str(datetime.datetime.now().date() + datetime.timedelta(days=30)))
# You can also use an explicit date string
client.add(messages=messages, user_id="alex", expiration_date="2023-08-31")
```
```javascript JavaScript
const messages = [
{
"role": "user",
"content": "I'll be in San Francisco until August 31st."
}
];
// Set an expiration date 30 days from now
const expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + 30);
client.add(messages, {
user_id: "alex",
expiration_date: expirationDate.toISOString().split('T')[0]
})
.then(response => console.log(response))
.catch(error => console.error(error));
// You can also use an explicit date string
client.add(messages, {
user_id: "alex",
expiration_date: "2023-08-31"
})
.then(response => console.log(response))
.catch(error => console.error(error));
```
```bash cURL
curl -X POST "https://api.mem0.ai/v1/memories/" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "I'll be in San Francisco until August 31st."
}
],
"user_id": "alex",
"expiration_date": "2023-08-31"
}'
```
```json Output
[
{
"id": "a1b2c3d4-e5f6-4g7h-8i9j-k0l1m2n3o4p5",
"data": {
"memory": "In San Francisco until August 31st"
},
"event": "ADD"
}
]
```
</CodeGroup>
<Note>
Once a memory reaches its expiration date, it won't be included in search or get results.
</Note>
#### Monitor Memories #### Monitor Memories
@@ -470,6 +553,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/" \
"metadata": {"food": "vegan"}, "metadata": {"food": "vegan"},
"categories": ["food_preferences"], "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-20T01:30:36.275141-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"updated_at": "2024-07-20T01:30:36.275172-07:00" "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
@@ -486,6 +570,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/" \
"metadata": {"food": "vegan"}, "metadata": {"food": "vegan"},
"categories": ["food_preferences"], "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-20T01:30:36.275141-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"updated_at": "2024-07-20T01:30:36.275172-07:00" "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
@@ -531,6 +616,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
"metadata": {"food": "vegan"}, "metadata": {"food": "vegan"},
"categories": ["food_preferences"], "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-20T01:30:36.275141-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"updated_at": "2024-07-20T01:30:36.275172-07:00" "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
@@ -621,6 +707,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
"metadata": null, "metadata": null,
"categories": ["food_preferences"], "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-20T01:30:36.275141-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"updated_at": "2024-07-20T01:30:36.275172-07:00" "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
@@ -686,6 +773,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
"metadata": null, "metadata": null,
"categories": ["food_preferences"], "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-20T01:30:36.275141-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"updated_at": "2024-07-20T01:30:36.275172-07:00" "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
@@ -760,6 +848,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
"metadata": {"food": "vegan"}, "metadata": {"food": "vegan"},
"categories": ["food_preferences"], "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-20T01:30:36.275141-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"updated_at": "2024-07-20T01:30:36.275172-07:00" "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
@@ -860,6 +949,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
"hash":"62bc074f56d1f909f1b4c2b639f56f6a", "hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-25T23:57:00.108347-07:00", "created_at":"2024-07-25T23:57:00.108347-07:00",
"updated_at":"2024-07-25T23:57:00.108367-07:00", "updated_at":"2024-07-25T23:57:00.108367-07:00",
"categories":None "categories":None
@@ -871,6 +961,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
"hash":"35a305373d639b0bffc6c2a3e2eb4244", "hash":"35a305373d639b0bffc6c2a3e2eb4244",
"metadata":"None", "metadata":"None",
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:31:03.543759-07:00", "created_at":"2024-07-26T00:31:03.543759-07:00",
"updated_at":"2024-07-26T00:31:03.543778-07:00", "updated_at":"2024-07-26T00:31:03.543778-07:00",
"categories":None "categories":None
@@ -894,6 +985,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
"hash":"62bc074f56d1f909f1b4c2b639f56f6a", "hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-25T23:57:00.108347-07:00", "created_at":"2024-07-25T23:57:00.108347-07:00",
"updated_at":"2024-07-25T23:57:00.108367-07:00", "updated_at":"2024-07-25T23:57:00.108367-07:00",
"categories":None "categories":None
@@ -905,6 +997,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
"hash":"35a305373d639b0bffc6c2a3e2eb4244", "hash":"35a305373d639b0bffc6c2a3e2eb4244",
"metadata":"None", "metadata":"None",
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:31:03.543759-07:00", "created_at":"2024-07-26T00:31:03.543759-07:00",
"updated_at":"2024-07-26T00:31:03.543778-07:00", "updated_at":"2024-07-26T00:31:03.543778-07:00",
"categories":None "categories":None
@@ -949,6 +1042,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
"hash":"62bc074f56d1f909f1b4c2b639f56f6a", "hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-25T23:57:00.108347-07:00", "created_at":"2024-07-25T23:57:00.108347-07:00",
"updated_at":"2024-07-25T23:57:00.108367-07:00", "updated_at":"2024-07-25T23:57:00.108367-07:00",
"categories":None "categories":None
@@ -960,6 +1054,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
"hash":"35a305373d639b0bffc6c2a3e2eb4244", "hash":"35a305373d639b0bffc6c2a3e2eb4244",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:31:03.543759-07:00", "created_at":"2024-07-26T00:31:03.543759-07:00",
"updated_at":"2024-07-26T00:31:03.543778-07:00", "updated_at":"2024-07-26T00:31:03.543778-07:00",
"categories":None "categories":None
@@ -983,6 +1078,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
"hash": "62bc074f56d1f909f1b4c2b639f56f6a", "hash": "62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-25T23:57:00.108347-07:00", "created_at": "2024-07-25T23:57:00.108347-07:00",
"updated_at": "2024-07-25T23:57:00.108367-07:00" "updated_at": "2024-07-25T23:57:00.108367-07:00"
}, },
@@ -993,6 +1089,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
"hash": "35a305373d639b0bffc6c2a3e2eb4244", "hash": "35a305373d639b0bffc6c2a3e2eb4244",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-26T00:31:03.543759-07:00", "created_at": "2024-07-26T00:31:03.543759-07:00",
"updated_at": "2024-07-26T00:31:03.543778-07:00" "updated_at": "2024-07-26T00:31:03.543778-07:00"
} }
@@ -1035,6 +1132,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-
"hash":"d2088c936e259f2f5d2d75543d31401c", "hash":"d2088c936e259f2f5d2d75543d31401c",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:25:16.566471-07:00", "created_at":"2024-07-26T00:25:16.566471-07:00",
"updated_at":"2024-07-26T00:25:16.566492-07:00", "updated_at":"2024-07-26T00:25:16.566492-07:00",
"categories":None "categories":None
@@ -1046,6 +1144,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-
"hash":"d2088c936e259f2f5d2d75543d31401c", "hash":"d2088c936e259f2f5d2d75543d31401c",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:33:20.350542-07:00", "created_at":"2024-07-26T00:33:20.350542-07:00",
"updated_at":"2024-07-26T00:33:20.350560-07:00", "updated_at":"2024-07-26T00:33:20.350560-07:00",
"categories":None "categories":None
@@ -1057,6 +1156,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-
"hash":"d2088c936e259f2f5d2d75543d31401c", "hash":"d2088c936e259f2f5d2d75543d31401c",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:51:09.642275-07:00", "created_at":"2024-07-26T00:51:09.642275-07:00",
"updated_at":"2024-07-26T00:51:09.642295-07:00", "updated_at":"2024-07-26T00:51:09.642295-07:00",
"categories":None "categories":None
@@ -1080,6 +1180,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-
"hash": "d2088c936e259f2f5d2d75543d31401c", "hash": "d2088c936e259f2f5d2d75543d31401c",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-26T00:25:16.566471-07:00", "created_at": "2024-07-26T00:25:16.566471-07:00",
"updated_at": "2024-07-26T00:25:16.566492-07:00", "updated_at": "2024-07-26T00:25:16.566492-07:00",
"categories": ["food_preferences"] "categories": ["food_preferences"]
@@ -1091,6 +1192,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-
"hash": "d2088c936e259f2f5d2d75543d31401c", "hash": "d2088c936e259f2f5d2d75543d31401c",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-26T00:33:20.350542-07:00", "created_at": "2024-07-26T00:33:20.350542-07:00",
"updated_at": "2024-07-26T00:33:20.350560-07:00", "updated_at": "2024-07-26T00:33:20.350560-07:00",
"categories": ["food_preferences"] "categories": ["food_preferences"]
@@ -1102,6 +1204,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-
"hash": "d2088c936e259f2f5d2d75543d31401c", "hash": "d2088c936e259f2f5d2d75543d31401c",
"metadata":None, "metadata":None,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-26T00:51:09.642275-07:00", "created_at": "2024-07-26T00:51:09.642275-07:00",
"updated_at": "2024-07-26T00:51:09.642295-07:00", "updated_at": "2024-07-26T00:51:09.642295-07:00",
"categories": None "categories": None
@@ -1141,6 +1244,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/582bbe6d-506b-48c6-a4c6-5df3b1e6342
"hash":"d2088c936e259f2f5d2d75543d31401c", "hash":"d2088c936e259f2f5d2d75543d31401c",
"metadata":"None", "metadata":"None",
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-26T00:25:16.566471-07:00", "created_at":"2024-07-26T00:25:16.566471-07:00",
"updated_at":"2024-07-26T00:25:16.566492-07:00", "updated_at":"2024-07-26T00:25:16.566492-07:00",
"categories": ["travel"] "categories": ["travel"]
@@ -1221,6 +1325,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&keywords=to play&page
"hash": "d2088c936e259f2f5d2d75543d31401c", "hash": "d2088c936e259f2f5d2d75543d31401c",
"metadata": null, "metadata": null,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-26T00:25:16.566471-07:00", "created_at": "2024-07-26T00:25:16.566471-07:00",
"updated_at": "2024-07-26T00:25:16.566492-07:00", "updated_at": "2024-07-26T00:25:16.566492-07:00",
"categories": ["likes", "food_preferences"] "categories": ["likes", "food_preferences"]
@@ -1232,6 +1337,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&keywords=to play&page
"hash": "d2088c936e259f2f5d2d75543d31401c", "hash": "d2088c936e259f2f5d2d75543d31401c",
"metadata": null, "metadata": null,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at": "2024-07-26T00:33:20.350542-07:00", "created_at": "2024-07-26T00:33:20.350542-07:00",
"updated_at": "2024-07-26T00:33:20.350560-07:00", "updated_at": "2024-07-26T00:33:20.350560-07:00",
"categories": ["likes"] "categories": ["likes"]
@@ -1359,6 +1465,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
"hash":"62bc074f56d1f909f1b4c2b639f56f6a", "hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":null, "metadata":null,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-25T23:57:00.108347-07:00", "created_at":"2024-07-25T23:57:00.108347-07:00",
"updated_at":"2024-07-25T23:57:00.108367-07:00", "updated_at":"2024-07-25T23:57:00.108367-07:00",
"categories": ["food_preferences"] "categories": ["food_preferences"]
@@ -1379,6 +1486,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
"hash":"62bc074f56d1f909f1b4c2b639f56f6a", "hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":null, "metadata":null,
"immutable": false, "immutable": false,
"expiration_date": null,
"created_at":"2024-07-25T23:57:00.108347-07:00", "created_at":"2024-07-25T23:57:00.108347-07:00",
"updated_at":"2024-07-25T23:57:00.108367-07:00", "updated_at":"2024-07-25T23:57:00.108367-07:00",
"categories": ["food_preferences"] "categories": ["food_preferences"]
@@ -1468,45 +1576,25 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
}' }'
``` ```
```json Output (Default) ```json Output
[ [
{ {
"id":"f38b689d-6b24-45b7-bced-17fbb4d8bac7", "id": "654fee-b411-4afe-b7e5-35789b72c4a5",
"memory":"Name: Alex. Vegetarian. Allergic to nuts.", "memory": "Name: Alex. Vegetarian. Allergic to nuts.",
"user_id":"alex", "user_id": "alex",
"hash":"62bc074f56d1f909f1b4c2b639f56f6a", "metadata": {"food": "vegan"},
"metadata":{"food":"vegan"}, "categories": ["food_preferences"],
"immutable": false, "immutable": false,
"created_at":"2024-07-25T23:57:00.108347-07:00", "expiration_date": null,
"updated_at":"2024-07-25T23:57:00.108367-07:00", "created_at": "2024-07-20T01:30:36.275141-07:00",
"categories": ["food_preferences"] "updated_at": "2024-07-20T01:30:36.275172-07:00"
} }
] ]
``` ```
```json Output (Paginated)
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id":"f38b689d-6b24-45b7-bced-17fbb4d8bac7",
"memory":"Name: Alex. Vegetarian. Allergic to nuts.",
"user_id":"alex",
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
"metadata":{"food":"vegan"},
"immutable": false,
"created_at":"2024-07-25T23:57:00.108347-07:00",
"updated_at":"2024-07-25T23:57:00.108367-07:00",
"categories": ["food_preferences"]
}
]
}
```
</CodeGroup> </CodeGroup>
### 4.5 Memory History ### 4.5 Memory History
Get history of how a memory has changed over time. Get history of how a memory has changed over time.
@@ -1819,8 +1907,7 @@ response = client.batch_update(update_memories)
print(response) print(response)
``` ```
```javascript JavaScript ```javascript JavaScript
const updateMemories = [ const updateMemories = [{"memory_id": "285ed74b-6e05-4043-b16b-3abd5b533496",
{"memory_id": "285ed74b-6e05-4043-b16b-3abd5b533496",
text: "Watches football" text: "Watches football"
}, },
{"memory_id": "2c9bd859-d1b7-4d33-a6b8-94e0147c4f07", {"memory_id": "2c9bd859-d1b7-4d33-a6b8-94e0147c4f07",
+13 -1
View File
@@ -1,4 +1,5 @@
import os import os
import warnings
from typing import Optional from typing import Optional
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
@@ -16,7 +17,18 @@ class OpenAIEmbedder(BaseEmbedder):
self.config.model = "text-embedding-ada-002" self.config.model = "text-embedding-ada-002"
api_key = self.config.api_key or os.environ["OPENAI_API_KEY"] api_key = self.config.api_key or os.environ["OPENAI_API_KEY"]
api_base = self.config.api_base or os.environ.get("OPENAI_API_BASE") api_base = (
self.config.api_base
or os.environ.get("OPENAI_API_BASE")
or os.getenv("OPENAI_BASE_URL")
or "https://api.openai.com/v1"
)
if os.environ.get("OPENAI_API_BASE"):
warnings.warn(
"The environment variable 'OPENAI_API_BASE' is deprecated and will be removed in the 0.1.140. "
"Please use 'OPENAI_BASE_URL' instead.",
DeprecationWarning
)
if api_key is None and os.getenv("OPENAI_ORGANIZATION") is None: if api_key is None and os.getenv("OPENAI_ORGANIZATION") is None:
raise ValueError("OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided") # noqa:E501 raise ValueError("OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided") # noqa:E501
+14 -1
View File
@@ -1,5 +1,6 @@
import json import json
import os import os
import warnings
from typing import Any, Callable, Dict, Optional, Type, Union from typing import Any, Callable, Dict, Optional, Type, Union
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
@@ -58,7 +59,19 @@ class OpenAILlm(BaseLlm):
"model_kwargs": config.model_kwargs or {}, "model_kwargs": config.model_kwargs or {},
} }
api_key = config.api_key or os.environ["OPENAI_API_KEY"] api_key = config.api_key or os.environ["OPENAI_API_KEY"]
base_url = config.base_url or os.environ.get("OPENAI_API_BASE", None) base_url = (
config.base_url
or os.getenv("OPENAI_API_BASE")
or os.getenv("OPENAI_BASE_URL")
or "https://api.openai.com/v1"
)
if os.environ.get("OPENAI_API_BASE"):
warnings.warn(
"The environment variable 'OPENAI_API_BASE' is deprecated and will be removed in the 0.1.140. "
"Please use 'OPENAI_BASE_URL' instead.",
DeprecationWarning
)
if config.top_p: if config.top_p:
kwargs["top_p"] = config.top_p kwargs["top_p"] = config.top_p
if config.default_headers: if config.default_headers:
+564 -87
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "embedchain" name = "embedchain"
version = "0.1.127" version = "0.1.128"
description = "Simplest open source retrieval (RAG) framework" description = "Simplest open source retrieval (RAG) framework"
authors = [ authors = [
"Taranjeet Singh <taranjeet@embedchain.ai>", "Taranjeet Singh <taranjeet@embedchain.ai>",
@@ -91,7 +91,7 @@ exclude = '''
color = true color = true
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<=3.13" python = ">=3.9,<=3.13.2"
python-dotenv = "^1.0.0" python-dotenv = "^1.0.0"
langchain = "^0.3.1" langchain = "^0.3.1"
requests = "^2.31.0" requests = "^2.31.0"
@@ -139,7 +139,7 @@ alembic = "^1.13.1"
langchain-cohere = "^0.3.0" langchain-cohere = "^0.3.0"
langchain-community = "^0.3.1" langchain-community = "^0.3.1"
langchain-aws = {version = "^0.2.1", optional = true} langchain-aws = {version = "^0.2.1", optional = true}
langsmith = "^0.1.17" langsmith = "^0.3.18"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.3.0" black = "^23.3.0"
View File
@@ -0,0 +1,54 @@
{
"name": "mem0-sdk-chat-bot",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@mem0/vercel-ai-provider": "0.0.12",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"ai": "4.1.42",
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.11.11",
"lucide-react": "^0.454.0",
"openai": "^4.86.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"mem0ai": "2.1.2",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/node": "^22.8.6",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"typescript": "~5.6.2",
"typescript-eslint": "^8.11.0",
"vite": "^6.2.1"
},
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b"
}
+14 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "mem0ai", "name": "mem0ai",
"version": "2.1.8", "version": "2.1.12",
"description": "The Memory Layer For Your AI Apps", "description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.mjs", "module": "./dist/index.mjs",
@@ -34,7 +34,8 @@
"clean": "rimraf dist", "clean": "rimraf dist",
"build": "npm run clean && npx prettier --check . && npx tsup", "build": "npm run clean && npx prettier --check . && npx tsup",
"dev": "npx nodemon", "dev": "npx nodemon",
"start": "npx ts-node src/oss/examples/basic.ts", "start": "pnpm run example memory",
"example": "ts-node src/oss/examples/vector-stores/index.ts",
"test": "jest", "test": "jest",
"test:ts": "jest --config jest.config.js", "test:ts": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch", "test:watch": "jest --config jest.config.js --watch",
@@ -56,7 +57,13 @@
"sourcemap": true, "sourcemap": true,
"clean": true, "clean": true,
"treeshake": true, "treeshake": true,
"minify": false "minify": false,
"external": [
"@mem0/community"
],
"noExternal": [
"!src/community/**"
]
}, },
"keywords": [ "keywords": [
"mem0", "mem0",
@@ -85,7 +92,6 @@
}, },
"dependencies": { "dependencies": {
"axios": "1.7.7", "axios": "1.7.7",
"neo4j-driver": "^5.28.1",
"openai": "4.28.0", "openai": "4.28.0",
"uuid": "9.0.1", "uuid": "9.0.1",
"zod": "3.22.4" "zod": "3.22.4"
@@ -93,25 +99,16 @@
"peerDependencies": { "peerDependencies": {
"@anthropic-ai/sdk": "0.18.0", "@anthropic-ai/sdk": "0.18.0",
"@qdrant/js-client-rest": "1.13.0", "@qdrant/js-client-rest": "1.13.0",
"@supabase/supabase-js": "^2.49.1",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/pg": "8.11.0", "@types/pg": "8.11.0",
"@types/sqlite3": "3.1.11", "@types/sqlite3": "3.1.11",
"groq-sdk": "0.3.0", "groq-sdk": "0.3.0",
"neo4j-driver": "^5.28.1",
"ollama": "^0.5.14",
"pg": "8.11.3", "pg": "8.11.3",
"redis": "4.7.0", "redis": "4.7.0",
"sqlite3": "5.1.7", "sqlite3": "5.1.7"
"ollama": "^0.5.14"
},
"peerDependenciesMeta": {
"posthog-node": {
"optional": true
},
"posthog-js": {
"optional": true
}
},
"optionalDependencies": {
"posthog-js": "^1.116.6"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
+7564
View File
File diff suppressed because it is too large Load Diff
-12
View File
@@ -1,10 +1,4 @@
import { MemoryClient } from "./mem0"; import { MemoryClient } from "./mem0";
import type { TelemetryClient, TelemetryInstance } from "./telemetry.types";
import {
telemetry,
captureClientEvent,
generateHash,
} from "./telemetry.browser";
import type * as MemoryTypes from "./mem0.types"; import type * as MemoryTypes from "./mem0.types";
// Re-export all types from mem0.types // Re-export all types from mem0.types
@@ -27,12 +21,6 @@ export type {
Feedback, Feedback,
} from "./mem0.types"; } from "./mem0.types";
// Export telemetry types
export type { TelemetryClient, TelemetryInstance };
// Export telemetry implementation
export { telemetry, captureClientEvent, generateHash };
// Export the main client // Export the main client
export { MemoryClient }; export { MemoryClient };
export default MemoryClient; export default MemoryClient;
+62 -49
View File
@@ -62,7 +62,7 @@ export default class MemoryClient {
(this.organizationName !== null && this.projectName === null) (this.organizationName !== null && this.projectName === null)
) { ) {
console.warn( console.warn(
"Warning: Both organizationName and projectName must be provided together when using either. This will be removedfrom the version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.", "Warning: Both organizationName and projectName must be provided together when using either. This will be removed from version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.",
); );
} }
@@ -72,7 +72,7 @@ export default class MemoryClient {
(this.organizationId !== null && this.projectId === null) (this.organizationId !== null && this.projectId === null)
) { ) {
console.warn( console.warn(
"Warning: Both organizationId and projectId must be provided together when using either. This will be removedfrom the version 1.0.40.", "Warning: Both organizationId and projectId must be provided together when using either. This will be removed from version 1.0.40.",
); );
} }
} }
@@ -108,59 +108,42 @@ export default class MemoryClient {
private async _initializeClient() { private async _initializeClient() {
try { try {
// do this only in browser // Generate telemetry ID
if (typeof window !== "undefined") { this.telemetryId = generateHash(this.apiKey);
this.telemetryId = await generateHash(this.apiKey);
await captureClientEvent("init", this);
}
// Wrap methods after initialization // Capture initialization event
this.add = this.wrapMethod("add", this.add); await captureClientEvent("init", this, {
this.get = this.wrapMethod("get", this.get); api_version: "v1",
this.getAll = this.wrapMethod("get_all", this.getAll); client_type: "MemoryClient",
this.search = this.wrapMethod("search", this.search); });
this.delete = this.wrapMethod("delete", this.delete); } catch (error: any) {
this.deleteAll = this.wrapMethod("delete_all", this.deleteAll);
this.history = this.wrapMethod("history", this.history);
this.users = this.wrapMethod("users", this.users);
this.deleteUser = this.wrapMethod("delete_user", this.deleteUser);
this.deleteUsers = this.wrapMethod("delete_users", this.deleteUsers);
this.batchUpdate = this.wrapMethod("batch_update", this.batchUpdate);
this.batchDelete = this.wrapMethod("batch_delete", this.batchDelete);
this.getProject = this.wrapMethod("get_project", this.getProject);
this.updateProject = this.wrapMethod(
"update_project",
this.updateProject,
);
this.getWebhooks = this.wrapMethod("get_webhook", this.getWebhooks);
this.createWebhook = this.wrapMethod(
"create_webhook",
this.createWebhook,
);
this.updateWebhook = this.wrapMethod(
"update_webhook",
this.updateWebhook,
);
this.deleteWebhook = this.wrapMethod(
"delete_webhook",
this.deleteWebhook,
);
} catch (error) {
console.error("Failed to initialize client:", error); console.error("Failed to initialize client:", error);
await captureClientEvent("init_error", this, {
error: error?.message || "Unknown error",
stack: error?.stack || "No stack trace",
});
} }
} }
wrapMethod(methodName: any, method: any) { private _captureEvent(methodName: string, args: any[]) {
return async function (...args: any) { captureClientEvent(methodName, this, {
// @ts-ignore success: true,
await captureClientEvent(methodName, this); args_count: args.length,
// @ts-ignore keys: args.length > 0 ? args[0] : [],
return method.apply(this, args); }).catch((error: any) => {
}.bind(this); console.error("Failed to capture event:", error);
});
} }
async _fetchWithErrorHandling(url: string, options: any): Promise<any> { async _fetchWithErrorHandling(url: string, options: any): Promise<any> {
const response = await fetch(url, options); const response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Token ${this.apiKey}`,
"Mem0-User-ID": this.telemetryId,
},
});
if (!response.ok) { if (!response.ok) {
const errorData = await response.text(); const errorData = await response.text();
throw new APIError(`API request failed: ${errorData}`); throw new APIError(`API request failed: ${errorData}`);
@@ -211,6 +194,11 @@ export default class MemoryClient {
} }
const payload = this._preparePayload(messages, options); const payload = this._preparePayload(messages, options);
// get payload keys whose value is not null or undefined
const payloadKeys = Object.keys(payload);
this._captureEvent("add", [payloadKeys]);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/`, `${this.host}/v1/memories/`,
{ {
@@ -227,6 +215,10 @@ export default class MemoryClient {
const payload = { const payload = {
text: message, text: message,
}; };
const payloadKeys = Object.keys(payload);
this._captureEvent("update", [payloadKeys]);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/${memoryId}/`, `${this.host}/v1/memories/${memoryId}/`,
{ {
@@ -239,6 +231,7 @@ export default class MemoryClient {
} }
async get(memoryId: string): Promise<Memory> { async get(memoryId: string): Promise<Memory> {
this._captureEvent("get", []);
return this._fetchWithErrorHandling( return this._fetchWithErrorHandling(
`${this.host}/v1/memories/${memoryId}/`, `${this.host}/v1/memories/${memoryId}/`,
{ {
@@ -249,6 +242,8 @@ export default class MemoryClient {
async getAll(options?: SearchOptions): Promise<Array<Memory>> { async getAll(options?: SearchOptions): Promise<Array<Memory>> {
this._validateOrgProject(); this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]);
const { api_version, page, page_size, ...otherOptions } = options!; const { api_version, page, page_size, ...otherOptions } = options!;
if (this.organizationName != null && this.projectName != null) { if (this.organizationName != null && this.projectName != null) {
otherOptions.org_name = this.organizationName; otherOptions.org_name = this.organizationName;
@@ -294,6 +289,8 @@ export default class MemoryClient {
async search(query: string, options?: SearchOptions): Promise<Array<Memory>> { async search(query: string, options?: SearchOptions): Promise<Array<Memory>> {
this._validateOrgProject(); this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("search", [payloadKeys]);
const { api_version, ...otherOptions } = options!; const { api_version, ...otherOptions } = options!;
const payload = { query, ...otherOptions }; const payload = { query, ...otherOptions };
if (this.organizationName != null && this.projectName != null) { if (this.organizationName != null && this.projectName != null) {
@@ -322,6 +319,7 @@ export default class MemoryClient {
} }
async delete(memoryId: string): Promise<{ message: string }> { async delete(memoryId: string): Promise<{ message: string }> {
this._captureEvent("delete", []);
return this._fetchWithErrorHandling( return this._fetchWithErrorHandling(
`${this.host}/v1/memories/${memoryId}/`, `${this.host}/v1/memories/${memoryId}/`,
{ {
@@ -333,6 +331,8 @@ export default class MemoryClient {
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> { async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
this._validateOrgProject(); this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]);
if (this.organizationName != null && this.projectName != null) { if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName; options.org_name = this.organizationName;
options.project_name = this.projectName; options.project_name = this.projectName;
@@ -358,6 +358,7 @@ export default class MemoryClient {
} }
async history(memoryId: string): Promise<Array<MemoryHistory>> { async history(memoryId: string): Promise<Array<MemoryHistory>> {
this._captureEvent("history", []);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/${memoryId}/history/`, `${this.host}/v1/memories/${memoryId}/history/`,
{ {
@@ -369,6 +370,7 @@ export default class MemoryClient {
async users(): Promise<AllUsers> { async users(): Promise<AllUsers> {
this._validateOrgProject(); this._validateOrgProject();
this._captureEvent("users", []);
const options: MemoryOptions = {}; const options: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) { if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName; options.org_name = this.organizationName;
@@ -397,6 +399,7 @@ export default class MemoryClient {
entityId: string, entityId: string,
entity: { type: string } = { type: "user" }, entity: { type: string } = { type: "user" },
): Promise<{ message: string }> { ): Promise<{ message: string }> {
this._captureEvent("delete_user", []);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/entities/${entity.type}/${entityId}/`, `${this.host}/v1/entities/${entity.type}/${entityId}/`,
{ {
@@ -409,6 +412,7 @@ export default class MemoryClient {
async deleteUsers(): Promise<{ message: string }> { async deleteUsers(): Promise<{ message: string }> {
this._validateOrgProject(); this._validateOrgProject();
this._captureEvent("delete_users", []);
const entities = await this.users(); const entities = await this.users();
for (const entity of entities.results) { for (const entity of entities.results) {
@@ -433,6 +437,7 @@ export default class MemoryClient {
} }
async batchUpdate(memories: Array<MemoryUpdateBody>): Promise<string> { async batchUpdate(memories: Array<MemoryUpdateBody>): Promise<string> {
this._captureEvent("batch_update", []);
const memoriesBody = memories.map((memory) => ({ const memoriesBody = memories.map((memory) => ({
memory_id: memory.memoryId, memory_id: memory.memoryId,
text: memory.text, text: memory.text,
@@ -449,6 +454,7 @@ export default class MemoryClient {
} }
async batchDelete(memories: Array<string>): Promise<string> { async batchDelete(memories: Array<string>): Promise<string> {
this._captureEvent("batch_delete", []);
const memoriesBody = memories.map((memory) => ({ const memoriesBody = memories.map((memory) => ({
memory_id: memory, memory_id: memory,
})); }));
@@ -465,7 +471,8 @@ export default class MemoryClient {
async getProject(options: ProjectOptions): Promise<ProjectResponse> { async getProject(options: ProjectOptions): Promise<ProjectResponse> {
this._validateOrgProject(); this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_project", [payloadKeys]);
const { fields } = options; const { fields } = options;
if (!(this.organizationId && this.projectId)) { if (!(this.organizationId && this.projectId)) {
@@ -490,7 +497,7 @@ export default class MemoryClient {
prompts: PromptUpdatePayload, prompts: PromptUpdatePayload,
): Promise<Record<string, any>> { ): Promise<Record<string, any>> {
this._validateOrgProject(); this._validateOrgProject();
this._captureEvent("update_project", []);
if (!(this.organizationId && this.projectId)) { if (!(this.organizationId && this.projectId)) {
throw new Error( throw new Error(
"organizationId and projectId must be set to update instructions or categories", "organizationId and projectId must be set to update instructions or categories",
@@ -510,6 +517,7 @@ export default class MemoryClient {
// WebHooks // WebHooks
async getWebhooks(data?: { projectId?: string }): Promise<Array<Webhook>> { async getWebhooks(data?: { projectId?: string }): Promise<Array<Webhook>> {
this._captureEvent("get_webhooks", []);
const project_id = data?.projectId || this.projectId; const project_id = data?.projectId || this.projectId;
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/api/v1/webhooks/projects/${project_id}/`, `${this.host}/api/v1/webhooks/projects/${project_id}/`,
@@ -521,6 +529,7 @@ export default class MemoryClient {
} }
async createWebhook(webhook: WebhookPayload): Promise<Webhook> { async createWebhook(webhook: WebhookPayload): Promise<Webhook> {
this._captureEvent("create_webhook", []);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/api/v1/webhooks/projects/${this.projectId}/`, `${this.host}/api/v1/webhooks/projects/${this.projectId}/`,
{ {
@@ -533,6 +542,7 @@ export default class MemoryClient {
} }
async updateWebhook(webhook: WebhookPayload): Promise<{ message: string }> { async updateWebhook(webhook: WebhookPayload): Promise<{ message: string }> {
this._captureEvent("update_webhook", []);
const project_id = webhook.projectId || this.projectId; const project_id = webhook.projectId || this.projectId;
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/api/v1/webhooks/${webhook.webhookId}/`, `${this.host}/api/v1/webhooks/${webhook.webhookId}/`,
@@ -551,6 +561,7 @@ export default class MemoryClient {
async deleteWebhook(data: { async deleteWebhook(data: {
webhookId: string; webhookId: string;
}): Promise<{ message: string }> { }): Promise<{ message: string }> {
this._captureEvent("delete_webhook", []);
const webhook_id = data.webhookId || data; const webhook_id = data.webhookId || data;
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/api/v1/webhooks/${webhook_id}/`, `${this.host}/api/v1/webhooks/${webhook_id}/`,
@@ -563,6 +574,8 @@ export default class MemoryClient {
} }
async feedback(data: FeedbackPayload): Promise<{ message: string }> { async feedback(data: FeedbackPayload): Promise<{ message: string }> {
const payloadKeys = Object.keys(data || {});
this._captureEvent("feedback", [payloadKeys]);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/feedback/`, `${this.host}/v1/feedback/`,
{ {
-85
View File
@@ -1,85 +0,0 @@
// @ts-nocheck
import type { PostHog } from "posthog-js";
import type { TelemetryClient } from "./telemetry.types";
let version = "1.0.20";
const MEM0_TELEMETRY = "false";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com";
// Browser-specific hash function using Web Crypto API
async function generateHash(input: string): Promise<string> {
const msgBuffer = new TextEncoder().encode(input);
const hashBuffer = await window.crypto.subtle.digest("SHA-256", msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}
class BrowserTelemetry implements TelemetryClient {
client: PostHog | null = null;
constructor(projectApiKey: string, host: string) {
if (MEM0_TELEMETRY) {
this.initializeClient(projectApiKey, host);
}
}
private async initializeClient(projectApiKey: string, host: string) {
try {
const posthog = await import("posthog-js").catch(() => null);
if (posthog) {
posthog.init(projectApiKey, { api_host: host });
this.client = posthog;
}
} catch (error) {
// Silently fail if posthog-js is not available
this.client = null;
}
}
async captureEvent(distinctId: string, eventName: string, properties = {}) {
if (!this.client || !MEM0_TELEMETRY) return;
const eventProperties = {
client_source: "browser",
client_version: getVersion(),
browser: window.navigator.userAgent,
...properties,
};
try {
this.client.capture(eventName, eventProperties);
} catch (error) {
// Silently fail if telemetry fails
}
}
async shutdown() {
// No shutdown needed for browser client
}
}
function getVersion() {
return version;
}
const telemetry = new BrowserTelemetry(POSTHOG_API_KEY, POSTHOG_HOST);
async function captureClientEvent(
eventName: string,
instance: any,
additionalData = {},
) {
const eventData = {
function: `${instance.constructor.name}`,
...additionalData,
};
await telemetry.captureEvent(
instance.telemetryId,
`client.${eventName}`,
eventData,
);
}
export { telemetry, captureClientEvent, generateHash };
-107
View File
@@ -1,107 +0,0 @@
// @ts-nocheck
import type { TelemetryClient } from "./telemetry.types";
let version = "1.0.20";
const MEM0_TELEMETRY = process.env.MEM0_TELEMETRY !== "false";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com";
// Node-specific hash function using crypto module
function generateHash(input: string): string {
const crypto = require("crypto");
return crypto.createHash("sha256").update(input).digest("hex");
}
class NodeTelemetry implements TelemetryClient {
client: any = null;
constructor(projectApiKey: string, host: string) {
if (MEM0_TELEMETRY) {
this.initializeClient(projectApiKey, host);
}
}
private async initializeClient(projectApiKey: string, host: string) {
try {
const { PostHog } = await import("posthog-node").catch(() => ({
PostHog: null,
}));
if (PostHog) {
this.client = new PostHog(projectApiKey, { host, flushAt: 1 });
}
} catch (error) {
// Silently fail if posthog-node is not available
this.client = null;
}
}
async captureEvent(distinctId: string, eventName: string, properties = {}) {
if (!this.client || !MEM0_TELEMETRY) return;
const eventProperties = {
client_source: "nodejs",
client_version: getVersion(),
...this.getEnvironmentInfo(),
...properties,
};
try {
this.client.capture({
distinctId,
event: eventName,
properties: eventProperties,
});
} catch (error) {
// Silently fail if telemetry fails
}
}
private getEnvironmentInfo() {
try {
const os = require("os");
return {
node_version: process.version,
os: process.platform,
os_version: os.release(),
os_arch: os.arch(),
};
} catch (error) {
return {};
}
}
async shutdown() {
if (this.client) {
try {
return this.client.shutdown();
} catch (error) {
// Silently fail shutdown
}
}
}
}
function getVersion() {
return version;
}
const telemetry = new NodeTelemetry(POSTHOG_API_KEY, POSTHOG_HOST);
async function captureClientEvent(
eventName: string,
instance: any,
additionalData = {},
) {
const eventData = {
function: `${instance.constructor.name}`,
...additionalData,
};
await telemetry.captureEvent(
instance.telemetryId,
`client.${eventName}`,
eventData,
);
}
export { telemetry, captureClientEvent, generateHash };
+96 -2
View File
@@ -1,3 +1,97 @@
// @ts-nocheck // @ts-nocheck
// Re-export browser telemetry by default import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
export * from "./telemetry.browser";
let version = "2.1.12";
// Safely check for process.env in different environments
const MEM0_TELEMETRY = process?.env?.MEM0_TELEMETRY === "false" ? false : true;
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
// Simple hash function using random strings
function generateHash(input: string): string {
const randomStr =
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
return randomStr;
}
class UnifiedTelemetry implements TelemetryClient {
private apiKey: string;
private host: string;
constructor(projectApiKey: string, host: string) {
this.apiKey = projectApiKey;
this.host = host;
}
async captureEvent(distinctId: string, eventName: string, properties = {}) {
if (!MEM0_TELEMETRY) return;
const eventProperties = {
client_version: version,
timestamp: new Date().toISOString(),
...properties,
$process_person_profile: false,
$lib: "posthog-node",
};
const payload = {
api_key: this.apiKey,
distinct_id: distinctId,
event: eventName,
properties: eventProperties,
};
try {
const response = await fetch(this.host, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
console.error("Telemetry event capture failed:", await response.text());
}
} catch (error) {
console.error("Telemetry event capture failed:", error);
}
}
async shutdown() {
// No shutdown needed for direct API calls
}
}
const telemetry = new UnifiedTelemetry(POSTHOG_API_KEY, POSTHOG_HOST);
async function captureClientEvent(
eventName: string,
instance: any,
additionalData = {},
) {
if (!instance.telemetryId) {
console.warn("No telemetry ID found for instance");
return;
}
const eventData = {
function: `${instance.constructor.name}`,
method: eventName,
api_host: instance.host,
timestamp: new Date().toISOString(),
client_version: version,
keys: additionalData?.keys || [],
...additionalData,
};
await telemetry.captureEvent(
instance.telemetryId,
`client.${eventName}`,
eventData,
);
}
export { telemetry, captureClientEvent, generateHash };
+19
View File
@@ -12,4 +12,23 @@ export interface TelemetryInstance {
constructor: { constructor: {
name: string; name: string;
}; };
host?: string;
apiKey?: string;
}
export interface TelemetryEventData {
function: string;
method: string;
api_host?: string;
timestamp?: string;
client_source: "browser" | "nodejs";
client_version: string;
[key: string]: any;
}
export interface TelemetryOptions {
enabled?: boolean;
apiKey?: string;
host?: string;
version?: string;
} }
+28
View File
@@ -0,0 +1,28 @@
# Dependencies
node_modules
.pnp
.pnp.js
# Build outputs
dist
build
# Lock files
package-lock.json
yarn.lock
pnpm-lock.yaml
# Coverage
coverage
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+91
View File
@@ -0,0 +1,91 @@
{
"name": "@mem0/community",
"version": "0.0.1",
"description": "Community features for Mem0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./langchain": {
"types": "./dist/integrations/langchain/index.d.ts",
"require": "./dist/integrations/langchain/index.js",
"import": "./dist/integrations/langchain/index.mjs"
}
},
"files": [
"dist"
],
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && npx prettier --check . && npx tsup",
"dev": "npx nodemon",
"test": "jest",
"test:ts": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"format": "npm run clean && prettier --write .",
"format:check": "npm run clean && prettier --check .",
"prepublishOnly": "npm run build"
},
"tsup": {
"entry": {
"index": "src/index.ts",
"integrations/langchain/index": "src/integrations/langchain/index.ts"
},
"format": [
"cjs",
"esm"
],
"dts": {
"resolve": true,
"compilerOptions": {
"rootDir": "src"
}
},
"splitting": false,
"sourcemap": true,
"clean": true,
"treeshake": true,
"minify": false,
"outDir": "dist",
"tsconfig": "./tsconfig.json"
},
"keywords": [
"mem0",
"community",
"ai",
"memory"
],
"author": "Deshraj Yadav",
"license": "Apache-2.0",
"devDependencies": {
"@types/node": "^22.7.6",
"@types/uuid": "^9.0.8",
"dotenv": "^16.4.5",
"jest": "^29.7.0",
"nodemon": "^3.0.1",
"prettier": "^3.5.2",
"rimraf": "^5.0.5",
"ts-jest": "^29.2.6",
"tsup": "^8.3.0",
"typescript": "5.5.4"
},
"dependencies": {
"@langchain/community": "^0.3.36",
"@langchain/core": "^0.3.42",
"axios": "1.7.7",
"mem0ai": "^2.1.8",
"uuid": "9.0.1",
"zod": "3.22.4"
},
"engines": {
"node": ">=18"
},
"publishConfig": {
"access": "public"
}
}
+1
View File
@@ -0,0 +1 @@
export * from "./integrations/langchain";
@@ -0,0 +1 @@
export * from "./mem0";
@@ -0,0 +1,314 @@
import { MemoryClient } from "mem0ai";
import type { Memory, MemoryOptions, SearchOptions } from "mem0ai";
import {
InputValues,
OutputValues,
MemoryVariables,
getInputValue,
getOutputValue,
} from "@langchain/core/memory";
import {
AIMessage,
BaseMessage,
ChatMessage,
getBufferString,
HumanMessage,
SystemMessage,
} from "@langchain/core/messages";
import {
BaseChatMemory,
BaseChatMemoryInput,
} from "@langchain/community/memory/chat_memory";
/**
* Extracts and formats memory content into a system prompt
* @param memory Array of Memory objects from mem0ai
* @returns Formatted system prompt string
*/
export const mem0MemoryContextToSystemPrompt = (memory: Memory[]): string => {
if (!memory || !Array.isArray(memory)) {
return "";
}
return memory
.filter((m) => m?.memory)
.map((m) => m.memory)
.join("\n");
};
/**
* Condenses memory content into a single HumanMessage with context
* @param memory Array of Memory objects from mem0ai
* @returns HumanMessage containing formatted memory context
*/
export const condenseMem0MemoryIntoHumanMessage = (
memory: Memory[],
): HumanMessage => {
const basePrompt =
"These are the memories I have stored. Give more weightage to the question by users and try to answer that first. You have to modify your answer based on the memories I have provided. If the memories are irrelevant you can ignore them. Also don't reply to this section of the prompt, or the memories, they are only for your reference. The MEMORIES of the USER are: \n\n";
const systemPrompt = mem0MemoryContextToSystemPrompt(memory);
return new HumanMessage(`${basePrompt}\n${systemPrompt}`);
};
/**
* Converts Mem0 memories to a list of BaseMessages
* @param memories Array of Memory objects from mem0ai
* @returns Array of BaseMessage objects
*/
export const mem0MemoryToMessages = (memories: Memory[]): BaseMessage[] => {
if (!memories || !Array.isArray(memories)) {
return [];
}
const messages: BaseMessage[] = [];
// Add memories as system message if present
const memoryContent = memories
.filter((m) => m?.memory)
.map((m) => m.memory)
.join("\n");
if (memoryContent) {
messages.push(new SystemMessage(memoryContent));
}
// Add conversation messages
memories.forEach((memory) => {
if (memory.messages) {
memory.messages.forEach((msg) => {
const content =
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content);
if (msg.role === "user") {
messages.push(new HumanMessage(content));
} else if (msg.role === "assistant") {
messages.push(new AIMessage(content));
} else if (content) {
messages.push(new ChatMessage(content, msg.role));
}
});
}
});
return messages;
};
/**
* Interface defining the structure of the input data for the Mem0Client
*/
export interface ClientOptions {
apiKey: string;
host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
}
/**
* Interface defining the structure of the input data for the Mem0Memory
* class. It includes properties like memoryKey, sessionId, and apiKey.
*/
export interface Mem0MemoryInput extends BaseChatMemoryInput {
sessionId: string;
apiKey: string;
humanPrefix?: string;
aiPrefix?: string;
memoryOptions?: MemoryOptions | SearchOptions;
mem0Options?: ClientOptions;
separateMessages?: boolean;
}
/**
* Class used to manage the memory of a chat session using the Mem0 service.
* It handles loading and saving chat history, and provides methods to format
* the memory content for use in chat models.
*
* @example
* ```typescript
* const memory = new Mem0Memory({
* sessionId: "user123" // or use user_id inside of memoryOptions (recommended),
* apiKey: "your-api-key",
* memoryOptions: {
* user_id: "user123",
* run_id: "run123"
* },
* });
*
* // Use with a chat model
* const model = new ChatOpenAI({
* modelName: "gpt-3.5-turbo",
* temperature: 0,
* });
*
* const chain = new ConversationChain({ llm: model, memory });
* ```
*/
export class Mem0Memory extends BaseChatMemory implements Mem0MemoryInput {
memoryKey = "history";
apiKey: string;
sessionId: string;
humanPrefix = "Human";
aiPrefix = "AI";
mem0Client: InstanceType<typeof MemoryClient>;
memoryOptions: MemoryOptions | SearchOptions;
mem0Options: ClientOptions;
// Whether to return separate messages for chat history with a SystemMessage containing (facts and summary) or return a single HumanMessage with the entire memory context.
// Defaults to false (return a single HumanMessage) in order to allow more flexibility with different models.
separateMessages?: boolean;
constructor(fields: Mem0MemoryInput) {
if (!fields.apiKey) {
throw new Error("apiKey is required for Mem0Memory");
}
if (!fields.sessionId) {
throw new Error("sessionId is required for Mem0Memory");
}
super({
returnMessages: fields?.returnMessages ?? false,
inputKey: fields?.inputKey,
outputKey: fields?.outputKey,
});
this.apiKey = fields.apiKey;
this.sessionId = fields.sessionId;
this.humanPrefix = fields.humanPrefix ?? this.humanPrefix;
this.aiPrefix = fields.aiPrefix ?? this.aiPrefix;
this.memoryOptions = fields.memoryOptions ?? {};
this.mem0Options = fields.mem0Options ?? {
apiKey: this.apiKey,
};
this.separateMessages = fields.separateMessages ?? false;
try {
this.mem0Client = new MemoryClient({
...this.mem0Options,
apiKey: this.apiKey,
});
} catch (error) {
console.error("Failed to initialize Mem0Client:", error);
throw new Error(
"Failed to initialize Mem0Client. Please check your configuration.",
);
}
}
get memoryKeys(): string[] {
return [this.memoryKey];
}
/**
* Retrieves memories from the Mem0 service and formats them for use
* @param values Input values containing optional search query
* @returns Promise resolving to formatted memory variables
*/
async loadMemoryVariables(values: InputValues): Promise<MemoryVariables> {
const searchType = values.input ? "search" : "get_all";
let memories: Memory[] = [];
try {
if (searchType === "get_all") {
memories = await this.mem0Client.getAll({
user_id: this.sessionId,
...this.memoryOptions,
});
} else {
memories = await this.mem0Client.search(values.input, {
user_id: this.sessionId,
...this.memoryOptions,
});
}
} catch (error) {
console.error("Error loading memories:", error);
return this.returnMessages
? { [this.memoryKey]: [] }
: { [this.memoryKey]: "" };
}
if (this.returnMessages) {
return {
[this.memoryKey]: this.separateMessages
? mem0MemoryToMessages(memories)
: [condenseMem0MemoryIntoHumanMessage(memories)],
};
}
return {
[this.memoryKey]: this.separateMessages
? getBufferString(
mem0MemoryToMessages(memories),
this.humanPrefix,
this.aiPrefix,
)
: (condenseMem0MemoryIntoHumanMessage(memories).content ?? ""),
};
}
/**
* Saves the current conversation context to the Mem0 service
* @param inputValues Input messages to be saved
* @param outputValues Output messages to be saved
* @returns Promise resolving when the context has been saved
*/
async saveContext(
inputValues: InputValues,
outputValues: OutputValues,
): Promise<void> {
const input = getInputValue(inputValues, this.inputKey);
const output = getOutputValue(outputValues, this.outputKey);
if (!input || !output) {
console.warn("Missing input or output values, skipping memory save");
return;
}
try {
const messages = [
{
role: "user",
content: `${input}`,
},
{
role: "assistant",
content: `${output}`,
},
];
await this.mem0Client.add(messages, {
user_id: this.sessionId,
...this.memoryOptions,
});
} catch (error) {
console.error("Error saving memory context:", error);
// Continue execution even if memory save fails
}
await super.saveContext(inputValues, outputValues);
}
/**
* Clears all memories for the current session
* @returns Promise resolving when memories have been cleared
*/
async clear(): Promise<void> {
try {
// Note: Implement clear functionality if Mem0Client provides it
// await this.mem0Client.clear(this.sessionId);
} catch (error) {
console.error("Error clearing memories:", error);
}
await super.clear();
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": ["node"],
"typeRoots": ["./node_modules/@types"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
@@ -0,0 +1,99 @@
import { Memory } from "../../src";
export async function runTests(memory: Memory) {
try {
// Reset all memories
console.log("\nResetting all memories...");
await memory.reset();
console.log("All memories reset");
// Add a single memory
console.log("\nAdding a single memory...");
const result1 = await memory.add(
"Hi, my name is John and I am a software engineer.",
{
userId: "john",
},
);
console.log("Added memory:", result1);
// Add multiple messages
console.log("\nAdding multiple messages...");
const result2 = await memory.add(
[
{ role: "user", content: "What is your favorite city?" },
{ role: "assistant", content: "I love Paris, it is my favorite city." },
],
{
userId: "john",
},
);
console.log("Added messages:", result2);
// Trying to update the memory
const result3 = await memory.add(
[
{ role: "user", content: "What is your favorite city?" },
{
role: "assistant",
content: "I love New York, it is my favorite city.",
},
],
{
userId: "john",
},
);
console.log("Updated messages:", result3);
// Get a single memory
console.log("\nGetting a single memory...");
if (result1.results && result1.results.length > 0) {
const singleMemory = await memory.get(result1.results[0].id);
console.log("Single memory:", singleMemory);
} else {
console.log("No memory was added in the first step");
}
// Updating this memory
const result4 = await memory.update(
result1.results[0].id,
"I love India, it is my favorite country.",
);
console.log("Updated memory:", result4);
// Get all memories
console.log("\nGetting all memories...");
const allMemories = await memory.getAll({
userId: "john",
});
console.log("All memories:", allMemories);
// Search for memories
console.log("\nSearching memories...");
const searchResult = await memory.search("What do you know about Paris?", {
userId: "john",
});
console.log("Search results:", searchResult);
// Get memory history
if (result1.results && result1.results.length > 0) {
console.log("\nGetting memory history...");
const history = await memory.history(result1.results[0].id);
console.log("Memory history:", history);
}
// Delete a memory
if (result1.results && result1.results.length > 0) {
console.log("\nDeleting a memory...");
await memory.delete(result1.results[0].id);
console.log("Memory deleted successfully");
}
// Reset all memories
console.log("\nResetting all memories...");
await memory.reset();
console.log("All memories reset");
} catch (error) {
console.error("Error:", error);
}
}
@@ -0,0 +1,53 @@
import dotenv from "dotenv";
import { demoMemoryStore } from "./memory";
import { demoSupabase } from "./supabase";
// import { demoQdrant } from "./qdrant";
// import { demoRedis } from "./redis";
// import { demoPGVector } from "./pgvector";
// Load environment variables
dotenv.config();
async function main() {
const args = process.argv.slice(2);
const selectedStore = args[0]?.toLowerCase();
const stores: Record<string, () => Promise<void>> = {
// memory: demoMemoryStore,
supabase: demoSupabase,
// Uncomment these as they are implemented
// qdrant: demoQdrant,
// redis: demoRedis,
// pgvector: demoPGVector,
};
if (selectedStore) {
const demo = stores[selectedStore];
if (demo) {
try {
await demo();
} catch (error) {
console.error(`\nError running ${selectedStore} demo:`, error);
if (selectedStore !== "memory") {
console.log("\nFalling back to memory store...");
await stores.memory();
}
}
} else {
console.log(`\nUnknown vector store: ${selectedStore}`);
console.log("Available stores:", Object.keys(stores).join(", "));
}
return;
}
// If no store specified, run all available demos
for (const [name, demo] of Object.entries(stores)) {
try {
await demo();
} catch (error) {
console.error(`\nError running ${name} demo:`, error);
}
}
}
main().catch(console.error);
@@ -0,0 +1,38 @@
import { Memory } from "../../src";
import { runTests } from "../utils/test-utils";
export async function demoMemoryStore() {
console.log("\n=== Testing In-Memory Vector Store ===\n");
const memory = new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "memory",
config: {
collectionName: "memories",
dimension: 1536,
},
},
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
},
},
historyDbPath: "memory.db",
});
await runTests(memory);
}
if (require.main === module) {
demoMemoryStore();
}
@@ -0,0 +1,49 @@
import { Memory } from "../../src";
import { runTests } from "../utils/test-utils";
export async function demoPGVector() {
console.log("\n=== Testing PGVector Store ===\n");
const memory = new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "pgvector",
config: {
collectionName: "memories",
dimension: 1536,
dbname: process.env.PGVECTOR_DB || "vectordb",
user: process.env.PGVECTOR_USER || "postgres",
password: process.env.PGVECTOR_PASSWORD || "postgres",
host: process.env.PGVECTOR_HOST || "localhost",
port: parseInt(process.env.PGVECTOR_PORT || "5432"),
embeddingModelDims: 1536,
hnsw: true,
},
},
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
},
},
historyDbPath: "memory.db",
});
await runTests(memory);
}
if (require.main === module) {
if (!process.env.PGVECTOR_DB) {
console.log("\nSkipping PGVector test - environment variables not set");
process.exit(0);
}
demoPGVector();
}
@@ -0,0 +1,50 @@
import { Memory } from "../../src";
import { runTests } from "../utils/test-utils";
export async function demoQdrant() {
console.log("\n=== Testing Qdrant Store ===\n");
const memory = new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "qdrant",
config: {
collectionName: "memories",
embeddingModelDims: 1536,
url: process.env.QDRANT_URL,
apiKey: process.env.QDRANT_API_KEY,
path: process.env.QDRANT_PATH,
host: process.env.QDRANT_HOST,
port: process.env.QDRANT_PORT
? parseInt(process.env.QDRANT_PORT)
: undefined,
onDisk: true,
},
},
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
},
},
historyDbPath: "memory.db",
});
await runTests(memory);
}
if (require.main === module) {
if (!process.env.QDRANT_URL && !process.env.QDRANT_HOST) {
console.log("\nSkipping Qdrant test - environment variables not set");
process.exit(0);
}
demoQdrant();
}
@@ -0,0 +1,45 @@
import { Memory } from "../../src";
import { runTests } from "../utils/test-utils";
export async function demoRedis() {
console.log("\n=== Testing Redis Store ===\n");
const memory = new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "redis",
config: {
collectionName: "memories",
embeddingModelDims: 1536,
redisUrl: process.env.REDIS_URL || "redis://localhost:6379",
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD,
},
},
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
},
},
historyDbPath: "memory.db",
});
await runTests(memory);
}
if (require.main === module) {
if (!process.env.REDIS_URL) {
console.log("\nSkipping Redis test - environment variables not set");
process.exit(0);
}
demoRedis();
}
@@ -0,0 +1,49 @@
import { Memory } from "../../src";
import { runTests } from "../utils/test-utils";
import dotenv from "dotenv";
// Load environment variables
dotenv.config();
export async function demoSupabase() {
console.log("\n=== Testing Supabase Vector Store ===\n");
const memory = new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "supabase",
config: {
collectionName: "memories",
embeddingModelDims: 1536,
supabaseUrl: process.env.SUPABASE_URL || "",
supabaseKey: process.env.SUPABASE_KEY || "",
tableName: "memories",
},
},
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
},
},
historyDbPath: "memory.db",
});
await runTests(memory);
}
if (require.main === module) {
if (!process.env.SUPABASE_URL || !process.env.SUPABASE_KEY) {
console.log("\nSkipping Supabase test - environment variables not set");
process.exit(0);
}
demoSupabase();
}
+2 -2
View File
@@ -7,8 +7,8 @@
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"test": "jest", "test": "jest",
"start": "ts-node examples/basic.ts", "start": "pnpm run example memory",
"example": "ts-node examples/basic.ts", "example": "ts-node examples/vector-stores/index.ts",
"clean": "rimraf dist", "clean": "rimraf dist",
"prepare": "npm run build" "prepare": "npm run build"
}, },
+7 -1
View File
@@ -1,6 +1,7 @@
import { MemoryConfig } from "../types"; import { MemoryConfig } from "../types";
export const DEFAULT_MEMORY_CONFIG: MemoryConfig = { export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
disableHistory: false,
version: "v1.1", version: "v1.1",
embedder: { embedder: {
provider: "openai", provider: "openai",
@@ -38,5 +39,10 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
}, },
}, },
}, },
historyDbPath: "memory.db", historyStore: {
provider: "sqlite",
config: {
historyDbPath: "memory.db",
},
},
}; };
+6
View File
@@ -51,6 +51,12 @@ export class ConfigManager {
...DEFAULT_MEMORY_CONFIG.graphStore, ...DEFAULT_MEMORY_CONFIG.graphStore,
...userConfig.graphStore, ...userConfig.graphStore,
}, },
historyStore: {
...DEFAULT_MEMORY_CONFIG.historyStore,
...userConfig.historyStore,
},
disableHistory:
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph, enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
}; };
+2
View File
@@ -3,11 +3,13 @@ export * from "./memory/memory.types";
export * from "./types"; export * from "./types";
export * from "./embeddings/base"; export * from "./embeddings/base";
export * from "./embeddings/openai"; export * from "./embeddings/openai";
export * from "./embeddings/ollama";
export * from "./llms/base"; export * from "./llms/base";
export * from "./llms/openai"; export * from "./llms/openai";
export * from "./llms/openai_structured"; export * from "./llms/openai_structured";
export * from "./llms/anthropic"; export * from "./llms/anthropic";
export * from "./llms/groq"; export * from "./llms/groq";
export * from "./llms/ollama";
export * from "./vector_stores/base"; export * from "./vector_stores/base";
export * from "./vector_stores/memory"; export * from "./vector_stores/memory";
export * from "./vector_stores/qdrant"; export * from "./vector_stores/qdrant";
+1 -1
View File
@@ -57,7 +57,7 @@ export class OllamaLLM implements LLM {
arguments: JSON.stringify(call.function.arguments), arguments: JSON.stringify(call.function.arguments),
})), })),
}; };
} }
return response.content || ""; return response.content || "";
} }
+45 -5
View File
@@ -12,6 +12,7 @@ import {
EmbedderFactory, EmbedderFactory,
LLMFactory, LLMFactory,
VectorStoreFactory, VectorStoreFactory,
HistoryManagerFactory,
} from "../utils/factory"; } from "../utils/factory";
import { import {
getFactRetrievalMessages, getFactRetrievalMessages,
@@ -19,7 +20,7 @@ import {
parseMessages, parseMessages,
removeCodeBlocks, removeCodeBlocks,
} from "../prompts"; } from "../prompts";
import { SQLiteManager } from "../storage"; import { DummyHistoryManager } from "../storage/DummyHistoryManager";
import { Embedder } from "../embeddings/base"; import { Embedder } from "../embeddings/base";
import { LLM } from "../llms/base"; import { LLM } from "../llms/base";
import { VectorStore } from "../vector_stores/base"; import { VectorStore } from "../vector_stores/base";
@@ -32,14 +33,14 @@ import {
GetAllMemoryOptions, GetAllMemoryOptions,
} from "./memory.types"; } from "./memory.types";
import { parse_vision_messages } from "../utils/memory"; import { parse_vision_messages } from "../utils/memory";
import { HistoryManager } from "../storage/base";
export class Memory { export class Memory {
private config: MemoryConfig; private config: MemoryConfig;
private customPrompt: string | undefined; private customPrompt: string | undefined;
private embedder: Embedder; private embedder: Embedder;
private vectorStore: VectorStore; private vectorStore: VectorStore;
private llm: LLM; private llm: LLM;
private db: SQLiteManager; private db: HistoryManager;
private collectionName: string; private collectionName: string;
private apiVersion: string; private apiVersion: string;
private graphMemory?: MemoryGraph; private graphMemory?: MemoryGraph;
@@ -62,7 +63,25 @@ export class Memory {
this.config.llm.provider, this.config.llm.provider,
this.config.llm.config, this.config.llm.config,
); );
this.db = new SQLiteManager(this.config.historyDbPath || ":memory:"); if (this.config.disableHistory) {
this.db = new DummyHistoryManager();
} else {
const defaultConfig = {
provider: "sqlite",
config: {
historyDbPath: this.config.historyDbPath || ":memory:",
},
};
this.db =
this.config.historyStore && !this.config.disableHistory
? HistoryManagerFactory.create(
this.config.historyStore.provider,
this.config.historyStore,
)
: HistoryManagerFactory.create("sqlite", defaultConfig);
}
this.collectionName = this.config.vectorStore.config.collectionName; this.collectionName = this.config.vectorStore.config.collectionName;
this.apiVersion = this.config.version || "v1.0"; this.apiVersion = this.config.version || "v1.0";
this.enableGraph = this.config.enableGraph || false; this.enableGraph = this.config.enableGraph || false;
@@ -93,7 +112,7 @@ export class Memory {
runId, runId,
metadata = {}, metadata = {},
filters = {}, filters = {},
prompt, infer = true,
} = config; } = config;
if (userId) filters.userId = metadata.userId = userId; if (userId) filters.userId = metadata.userId = userId;
@@ -117,6 +136,7 @@ export class Memory {
final_parsedMessages, final_parsedMessages,
metadata, metadata,
filters, filters,
infer,
); );
// Add to graph store if available // Add to graph store if available
@@ -142,7 +162,27 @@ export class Memory {
messages: Message[], messages: Message[],
metadata: Record<string, any>, metadata: Record<string, any>,
filters: SearchFilters, filters: SearchFilters,
infer: boolean,
): Promise<MemoryItem[]> { ): Promise<MemoryItem[]> {
if (!infer) {
const returnedMemories: MemoryItem[] = [];
for (const message of messages) {
if (message.content === "system") {
continue;
}
const memoryId = await this.createMemory(
message.content as string,
{},
metadata,
);
returnedMemories.push({
id: memoryId,
memory: message.content as string,
metadata: { event: "ADD" },
});
}
return returnedMemories;
}
const parsedMessages = messages.map((m) => m.content).join("\n"); const parsedMessages = messages.map((m) => m.content).join("\n");
// Get prompts // Get prompts
+1 -1
View File
@@ -10,7 +10,7 @@ export interface Entity {
export interface AddMemoryOptions extends Entity { export interface AddMemoryOptions extends Entity {
metadata?: Record<string, any>; metadata?: Record<string, any>;
filters?: SearchFilters; filters?: SearchFilters;
prompt?: string; infer?: boolean;
} }
export interface SearchMemoryOptions extends Entity { export interface SearchMemoryOptions extends Entity {
@@ -0,0 +1,27 @@
export class DummyHistoryManager {
constructor() {}
async addHistory(
memoryId: string,
previousValue: string | null,
newValue: string | null,
action: string,
createdAt?: string,
updatedAt?: string,
isDeleted: number = 0,
): Promise<void> {
return;
}
async getHistory(memoryId: string): Promise<any[]> {
return [];
}
async reset(): Promise<void> {
return;
}
close(): void {
return;
}
}
@@ -0,0 +1,58 @@
import { v4 as uuidv4 } from "uuid";
import { HistoryManager } from "./base";
interface HistoryEntry {
id: string;
memory_id: string;
previous_value: string | null;
new_value: string | null;
action: string;
created_at: string;
updated_at: string | null;
is_deleted: number;
}
export class MemoryHistoryManager implements HistoryManager {
private memoryStore: Map<string, HistoryEntry> = new Map();
async addHistory(
memoryId: string,
previousValue: string | null,
newValue: string | null,
action: string,
createdAt?: string,
updatedAt?: string,
isDeleted: number = 0,
): Promise<void> {
const historyEntry: HistoryEntry = {
id: uuidv4(),
memory_id: memoryId,
previous_value: previousValue,
new_value: newValue,
action: action,
created_at: createdAt || new Date().toISOString(),
updated_at: updatedAt || null,
is_deleted: isDeleted,
};
this.memoryStore.set(historyEntry.id, historyEntry);
}
async getHistory(memoryId: string): Promise<any[]> {
return Array.from(this.memoryStore.values())
.filter((entry) => entry.memory_id === memoryId)
.sort(
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
)
.slice(0, 100);
}
async reset(): Promise<void> {
this.memoryStore.clear();
}
close(): void {
// No need to close anything for in-memory storage
return;
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import sqlite3 from "sqlite3"; import sqlite3 from "sqlite3";
import { promisify } from "util"; import { HistoryManager } from "./base";
export class SQLiteManager { export class SQLiteManager implements HistoryManager {
private db: sqlite3.Database; private db: sqlite3.Database;
constructor(dbPath: string) { constructor(dbPath: string) {
@@ -0,0 +1,121 @@
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { v4 as uuidv4 } from "uuid";
import { HistoryManager } from "./base";
interface HistoryEntry {
id: string;
memory_id: string;
previous_value: string | null;
new_value: string | null;
action: string;
created_at: string;
updated_at: string | null;
is_deleted: number;
}
interface SupabaseHistoryConfig {
supabaseUrl: string;
supabaseKey: string;
tableName?: string;
}
export class SupabaseHistoryManager implements HistoryManager {
private supabase: SupabaseClient;
private readonly tableName: string;
constructor(config: SupabaseHistoryConfig) {
this.tableName = config.tableName || "memory_history";
this.supabase = createClient(config.supabaseUrl, config.supabaseKey);
this.initializeSupabase().catch(console.error);
}
private async initializeSupabase(): Promise<void> {
// Check if table exists
const { error } = await this.supabase
.from(this.tableName)
.select("id")
.limit(1);
if (error) {
console.error(
"Error: Table does not exist. Please run this SQL in your Supabase SQL Editor:",
);
console.error(`
create table ${this.tableName} (
id text primary key,
memory_id text not null,
previous_value text,
new_value text,
action text not null,
created_at timestamp with time zone default timezone('utc', now()),
updated_at timestamp with time zone,
is_deleted integer default 0
);
`);
throw error;
}
}
async addHistory(
memoryId: string,
previousValue: string | null,
newValue: string | null,
action: string,
createdAt?: string,
updatedAt?: string,
isDeleted: number = 0,
): Promise<void> {
const historyEntry: HistoryEntry = {
id: uuidv4(),
memory_id: memoryId,
previous_value: previousValue,
new_value: newValue,
action: action,
created_at: createdAt || new Date().toISOString(),
updated_at: updatedAt || null,
is_deleted: isDeleted,
};
const { error } = await this.supabase
.from(this.tableName)
.insert(historyEntry);
if (error) {
console.error("Error adding history to Supabase:", error);
throw error;
}
}
async getHistory(memoryId: string): Promise<any[]> {
const { data, error } = await this.supabase
.from(this.tableName)
.select("*")
.eq("memory_id", memoryId)
.order("created_at", { ascending: false })
.limit(100);
if (error) {
console.error("Error getting history from Supabase:", error);
throw error;
}
return data || [];
}
async reset(): Promise<void> {
const { error } = await this.supabase
.from(this.tableName)
.delete()
.neq("id", "");
if (error) {
console.error("Error resetting Supabase history:", error);
throw error;
}
}
close(): void {
// No need to close anything as connections are handled by the client
return;
}
}
+14
View File
@@ -0,0 +1,14 @@
export interface HistoryManager {
addHistory(
memoryId: string,
previousValue: string | null,
newValue: string | null,
action: string,
createdAt?: string,
updatedAt?: string,
isDeleted?: number,
): Promise<void>;
getHistory(memoryId: string): Promise<any[]>;
reset(): Promise<void>;
close(): void;
}
+4
View File
@@ -1 +1,5 @@
export * from "./SQLiteManager"; export * from "./SQLiteManager";
export * from "./DummyHistoryManager";
export * from "./SupabaseHistoryManager";
export * from "./MemoryHistoryManager";
export * from "./base";
+19
View File
@@ -24,6 +24,16 @@ export interface VectorStoreConfig {
[key: string]: any; [key: string]: any;
} }
export interface HistoryStoreConfig {
provider: string;
config: {
historyDbPath?: string;
supabaseUrl?: string;
supabaseKey?: string;
tableName?: string;
};
}
export interface LLMConfig { export interface LLMConfig {
provider?: string; provider?: string;
config?: Record<string, any>; config?: Record<string, any>;
@@ -58,6 +68,8 @@ export interface MemoryConfig {
provider: string; provider: string;
config: LLMConfig; config: LLMConfig;
}; };
historyStore?: HistoryStoreConfig;
disableHistory?: boolean;
historyDbPath?: string; historyDbPath?: string;
customPrompt?: string; customPrompt?: string;
graphStore?: GraphStoreConfig; graphStore?: GraphStoreConfig;
@@ -137,4 +149,11 @@ export const MemoryConfigSchema = z.object({
customPrompt: z.string().optional(), customPrompt: z.string().optional(),
}) })
.optional(), .optional(),
historyStore: z
.object({
provider: z.string(),
config: z.record(z.string(), z.any()),
})
.optional(),
disableHistory: z.boolean().optional(),
}); });
+33 -1
View File
@@ -5,13 +5,24 @@ import { OpenAIStructuredLLM } from "../llms/openai_structured";
import { AnthropicLLM } from "../llms/anthropic"; import { AnthropicLLM } from "../llms/anthropic";
import { GroqLLM } from "../llms/groq"; import { GroqLLM } from "../llms/groq";
import { MemoryVectorStore } from "../vector_stores/memory"; import { MemoryVectorStore } from "../vector_stores/memory";
import { EmbeddingConfig, LLMConfig, VectorStoreConfig } from "../types"; import {
EmbeddingConfig,
HistoryStoreConfig,
LLMConfig,
VectorStoreConfig,
} from "../types";
import { Embedder } from "../embeddings/base"; import { Embedder } from "../embeddings/base";
import { LLM } from "../llms/base"; import { LLM } from "../llms/base";
import { VectorStore } from "../vector_stores/base"; import { VectorStore } from "../vector_stores/base";
import { Qdrant } from "../vector_stores/qdrant"; import { Qdrant } from "../vector_stores/qdrant";
import { RedisDB } from "../vector_stores/redis"; import { RedisDB } from "../vector_stores/redis";
import { OllamaLLM } from "../llms/ollama"; import { OllamaLLM } from "../llms/ollama";
import { SupabaseDB } from "../vector_stores/supabase";
import { SQLiteManager } from "../storage/SQLiteManager";
import { MemoryHistoryManager } from "../storage/MemoryHistoryManager";
import { SupabaseHistoryManager } from "../storage/SupabaseHistoryManager";
import { HistoryManager } from "../storage/base";
export class EmbedderFactory { export class EmbedderFactory {
static create(provider: string, config: EmbeddingConfig): Embedder { static create(provider: string, config: EmbeddingConfig): Embedder {
switch (provider.toLowerCase()) { switch (provider.toLowerCase()) {
@@ -53,8 +64,29 @@ export class VectorStoreFactory {
return new Qdrant(config as any); // Type assertion needed as config is extended return new Qdrant(config as any); // Type assertion needed as config is extended
case "redis": case "redis":
return new RedisDB(config as any); // Type assertion needed as config is extended return new RedisDB(config as any); // Type assertion needed as config is extended
case "supabase":
return new SupabaseDB(config as any); // Type assertion needed as config is extended
default: default:
throw new Error(`Unsupported vector store provider: ${provider}`); throw new Error(`Unsupported vector store provider: ${provider}`);
} }
} }
} }
export class HistoryManagerFactory {
static create(provider: string, config: HistoryStoreConfig): HistoryManager {
switch (provider.toLowerCase()) {
case "sqlite":
return new SQLiteManager(config.config.historyDbPath || ":memory:");
case "supabase":
return new SupabaseHistoryManager({
supabaseUrl: config.config.supabaseUrl || "",
supabaseKey: config.config.supabaseKey || "",
tableName: config.config.tableName || "memory_history",
});
case "memory":
return new MemoryHistoryManager();
default:
throw new Error(`Unsupported history store provider: ${provider}`);
}
}
}
@@ -0,0 +1,339 @@
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { VectorStore } from "./base";
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
interface VectorData {
id: string;
embedding: number[];
metadata: Record<string, any>;
[key: string]: any;
}
interface VectorQueryParams {
query_embedding: number[];
match_count: number;
filter?: SearchFilters;
}
interface VectorSearchResult {
id: string;
similarity: number;
metadata: Record<string, any>;
[key: string]: any;
}
interface SupabaseConfig extends VectorStoreConfig {
supabaseUrl: string;
supabaseKey: string;
tableName: string;
embeddingColumnName?: string;
metadataColumnName?: string;
}
/*
SQL Migration to run in Supabase SQL Editor:
-- Enable the vector extension
create extension if not exists vector;
-- Create the memories table
create table if not exists memories (
id text primary key,
embedding vector(1536),
metadata jsonb,
created_at timestamp with time zone default timezone('utc', now()),
updated_at timestamp with time zone default timezone('utc', now())
);
-- Create the vector similarity search function
create or replace function match_vectors(
query_embedding vector(1536),
match_count int,
filter jsonb default '{}'::jsonb
)
returns table (
id text,
similarity float,
metadata jsonb
)
language plpgsql
as $$
begin
return query
select
t.id::text,
1 - (t.embedding <=> query_embedding) as similarity,
t.metadata
from memories t
where case
when filter::text = '{}'::text then true
else t.metadata @> filter
end
order by t.embedding <=> query_embedding
limit match_count;
end;
$$;
*/
export class SupabaseDB implements VectorStore {
private client: SupabaseClient;
private readonly tableName: string;
private readonly embeddingColumnName: string;
private readonly metadataColumnName: string;
constructor(config: SupabaseConfig) {
this.client = createClient(config.supabaseUrl, config.supabaseKey);
this.tableName = config.tableName;
this.embeddingColumnName = config.embeddingColumnName || "embedding";
this.metadataColumnName = config.metadataColumnName || "metadata";
this.initialize().catch((err) => {
console.error("Failed to initialize Supabase:", err);
throw err;
});
}
private async initialize(): Promise<void> {
try {
// Verify table exists and vector operations work by attempting a test insert
const testVector = Array(1536).fill(0);
try {
await this.client.from(this.tableName).delete().eq("id", "test_vector");
} catch (error) {
console.warn("No test vector to delete, safe to ignore.");
}
const { error: testError } = await this.client
.from(this.tableName)
.insert({
id: "test_vector",
[this.embeddingColumnName]: testVector,
[this.metadataColumnName]: {},
})
.select();
if (testError) {
console.error("Test insert error:", testError);
throw new Error(
`Vector operations failed. Please ensure:
1. The vector extension is enabled
2. The table "${this.tableName}" exists with correct schema
3. The match_vectors function is created
RUN THE FOLLOWING SQL IN YOUR SUPABASE SQL EDITOR:
-- Enable the vector extension
create extension if not exists vector;
-- Create the memories table
create table if not exists memories (
id text primary key,
embedding vector(1536),
metadata jsonb,
created_at timestamp with time zone default timezone('utc', now()),
updated_at timestamp with time zone default timezone('utc', now())
);
-- Create the vector similarity search function
create or replace function match_vectors(
query_embedding vector(1536),
match_count int,
filter jsonb default '{}'::jsonb
)
returns table (
id text,
similarity float,
metadata jsonb
)
language plpgsql
as $$
begin
return query
select
t.id::text,
1 - (t.embedding <=> query_embedding) as similarity,
t.metadata
from memories t
where case
when filter::text = '{}'::text then true
else t.metadata @> filter
end
order by t.embedding <=> query_embedding
limit match_count;
end;
$$;
See the SQL migration instructions in the code comments.`,
);
}
// Clean up test vector
await this.client.from(this.tableName).delete().eq("id", "test_vector");
console.log("Connected to Supabase successfully");
} catch (error) {
console.error("Error during Supabase initialization:", error);
throw error;
}
}
async insert(
vectors: number[][],
ids: string[],
payloads: Record<string, any>[],
): Promise<void> {
try {
const data = vectors.map((vector, idx) => ({
id: ids[idx],
[this.embeddingColumnName]: vector,
[this.metadataColumnName]: {
...payloads[idx],
created_at: new Date().toISOString(),
},
}));
const { error } = await this.client.from(this.tableName).insert(data);
if (error) throw error;
} catch (error) {
console.error("Error during vector insert:", error);
throw error;
}
}
async search(
query: number[],
limit: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
try {
const rpcQuery: VectorQueryParams = {
query_embedding: query,
match_count: limit,
};
if (filters) {
rpcQuery.filter = filters;
}
const { data, error } = await this.client.rpc("match_vectors", rpcQuery);
if (error) throw error;
if (!data) return [];
const results = data as VectorSearchResult[];
return results.map((result) => ({
id: result.id,
payload: result.metadata,
score: result.similarity,
}));
} catch (error) {
console.error("Error during vector search:", error);
throw error;
}
}
async get(vectorId: string): Promise<VectorStoreResult | null> {
try {
const { data, error } = await this.client
.from(this.tableName)
.select("*")
.eq("id", vectorId)
.single();
if (error) throw error;
if (!data) return null;
return {
id: data.id,
payload: data[this.metadataColumnName],
};
} catch (error) {
console.error("Error getting vector:", error);
throw error;
}
}
async update(
vectorId: string,
vector: number[],
payload: Record<string, any>,
): Promise<void> {
try {
const { error } = await this.client
.from(this.tableName)
.update({
[this.embeddingColumnName]: vector,
[this.metadataColumnName]: {
...payload,
updated_at: new Date().toISOString(),
},
})
.eq("id", vectorId);
if (error) throw error;
} catch (error) {
console.error("Error during vector update:", error);
throw error;
}
}
async delete(vectorId: string): Promise<void> {
try {
const { error } = await this.client
.from(this.tableName)
.delete()
.eq("id", vectorId);
if (error) throw error;
} catch (error) {
console.error("Error deleting vector:", error);
throw error;
}
}
async deleteCol(): Promise<void> {
try {
const { error } = await this.client
.from(this.tableName)
.delete()
.neq("id", ""); // Delete all rows
if (error) throw error;
} catch (error) {
console.error("Error deleting collection:", error);
throw error;
}
}
async list(
filters?: SearchFilters,
limit: number = 100,
): Promise<[VectorStoreResult[], number]> {
try {
let query = this.client
.from(this.tableName)
.select("*", { count: "exact" })
.limit(limit);
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
query = query.eq(`${this.metadataColumnName}->>${key}`, value);
});
}
const { data, error, count } = await query;
if (error) throw error;
const results = data.map((item: VectorData) => ({
id: item.id,
payload: item[this.metadataColumnName],
}));
return [results, count || 0];
} catch (error) {
console.error("Error listing vectors:", error);
throw error;
}
}
}
+23 -9
View File
@@ -3,7 +3,6 @@ import os
import warnings import warnings
from functools import wraps from functools import wraps
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from enum import Enum
import httpx import httpx
@@ -618,6 +617,23 @@ class MemoryClient:
capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id}) capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id})
return response.json() return response.json()
@api_error_handler
def feedback(
self, memory_id: str, feedback: Optional[str] = None, feedback_reason: Optional[str] = None
) -> Dict[str, str]:
VALID_FEEDBACK_VALUES = {"POSITIVE", "NEGATIVE", "VERY_NEGATIVE"}
feedback = feedback.upper() if feedback else None
if feedback is not None and feedback not in VALID_FEEDBACK_VALUES:
raise ValueError(f'feedback must be one of {", ".join(VALID_FEEDBACK_VALUES)} or None')
data = {"memory_id": memory_id, "feedback": feedback, "feedback_reason": feedback_reason}
response = self.client.post("/v1/feedback/", json=data)
response.raise_for_status()
capture_client_event("client.feedback", self, data)
return response.json()
def _prepare_payload( def _prepare_payload(
self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any] self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
@@ -1001,20 +1017,18 @@ class AsyncMemoryClient:
return response.json() return response.json()
@api_error_handler @api_error_handler
async def feedback(self, memory_id: str, feedback: Optional[str] = None, feedback_reason: Optional[str] = None) -> Dict[str, str]: async def feedback(
self, memory_id: str, feedback: Optional[str] = None, feedback_reason: Optional[str] = None
) -> Dict[str, str]:
VALID_FEEDBACK_VALUES = {"POSITIVE", "NEGATIVE", "VERY_NEGATIVE"} VALID_FEEDBACK_VALUES = {"POSITIVE", "NEGATIVE", "VERY_NEGATIVE"}
feedback = feedback.upper() if feedback else None feedback = feedback.upper() if feedback else None
if feedback is not None and feedback not in VALID_FEEDBACK_VALUES: if feedback is not None and feedback not in VALID_FEEDBACK_VALUES:
raise ValueError(f'feedback must be one of {", ".join(VALID_FEEDBACK_VALUES)} or None') raise ValueError(f'feedback must be one of {", ".join(VALID_FEEDBACK_VALUES)} or None')
data = { data = {"memory_id": memory_id, "feedback": feedback, "feedback_reason": feedback_reason}
"memory_id": memory_id,
"feedback": feedback,
"feedback_reason": feedback_reason
}
response = await self.async_client.post("/v1/feedback/", json=data) response = await self.async_client.post("/v1/feedback/", json=data)
response.raise_for_status() response.raise_for_status()
capture_client_event("async_client.feedback", self.sync_client, data) capture_client_event("async_client.feedback", self.sync_client, data)
return response.json() return response.json()
+7
View File
@@ -30,6 +30,8 @@ class BaseEmbedderConfig(ABC):
memory_add_embedding_type: Optional[str] = None, memory_add_embedding_type: Optional[str] = None,
memory_update_embedding_type: Optional[str] = None, memory_update_embedding_type: Optional[str] = None,
memory_search_embedding_type: Optional[str] = None, memory_search_embedding_type: Optional[str] = None,
# LM Studio specific
lmstudio_base_url: Optional[str] = "http://localhost:1234/v1",
): ):
""" """
Initializes a configuration class instance for the Embeddings. Initializes a configuration class instance for the Embeddings.
@@ -58,6 +60,8 @@ class BaseEmbedderConfig(ABC):
:type memory_update_embedding_type: Optional[str], optional :type memory_update_embedding_type: Optional[str], optional
:param memory_search_embedding_type: The type of embedding to use for the search memory action, defaults to None :param memory_search_embedding_type: The type of embedding to use for the search memory action, defaults to None
:type memory_search_embedding_type: Optional[str], optional :type memory_search_embedding_type: 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
""" """
self.model = model self.model = model
@@ -82,3 +86,6 @@ class BaseEmbedderConfig(ABC):
self.memory_add_embedding_type = memory_add_embedding_type self.memory_add_embedding_type = memory_add_embedding_type
self.memory_update_embedding_type = memory_update_embedding_type self.memory_update_embedding_type = memory_update_embedding_type
self.memory_search_embedding_type = memory_search_embedding_type self.memory_search_embedding_type = memory_search_embedding_type
# LM Studio specific
self.lmstudio_base_url = lmstudio_base_url
+7
View File
@@ -39,6 +39,8 @@ class BaseLlmConfig(ABC):
deepseek_base_url: Optional[str] = None, deepseek_base_url: Optional[str] = None,
# XAI specific # XAI specific
xai_base_url: Optional[str] = None, xai_base_url: Optional[str] = None,
# LM Studio specific
lmstudio_base_url: Optional[str] = "http://localhost:1234/v1",
): ):
""" """
Initializes a configuration class instance for the LLM. Initializes a configuration class instance for the LLM.
@@ -83,6 +85,8 @@ class BaseLlmConfig(ABC):
:type deepseek_base_url: Optional[str], optional :type deepseek_base_url: Optional[str], optional
:param xai_base_url: XAI base URL to be use, defaults to None :param xai_base_url: XAI base URL to be use, defaults to None
:type xai_base_url: Optional[str], optional :type xai_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
""" """
self.model = model self.model = model
@@ -116,3 +120,6 @@ class BaseLlmConfig(ABC):
# XAI specific # XAI specific
self.xai_base_url = xai_base_url self.xai_base_url = xai_base_url
# LM Studio specific
self.lmstudio_base_url = lmstudio_base_url
+3 -2
View File
@@ -208,11 +208,12 @@ Please note to return the IDs in the output from the input IDs only and do not g
} }
""" """
def get_update_memory_messages(retrieved_old_memory_dict, response_content, custom_update_memory_prompt=None): def get_update_memory_messages(retrieved_old_memory_dict, response_content, custom_update_memory_prompt=None):
if custom_update_memory_prompt is None: if custom_update_memory_prompt is None:
global DEFAULT_UPDATE_MEMORY_PROMPT global DEFAULT_UPDATE_MEMORY_PROMPT
custom_update_memory_prompt = DEFAULT_UPDATE_MEMORY_PROMPT custom_update_memory_prompt = DEFAULT_UPDATE_MEMORY_PROMPT
return f"""{custom_update_memory_prompt} return f"""{custom_update_memory_prompt}
Below is the current content of my memory which I have collected till now. You have to update it in the following format only: Below is the current content of my memory which I have collected till now. You have to update it in the following format only:
@@ -250,4 +251,4 @@ def get_update_memory_messages(retrieved_old_memory_dict, response_content, cust
- If there is an update, the ID key should remain the same and only the value needs to be updated. - If there is an update, the ID key should remain the same and only the value needs to be updated.
Do not return anything except the JSON format. Do not return anything except the JSON format.
""" """
+16 -11
View File
@@ -8,21 +8,26 @@ class AzureAISearchConfig(BaseModel):
api_key: str = Field(None, description="API key for the Azure AI Search service") api_key: str = Field(None, description="API key for the Azure AI Search service")
embedding_model_dims: int = Field(None, description="Dimension of the embedding vector") embedding_model_dims: int = Field(None, description="Dimension of the embedding vector")
compression_type: Optional[str] = Field( compression_type: Optional[str] = Field(
None, None, description="Type of vector compression to use. Options: 'scalar', 'binary', or None"
description="Type of vector compression to use. Options: 'scalar', 'binary', or None"
) )
use_float16: bool = Field( use_float16: bool = Field(
False, False,
description="Whether to store vectors in half precision (Edm.Half) instead of full precision (Edm.Single)" description="Whether to store vectors in half precision (Edm.Half) instead of full precision (Edm.Single)",
) )
hybrid_search: bool = Field(
False, description="Whether to use hybrid search. If True, vector_filter_mode must be 'preFilter'"
)
vector_filter_mode: Optional[str] = Field(
"preFilter", description="Mode for vector filtering. Options: 'preFilter', 'postFilter'"
)
@model_validator(mode="before") @model_validator(mode="before")
@classmethod @classmethod
def validate_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]: def validate_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
allowed_fields = set(cls.model_fields.keys()) allowed_fields = set(cls.model_fields.keys())
input_fields = set(values.keys()) input_fields = set(values.keys())
extra_fields = input_fields - allowed_fields extra_fields = input_fields - allowed_fields
# Check for use_compression to provide a helpful error # Check for use_compression to provide a helpful error
if "use_compression" in extra_fields: if "use_compression" in extra_fields:
raise ValueError( raise ValueError(
@@ -30,13 +35,13 @@ class AzureAISearchConfig(BaseModel):
"Please use 'compression_type=\"scalar\"' instead of 'use_compression=True' " "Please use 'compression_type=\"scalar\"' instead of 'use_compression=True' "
"or 'compression_type=None' instead of 'use_compression=False'." "or 'compression_type=None' instead of 'use_compression=False'."
) )
if extra_fields: if extra_fields:
raise ValueError( raise ValueError(
f"Extra fields not allowed: {', '.join(extra_fields)}. " f"Extra fields not allowed: {', '.join(extra_fields)}. "
f"Please input only the following fields: {', '.join(allowed_fields)}" f"Please input only the following fields: {', '.join(allowed_fields)}"
) )
# Validate compression_type values # Validate compression_type values
if "compression_type" in values and values["compression_type"] is not None: if "compression_type" in values and values["compression_type"] is not None:
valid_types = ["scalar", "binary"] valid_types = ["scalar", "binary"]
@@ -45,9 +50,9 @@ class AzureAISearchConfig(BaseModel):
f"Invalid compression_type: {values['compression_type']}. " f"Invalid compression_type: {values['compression_type']}. "
f"Must be one of: {', '.join(valid_types)}, or None" f"Must be one of: {', '.join(valid_types)}, or None"
) )
return values return values
model_config = { model_config = {
"arbitrary_types_allowed": True, "arbitrary_types_allowed": True,
} }
+1 -2
View File
@@ -17,8 +17,7 @@ class ElasticsearchConfig(BaseModel):
use_ssl: bool = Field(True, description="Use SSL for connection") use_ssl: bool = Field(True, description="Use SSL for connection")
auto_create_index: bool = Field(True, description="Automatically create index during initialization") auto_create_index: bool = Field(True, description="Automatically create index during initialization")
custom_search_query: Optional[Callable[[List[float], int, Optional[Dict]], Dict]] = Field( custom_search_query: Optional[Callable[[List[float], int, Optional[Dict]], Dict]] = Field(
None, None, description="Custom search query function. Parameters: (query, limit, filters) -> Dict"
description="Custom search query function. Parameters: (query, limit, filters) -> Dict"
) )
@model_validator(mode="before") @model_validator(mode="before")
+38
View File
@@ -0,0 +1,38 @@
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field, model_validator
class FAISSConfig(BaseModel):
collection_name: str = Field("mem0", description="Default name for the collection")
path: Optional[str] = Field(None, description="Path to store FAISS index and metadata")
distance_strategy: str = Field(
"euclidean", description="Distance strategy to use. Options: 'euclidean', 'inner_product', 'cosine'"
)
normalize_L2: bool = Field(
False, description="Whether to normalize L2 vectors (only applicable for euclidean distance)"
)
@model_validator(mode="before")
@classmethod
def validate_distance_strategy(cls, values: Dict[str, Any]) -> Dict[str, Any]:
distance_strategy = values.get("distance_strategy")
if distance_strategy and distance_strategy not in ["euclidean", "inner_product", "cosine"]:
raise ValueError("Invalid distance_strategy. Must be one of: 'euclidean', 'inner_product', 'cosine'")
return values
@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,
}
@@ -14,9 +14,7 @@ class GoogleMatchingEngineConfig(BaseModel):
credentials_path: Optional[str] = Field(None, description="Path to service account credentials file") credentials_path: Optional[str] = Field(None, description="Path to service account credentials file")
vector_search_api_endpoint: Optional[str] = Field(None, description="Vector search API endpoint") vector_search_api_endpoint: Optional[str] = Field(None, description="Vector search API endpoint")
model_config = { model_config = {"extra": "forbid"}
"extra": "forbid"
}
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -26,4 +24,4 @@ class GoogleMatchingEngineConfig(BaseModel):
def model_post_init(self, _context) -> None: def model_post_init(self, _context) -> None:
"""Set collection_name to index_id if not provided""" """Set collection_name to index_id if not provided"""
if self.collection_name is None: if self.collection_name is None:
self.collection_name = self.index_id self.collection_name = self.index_id
+10 -1
View File
@@ -13,7 +13,16 @@ class EmbedderConfig(BaseModel):
@field_validator("config") @field_validator("config")
def validate_config(cls, v, values): def validate_config(cls, v, values):
provider = values.data.get("provider") provider = values.data.get("provider")
if provider in ["openai", "ollama", "huggingface", "azure_openai", "gemini", "vertexai", "together"]: if provider in [
"openai",
"ollama",
"huggingface",
"azure_openai",
"gemini",
"vertexai",
"together",
"lmstudio",
]:
return v return v
else: else:
raise ValueError(f"Unsupported embedding provider: {provider}") raise ValueError(f"Unsupported embedding provider: {provider}")
+3 -1
View File
@@ -28,5 +28,7 @@ class GoogleGenAIEmbedding(EmbeddingBase):
list: The embedding vector. list: The embedding vector.
""" """
text = text.replace("\n", " ") text = text.replace("\n", " ")
response = genai.embed_content(model=self.config.model, content=text, output_dimensionality=self.config.embedding_dims) response = genai.embed_content(
model=self.config.model, content=text, output_dimensionality=self.config.embedding_dims
)
return response["embedding"] return response["embedding"]
+29
View File
@@ -0,0 +1,29 @@
from typing import Literal, Optional
from openai import OpenAI
from mem0.configs.embeddings.base import BaseEmbedderConfig
from mem0.embeddings.base import EmbeddingBase
class LMStudioEmbedding(EmbeddingBase):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config)
self.config.model = self.config.model or "nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.f16.gguf"
self.config.embedding_dims = self.config.embedding_dims or 1536
self.config.api_key = self.config.api_key or "lm-studio"
self.client = OpenAI(base_url=self.config.lmstudio_base_url, api_key=self.config.api_key)
def embed(self, text, memory_action: Optional[Literal["add", "search", "update"]] = None):
"""
Get the embedding for the given text using LM Studio.
Args:
text (str): The text to embed.
memory_action (optional): The type of embedding to use. Must be one of "add", "search", or "update". Defaults to None.
Returns:
list: The embedding vector.
"""
text = text.replace("\n", " ")
return self.client.embeddings.create(input=[text], model=self.config.model).data[0].embedding
+19 -2
View File
@@ -1,4 +1,5 @@
import os import os
import warnings
from typing import Literal, Optional from typing import Literal, Optional
from openai import OpenAI from openai import OpenAI
@@ -15,7 +16,19 @@ class OpenAIEmbedding(EmbeddingBase):
self.config.embedding_dims = self.config.embedding_dims or 1536 self.config.embedding_dims = self.config.embedding_dims or 1536
api_key = self.config.api_key or os.getenv("OPENAI_API_KEY") api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
base_url = self.config.openai_base_url or os.getenv("OPENAI_API_BASE") base_url = (
self.config.openai_base_url
or os.getenv("OPENAI_API_BASE")
or os.getenv("OPENAI_BASE_URL")
or "https://api.openai.com/v1"
)
if os.environ.get("OPENAI_API_BASE"):
warnings.warn(
"The environment variable 'OPENAI_API_BASE' is deprecated and will be removed in the 0.1.80. "
"Please use 'OPENAI_BASE_URL' instead.",
DeprecationWarning,
)
self.client = OpenAI(api_key=api_key, base_url=base_url) self.client = OpenAI(api_key=api_key, base_url=base_url)
def embed(self, text, memory_action: Optional[Literal["add", "search", "update"]] = None): def embed(self, text, memory_action: Optional[Literal["add", "search", "update"]] = None):
@@ -29,4 +42,8 @@ class OpenAIEmbedding(EmbeddingBase):
list: The embedding vector. list: The embedding vector.
""" """
text = text.replace("\n", " ") text = text.replace("\n", " ")
return self.client.embeddings.create(input=[text], model=self.config.model, dimensions = self.config.embedding_dims).data[0].embedding return (
self.client.embeddings.create(input=[text], model=self.config.model, dimensions=self.config.embedding_dims)
.data[0]
.embedding
)
+12 -4
View File
@@ -80,13 +80,21 @@ class AzureOpenAILLM(LLMBase):
Returns: Returns:
str: The generated response. str: The generated response.
""" """
params = {
common_params = {
"model": self.config.model, "model": self.config.model,
"messages": messages, "messages": messages,
"temperature": self.config.temperature,
"max_tokens": self.config.max_tokens,
"top_p": self.config.top_p,
} }
if self.config.model in {"o3-mini", "o1-preview", "o1"}:
params = common_params
else:
params = {
**common_params,
"temperature": self.config.temperature,
"max_tokens": self.config.max_tokens,
"top_p": self.config.top_p,
}
if response_format: if response_format:
params["response_format"] = response_format params["response_format"] = response_format
if tools: # TODO: Remove tools if no issues found with new memory addition logic if tools: # TODO: Remove tools if no issues found with new memory addition logic
+2 -1
View File
@@ -1,4 +1,3 @@
import json
import os import os
from typing import Dict, List, Optional from typing import Dict, List, Optional
@@ -36,6 +35,8 @@ class AzureOpenAIStructuredLLM(LLMBase):
self, self,
messages: List[Dict[str, str]], messages: List[Dict[str, str]],
response_format: Optional[str] = None, response_format: Optional[str] = None,
tools: Optional[List[Dict]] = None,
tool_choice: str = "auto",
) -> str: ) -> str:
""" """
Generate a response based on the given messages using Azure OpenAI. Generate a response based on the given messages using Azure OpenAI.
+4 -2
View File
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Optional from typing import Dict, List, Optional
from mem0.configs.llms.base import BaseLlmConfig from mem0.configs.llms.base import BaseLlmConfig
@@ -17,12 +17,14 @@ class LLMBase(ABC):
self.config = config self.config = config
@abstractmethod @abstractmethod
def generate_response(self, messages): def generate_response(self, messages, tools: Optional[List[Dict]] = None, tool_choice: str = "auto"):
""" """
Generate a response based on the given messages. Generate a response based on the given messages.
Args: Args:
messages (list): List of message dicts containing 'role' and 'content'. messages (list): List of message dicts containing 'role' and 'content'.
tools (list, optional): List of tools that the model can call. Defaults to None.
tool_choice (str, optional): Tool choice method. Defaults to "auto".
Returns: Returns:
str: The generated response. str: The generated response.
+1
View File
@@ -24,6 +24,7 @@ class LlmConfig(BaseModel):
"gemini", "gemini",
"deepseek", "deepseek",
"xai", "xai",
"lmstudio",
): ):
return v return v
else: else:
+1 -1
View File
@@ -84,4 +84,4 @@ class GroqLLM(LLMBase):
params["tool_choice"] = tool_choice params["tool_choice"] = tool_choice
response = self.client.chat.completions.create(**params) response = self.client.chat.completions.create(**params)
return self._parse_response(response, tools) return self._parse_response(response, tools)
+51
View File
@@ -0,0 +1,51 @@
from typing import Dict, List, Optional
from openai import OpenAI
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
class LMStudioLLM(LLMBase):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config)
self.config.model = (
self.config.model
or "lmstudio-community/Meta-Llama-3.1-70B-Instruct-GGUF/Meta-Llama-3.1-70B-Instruct-IQ2_M.gguf"
)
self.config.api_key = self.config.api_key or "lm-studio"
self.client = OpenAI(base_url=self.config.lmstudio_base_url, api_key=self.config.api_key)
def generate_response(
self,
messages: List[Dict[str, str]],
response_format: dict = {"type": "json_object"},
tools: Optional[List[Dict]] = None,
tool_choice: str = "auto",
):
"""
Generate a response based on the given messages using LM Studio.
Args:
messages (list): List of message dicts containing 'role' and 'content'.
response_format (str or object, optional): Format of the response. Defaults to "text".
tools (list, optional): List of tools that the model can call. Defaults to None.
tool_choice (str, optional): Tool choice method. Defaults to "auto".
Returns:
str: The generated response.
"""
params = {
"model": self.config.model,
"messages": messages,
"temperature": self.config.temperature,
"max_tokens": self.config.max_tokens,
"top_p": self.config.top_p,
}
if response_format:
params["response_format"] = response_format
response = self.client.chat.completions.create(**params)
return response.choices[0].message.content
+14 -1
View File
@@ -1,5 +1,6 @@
import json import json
import os import os
import warnings
from typing import Dict, List, Optional from typing import Dict, List, Optional
from openai import OpenAI from openai import OpenAI
@@ -24,7 +25,19 @@ class OpenAILLM(LLMBase):
) )
else: else:
api_key = self.config.api_key or os.getenv("OPENAI_API_KEY") api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
base_url = self.config.openai_base_url or os.getenv("OPENAI_API_BASE") or "https://api.openai.com/v1" base_url = (
self.config.openai_base_url
or os.getenv("OPENAI_API_BASE")
or os.getenv("OPENAI_BASE_URL")
or "https://api.openai.com/v1"
)
if os.environ.get("OPENAI_API_BASE"):
warnings.warn(
"The environment variable 'OPENAI_API_BASE' is deprecated and will be removed in the 0.1.80. "
"Please use 'OPENAI_BASE_URL' instead.",
DeprecationWarning,
)
self.client = OpenAI(api_key=api_key, base_url=base_url) self.client = OpenAI(api_key=api_key, base_url=base_url)
def _parse_response(self, response, tools): def _parse_response(self, response, tools):
+2 -1
View File
@@ -1,4 +1,3 @@
import json
import os import os
from typing import Dict, List, Optional from typing import Dict, List, Optional
@@ -23,6 +22,8 @@ class OpenAIStructuredLLM(LLMBase):
self, self,
messages: List[Dict[str, str]], messages: List[Dict[str, str]],
response_format: Optional[str] = None, response_format: Optional[str] = None,
tools: Optional[List[Dict]] = None,
tool_choice: str = "auto",
) -> str: ) -> str:
""" """
Generate a response based on the given messages using OpenAI. Generate a response based on the given messages using OpenAI.
+9 -1
View File
@@ -18,13 +18,21 @@ class XAILLM(LLMBase):
base_url = self.config.xai_base_url or os.getenv("XAI_API_BASE") or "https://api.x.ai/v1" base_url = self.config.xai_base_url or os.getenv("XAI_API_BASE") or "https://api.x.ai/v1"
self.client = OpenAI(api_key=api_key, base_url=base_url) self.client = OpenAI(api_key=api_key, base_url=base_url)
def generate_response(self, messages: List[Dict[str, str]], response_format=None): def generate_response(
self,
messages: List[Dict[str, str]],
response_format=None,
tools: Optional[List[Dict]] = None,
tool_choice: str = "auto",
):
""" """
Generate a response based on the given messages using XAI. Generate a response based on the given messages using XAI.
Args: Args:
messages (list): List of message dicts containing 'role' and 'content'. messages (list): List of message dicts containing 'role' and 'content'.
response_format (str or object, optional): Format of the response. Defaults to "text". response_format (str or object, optional): Format of the response. Defaults to "text".
tools (list, optional): List of tools that the model can call. Defaults to None.
tool_choice (str, optional): Tool choice method. Defaults to "auto".
Returns: Returns:
str: The generated response. str: The generated response.
+8 -5
View File
@@ -12,11 +12,14 @@ try:
except ImportError: except ImportError:
raise ImportError("rank_bm25 is not installed. Please install it using pip install rank-bm25") raise ImportError("rank_bm25 is not installed. Please install it using pip install rank-bm25")
from mem0.graphs.tools import (DELETE_MEMORY_STRUCT_TOOL_GRAPH, from mem0.graphs.tools import (
DELETE_MEMORY_TOOL_GRAPH, DELETE_MEMORY_STRUCT_TOOL_GRAPH,
EXTRACT_ENTITIES_STRUCT_TOOL, DELETE_MEMORY_TOOL_GRAPH,
EXTRACT_ENTITIES_TOOL, RELATIONS_STRUCT_TOOL, EXTRACT_ENTITIES_STRUCT_TOOL,
RELATIONS_TOOL) EXTRACT_ENTITIES_TOOL,
RELATIONS_STRUCT_TOOL,
RELATIONS_TOOL,
)
from mem0.graphs.utils import EXTRACT_RELATIONS_PROMPT, get_delete_messages from mem0.graphs.utils import EXTRACT_RELATIONS_PROMPT, get_delete_messages
from mem0.utils.factory import EmbedderFactory, LlmFactory from mem0.utils.factory import EmbedderFactory, LlmFactory
+9 -5
View File
@@ -71,13 +71,14 @@ class Memory(MemoryBase):
if "vector_store" not in config_dict and "embedder" in config_dict: if "vector_store" not in config_dict and "embedder" in config_dict:
config_dict["vector_store"] = {} config_dict["vector_store"] = {}
config_dict["vector_store"]["config"] = {} config_dict["vector_store"]["config"] = {}
config_dict["vector_store"]["config"]["embedding_model_dims"] = config_dict["embedder"]["config"]["embedding_dims"] config_dict["vector_store"]["config"]["embedding_model_dims"] = config_dict["embedder"]["config"][
"embedding_dims"
]
try: try:
return config_dict return config_dict
except ValidationError as e: except ValidationError as e:
logger.error(f"Configuration validation error: {e}") logger.error(f"Configuration validation error: {e}")
raise raise
def add( def add(
self, self,
@@ -204,7 +205,8 @@ class Memory(MemoryBase):
messages_embeddings = self.embedding_model.embed(new_mem, "add") messages_embeddings = self.embedding_model.embed(new_mem, "add")
new_message_embeddings[new_mem] = messages_embeddings new_message_embeddings[new_mem] = messages_embeddings
existing_memories = self.vector_store.search( existing_memories = self.vector_store.search(
query=messages_embeddings, query=new_mem,
vectors=messages_embeddings,
limit=5, limit=5,
filters=filters, filters=filters,
) )
@@ -222,7 +224,9 @@ class Memory(MemoryBase):
temp_uuid_mapping[str(idx)] = item["id"] temp_uuid_mapping[str(idx)] = item["id"]
retrieved_old_memory[idx]["id"] = str(idx) retrieved_old_memory[idx]["id"] = str(idx)
function_calling_prompt = get_update_memory_messages(retrieved_old_memory, new_retrieved_facts, self.custom_update_memory_prompt) function_calling_prompt = get_update_memory_messages(
retrieved_old_memory, new_retrieved_facts, self.custom_update_memory_prompt
)
try: try:
new_memories_with_actions = self.llm.generate_response( new_memories_with_actions = self.llm.generate_response(
@@ -479,7 +483,7 @@ class Memory(MemoryBase):
def _search_vector_store(self, query, filters, limit): def _search_vector_store(self, query, filters, limit):
embeddings = self.embedding_model.embed(query, "search") embeddings = self.embedding_model.embed(query, "search")
memories = self.vector_store.search(query=embeddings, limit=limit, filters=filters) memories = self.vector_store.search(query=query, vectors=embeddings, limit=limit, filters=filters)
excluded_keys = { excluded_keys = {
"user_id", "user_id",
+3
View File
@@ -25,6 +25,7 @@ class LlmFactory:
"gemini": "mem0.llms.gemini.GeminiLLM", "gemini": "mem0.llms.gemini.GeminiLLM",
"deepseek": "mem0.llms.deepseek.DeepSeekLLM", "deepseek": "mem0.llms.deepseek.DeepSeekLLM",
"xai": "mem0.llms.xai.XAILLM", "xai": "mem0.llms.xai.XAILLM",
"lmstudio": "mem0.llms.lmstudio.LMStudioLLM",
} }
@classmethod @classmethod
@@ -47,6 +48,7 @@ class EmbedderFactory:
"gemini": "mem0.embeddings.gemini.GoogleGenAIEmbedding", "gemini": "mem0.embeddings.gemini.GoogleGenAIEmbedding",
"vertexai": "mem0.embeddings.vertexai.VertexAIEmbedding", "vertexai": "mem0.embeddings.vertexai.VertexAIEmbedding",
"together": "mem0.embeddings.together.TogetherEmbedding", "together": "mem0.embeddings.together.TogetherEmbedding",
"lmstudio": "mem0.embeddings.lmstudio.LMStudioEmbedding",
} }
@classmethod @classmethod
@@ -74,6 +76,7 @@ class VectorStoreFactory:
"opensearch": "mem0.vector_stores.opensearch.OpenSearchDB", "opensearch": "mem0.vector_stores.opensearch.OpenSearchDB",
"supabase": "mem0.vector_stores.supabase.Supabase", "supabase": "mem0.vector_stores.supabase.Supabase",
"weaviate": "mem0.vector_stores.weaviate.Weaviate", "weaviate": "mem0.vector_stores.weaviate.Weaviate",
"faiss": "mem0.vector_stores.faiss.FAISS",
} }
@classmethod @classmethod
+39 -36
View File
@@ -45,8 +45,10 @@ class AzureAISearch(VectorStoreBase):
collection_name, collection_name,
api_key, api_key,
embedding_model_dims, embedding_model_dims,
compression_type: Optional[str] = None, compression_type: Optional[str] = None,
use_float16: bool = False, use_float16: bool = False,
hybrid_search: bool = False,
vector_filter_mode: Optional[str] = None,
): ):
""" """
Initialize the Azure AI Search vector store. Initialize the Azure AI Search vector store.
@@ -60,13 +62,17 @@ class AzureAISearch(VectorStoreBase):
Allowed values are None (no quantization), "scalar", or "binary". Allowed values are None (no quantization), "scalar", or "binary".
use_float16 (bool): Whether to store vectors in half precision (Edm.Half) or full precision (Edm.Single). use_float16 (bool): Whether to store vectors in half precision (Edm.Half) or full precision (Edm.Single).
(Note: This flag is preserved from the initial implementation per feedback.) (Note: This flag is preserved from the initial implementation per feedback.)
hybrid_search (bool): Whether to use hybrid search. Default is False.
vector_filter_mode (Optional[str]): Mode for vector filtering. Default is "preFilter".
""" """
self.index_name = collection_name self.index_name = collection_name
self.collection_name = collection_name self.collection_name = collection_name
self.embedding_model_dims = embedding_model_dims self.embedding_model_dims = embedding_model_dims
# If compression_type is None, treat it as "none". # If compression_type is None, treat it as "none".
self.compression_type = (compression_type or "none").lower() self.compression_type = (compression_type or "none").lower()
self.use_float16 = use_float16 self.use_float16 = use_float16
self.hybrid_search = hybrid_search
self.vector_filter_mode = vector_filter_mode
self.search_client = SearchClient( self.search_client = SearchClient(
endpoint=f"https://{service_name}.search.windows.net", endpoint=f"https://{service_name}.search.windows.net",
@@ -81,7 +87,9 @@ class AzureAISearch(VectorStoreBase):
self.search_client._client._config.user_agent_policy.add_user_agent("mem0") self.search_client._client._config.user_agent_policy.add_user_agent("mem0")
self.index_client._client._config.user_agent_policy.add_user_agent("mem0") self.index_client._client._config.user_agent_policy.add_user_agent("mem0")
self.create_col() # create the collection / index collections = self.list_cols()
if collection_name not in collections:
self.create_col()
def create_col(self): def create_col(self):
"""Create a new index in Azure AI Search.""" """Create a new index in Azure AI Search."""
@@ -113,8 +121,6 @@ class AzureAISearch(VectorStoreBase):
) )
] ]
# If no compression is desired, compression_configurations remains empty. # If no compression is desired, compression_configurations remains empty.
fields = [ fields = [
SimpleField(name="id", type=SearchFieldDataType.String, key=True), SimpleField(name="id", type=SearchFieldDataType.String, key=True),
SimpleField(name="user_id", type=SearchFieldDataType.String, filterable=True), SimpleField(name="user_id", type=SearchFieldDataType.String, filterable=True),
@@ -123,11 +129,11 @@ class AzureAISearch(VectorStoreBase):
SearchField( SearchField(
name="vector", name="vector",
type=vector_type, type=vector_type,
searchable=True, searchable=True,
vector_search_dimensions=self.embedding_model_dims, vector_search_dimensions=self.embedding_model_dims,
vector_search_profile_name="my-vector-config", vector_search_profile_name="my-vector-config",
), ),
SimpleField(name="payload", type=SearchFieldDataType.String, searchable=True), SearchField(name="payload", type=SearchFieldDataType.String, searchable=True),
] ]
vector_search = VectorSearch( vector_search = VectorSearch(
@@ -135,7 +141,7 @@ class AzureAISearch(VectorStoreBase):
VectorSearchProfile( VectorSearchProfile(
name="my-vector-config", name="my-vector-config",
algorithm_configuration_name="my-algorithms-config", algorithm_configuration_name="my-algorithms-config",
compression_name=compression_name if self.compression_type != "none" else None compression_name=compression_name if self.compression_type != "none" else None,
) )
], ],
algorithms=[HnswAlgorithmConfiguration(name="my-algorithms-config")], algorithms=[HnswAlgorithmConfiguration(name="my-algorithms-config")],
@@ -164,8 +170,7 @@ class AzureAISearch(VectorStoreBase):
""" """
logger.info(f"Inserting {len(vectors)} vectors into index {self.index_name}") logger.info(f"Inserting {len(vectors)} vectors into index {self.index_name}")
documents = [ documents = [
self._generate_document(vector, payload, id) self._generate_document(vector, payload, id) for id, vector, payload in zip(ids, vectors, payloads)
for id, vector, payload in zip(ids, vectors, payloads)
] ]
response = self.search_client.upload_documents(documents) response = self.search_client.upload_documents(documents)
for doc in response: for doc in response:
@@ -189,12 +194,13 @@ class AzureAISearch(VectorStoreBase):
filter_expression = " and ".join(filter_conditions) filter_expression = " and ".join(filter_conditions)
return filter_expression return filter_expression
def search(self, query, limit=5, filters=None): def search(self, query, vectors, limit=5, filters=None):
""" """
Search for similar vectors. Search for similar vectors.
Args: Args:
query (List[float]): Query vector. query (str): Query.
vectors (List[float]): Query vector.
limit (int, optional): Number of results to return. Defaults to 5. limit (int, optional): Number of results to return. Defaults to 5.
filters (Dict, optional): Filters to apply to the search. Defaults to None. filters (Dict, optional): Filters to apply to the search. Defaults to None.
@@ -205,23 +211,28 @@ class AzureAISearch(VectorStoreBase):
if filters: if filters:
filter_expression = self._build_filter_expression(filters) filter_expression = self._build_filter_expression(filters)
vector_query = VectorizedQuery( vector_query = VectorizedQuery(vector=vectors, k_nearest_neighbors=limit, fields="vector")
vector=query, k_nearest_neighbors=limit, fields="vector" if self.hybrid_search:
) search_results = self.search_client.search(
search_results = self.search_client.search( search_text=query,
vector_queries=[vector_query], vector_queries=[vector_query],
filter=filter_expression, filter=filter_expression,
top=limit top=limit,
) vector_filter_mode=self.vector_filter_mode,
search_fields=["payload"],
)
else:
search_results = self.search_client.search(
vector_queries=[vector_query],
filter=filter_expression,
top=limit,
vector_filter_mode=self.vector_filter_mode,
)
results = [] results = []
for result in search_results: for result in search_results:
payload = json.loads(result["payload"]) payload = json.loads(result["payload"])
results.append( results.append(OutputData(id=result["id"], score=result["@search.score"], payload=payload))
OutputData(
id=result["id"], score=result["@search.score"], payload=payload
)
)
return results return results
def delete(self, vector_id): def delete(self, vector_id):
@@ -275,9 +286,7 @@ class AzureAISearch(VectorStoreBase):
result = self.search_client.get_document(key=vector_id) result = self.search_client.get_document(key=vector_id)
except ResourceNotFoundError: except ResourceNotFoundError:
return None return None
return OutputData( return OutputData(id=result["id"], score=None, payload=json.loads(result["payload"]))
id=result["id"], score=None, payload=json.loads(result["payload"])
)
def list_cols(self) -> List[str]: def list_cols(self) -> List[str]:
""" """
@@ -321,17 +330,11 @@ class AzureAISearch(VectorStoreBase):
if filters: if filters:
filter_expression = self._build_filter_expression(filters) filter_expression = self._build_filter_expression(filters)
search_results = self.search_client.search( search_results = self.search_client.search(search_text="*", filter=filter_expression, top=limit)
search_text="*", filter=filter_expression, top=limit
)
results = [] results = []
for result in search_results: for result in search_results:
payload = json.loads(result["payload"]) payload = json.loads(result["payload"])
results.append( results.append(OutputData(id=result["id"], score=result["@search.score"], payload=payload))
OutputData(
id=result["id"], score=result["@search.score"], payload=payload
)
)
return [results] return [results]
def __del__(self): def __del__(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class VectorStoreBase(ABC):
pass pass
@abstractmethod @abstractmethod
def search(self, query, limit=5, filters=None): def search(self, query, vectors, limit=5, filters=None):
"""Search for similar vectors.""" """Search for similar vectors."""
pass pass
+6 -3
View File
@@ -127,19 +127,22 @@ class ChromaDB(VectorStoreBase):
logger.info(f"Inserting {len(vectors)} vectors into collection {self.collection_name}") logger.info(f"Inserting {len(vectors)} vectors into collection {self.collection_name}")
self.collection.add(ids=ids, embeddings=vectors, metadatas=payloads) self.collection.add(ids=ids, embeddings=vectors, metadatas=payloads)
def search(self, query: List[list], limit: int = 5, filters: Optional[Dict] = None) -> List[OutputData]: def search(
self, query: str, vectors: List[list], limit: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]:
""" """
Search for similar vectors. Search for similar vectors.
Args: Args:
query (List[list]): Query vector. query (str): Query.
vectors (List[list]): List of vectors to search.
limit (int, optional): Number of results to return. Defaults to 5. limit (int, optional): Number of results to return. Defaults to 5.
filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None. filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None.
Returns: Returns:
List[OutputData]: Search results. List[OutputData]: Search results.
""" """
results = self.collection.query(query_embeddings=query, where=filters, n_results=limit) results = self.collection.query(query_embeddings=vectors, where=filters, n_results=limit)
final_results = self._parse_output(results) final_results = self._parse_output(results)
return final_results return final_results
+1
View File
@@ -23,6 +23,7 @@ class VectorStoreConfig(BaseModel):
"opensearch": "OpenSearchConfig", "opensearch": "OpenSearchConfig",
"supabase": "SupabaseConfig", "supabase": "SupabaseConfig",
"weaviate": "WeaviateConfig", "weaviate": "WeaviateConfig",
"faiss": "FAISSConfig",
} }
@model_validator(mode="after") @model_validator(mode="after")
+9 -5
View File
@@ -45,7 +45,7 @@ class ElasticsearchDB(VectorStoreBase):
# Create index only if auto_create_index is True # Create index only if auto_create_index is True
if config.auto_create_index: if config.auto_create_index:
self.create_index() self.create_index()
if config.custom_search_query: if config.custom_search_query:
self.custom_search_query = config.custom_search_query self.custom_search_query = config.custom_search_query
else: else:
@@ -121,21 +121,25 @@ class ElasticsearchDB(VectorStoreBase):
) )
return results return results
def search(self, query: List[float], limit: int = 5, filters: Optional[Dict] = None) -> List[OutputData]: def search(
self, query: str, vectors: List[float], limit: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]:
""" """
Search with two options: Search with two options:
1. Use custom search query if provided 1. Use custom search query if provided
2. Use KNN search on vectors with pre-filtering if no custom search query is provided 2. Use KNN search on vectors with pre-filtering if no custom search query is provided
""" """
if self.custom_search_query: if self.custom_search_query:
search_query = self.custom_search_query(query, limit, filters) search_query = self.custom_search_query(vectors, limit, filters)
else: else:
search_query = {"knn": {"field": "vector", "query_vector": query, "k": limit, "num_candidates": limit * 2}} search_query = {
"knn": {"field": "vector", "query_vector": vectors, "k": limit, "num_candidates": limit * 2}
}
if filters: if filters:
filter_conditions = [] filter_conditions = []
for key, value in filters.items(): for key, value in filters.items():
filter_conditions.append({"term": {f"metadata.{key}": value}}) filter_conditions.append({"term": {f"metadata.{key}": value}})
search_query["filter"] = {"bool": {"must": filter_conditions}} search_query["knn"]["filter"] = {"bool": {"must": filter_conditions}}
response = self.client.search(index=self.collection_name, body=search_query) response = self.client.search(index=self.collection_name, body=search_query)
+464
View File
@@ -0,0 +1,464 @@
import logging
import os
import pickle
import uuid
from pathlib import Path
from typing import Dict, List, Optional
import numpy as np
from pydantic import BaseModel
try:
import faiss
except ImportError:
raise ImportError(
"Could not import faiss python package. "
"Please install it with `pip install faiss-gpu` (for CUDA supported GPU) "
"or `pip install faiss-cpu` (depending on Python version)."
)
from mem0.vector_stores.base import VectorStoreBase
logger = logging.getLogger(__name__)
class OutputData(BaseModel):
id: Optional[str] # memory id
score: Optional[float] # distance
payload: Optional[Dict] # metadata
class FAISS(VectorStoreBase):
def __init__(
self,
collection_name: str,
path: Optional[str] = None,
distance_strategy: str = "euclidean",
normalize_L2: bool = False,
):
"""
Initialize the FAISS vector store.
Args:
collection_name (str): Name of the collection.
path (str, optional): Path for local FAISS database. Defaults to None.
distance_strategy (str, optional): Distance strategy to use. Options: 'euclidean', 'inner_product', 'cosine'.
Defaults to "euclidean".
normalize_L2 (bool, optional): Whether to normalize L2 vectors. Only applicable for euclidean distance.
Defaults to False.
"""
self.collection_name = collection_name
self.path = path or f"/tmp/faiss/{collection_name}"
self.distance_strategy = distance_strategy
self.normalize_L2 = normalize_L2
# Initialize storage structures
self.index = None
self.docstore = {}
self.index_to_id = {}
# Create directory if it doesn't exist
if self.path:
os.makedirs(os.path.dirname(self.path), exist_ok=True)
# Try to load existing index if available
index_path = f"{self.path}/{collection_name}.faiss"
docstore_path = f"{self.path}/{collection_name}.pkl"
if os.path.exists(index_path) and os.path.exists(docstore_path):
self._load(index_path, docstore_path)
else:
self.create_col(collection_name)
def _load(self, index_path: str, docstore_path: str):
"""
Load FAISS index and docstore from disk.
Args:
index_path (str): Path to FAISS index file.
docstore_path (str): Path to docstore pickle file.
"""
try:
self.index = faiss.read_index(index_path)
with open(docstore_path, "rb") as f:
self.docstore, self.index_to_id = pickle.load(f)
logger.info(f"Loaded FAISS index from {index_path} with {self.index.ntotal} vectors")
except Exception as e:
logger.warning(f"Failed to load FAISS index: {e}")
self.docstore = {}
self.index_to_id = {}
def _save(self):
"""Save FAISS index and docstore to disk."""
if not self.path or not self.index:
return
try:
os.makedirs(self.path, exist_ok=True)
index_path = f"{self.path}/{self.collection_name}.faiss"
docstore_path = f"{self.path}/{self.collection_name}.pkl"
faiss.write_index(self.index, index_path)
with open(docstore_path, "wb") as f:
pickle.dump((self.docstore, self.index_to_id), f)
logger.info(f"Saved FAISS index to {index_path} with {self.index.ntotal} vectors")
except Exception as e:
logger.warning(f"Failed to save FAISS index: {e}")
def _parse_output(self, scores, ids, limit=None) -> List[OutputData]:
"""
Parse the output data.
Args:
scores: Similarity scores from FAISS.
ids: Indices from FAISS.
limit: Maximum number of results to return.
Returns:
List[OutputData]: Parsed output data.
"""
if limit is None:
limit = len(ids)
results = []
for i in range(min(len(ids), limit)):
if ids[i] == -1: # FAISS returns -1 for empty results
continue
index_id = int(ids[i])
vector_id = self.index_to_id.get(index_id)
if vector_id is None:
continue
payload = self.docstore.get(vector_id)
if payload is None:
continue
payload_copy = payload.copy()
score = float(scores[i])
entry = OutputData(
id=vector_id,
score=score,
payload=payload_copy,
)
results.append(entry)
return results
def create_col(self, name: str, vector_size: int = 1536, distance: str = None):
"""
Create a new collection.
Args:
name (str): Name of the collection.
vector_size (int, optional): Dimensionality of vectors. Defaults to 1536.
distance (str, optional): Distance metric to use. Overrides the distance_strategy
passed during initialization. Defaults to None.
Returns:
self: The FAISS instance.
"""
distance_strategy = distance or self.distance_strategy
# Create index based on distance strategy
if distance_strategy.lower() == "inner_product" or distance_strategy.lower() == "cosine":
self.index = faiss.IndexFlatIP(vector_size)
else:
self.index = faiss.IndexFlatL2(vector_size)
self.collection_name = name
self._save()
return self
def insert(
self,
vectors: List[list],
payloads: Optional[List[Dict]] = None,
ids: Optional[List[str]] = None,
):
"""
Insert vectors into a collection.
Args:
vectors (List[list]): List of vectors to insert.
payloads (Optional[List[Dict]], optional): List of payloads corresponding to vectors. Defaults to None.
ids (Optional[List[str]], optional): List of IDs corresponding to vectors. Defaults to None.
"""
if self.index is None:
raise ValueError("Collection not initialized. Call create_col first.")
if ids is None:
ids = [str(uuid.uuid4()) for _ in range(len(vectors))]
if payloads is None:
payloads = [{} for _ in range(len(vectors))]
if len(vectors) != len(ids) or len(vectors) != len(payloads):
raise ValueError("Vectors, payloads, and IDs must have the same length")
vectors_np = np.array(vectors, dtype=np.float32)
if self.normalize_L2 and self.distance_strategy.lower() == "euclidean":
faiss.normalize_L2(vectors_np)
self.index.add(vectors_np)
starting_idx = len(self.index_to_id)
for i, (vector_id, payload) in enumerate(zip(ids, payloads)):
self.docstore[vector_id] = payload.copy()
self.index_to_id[starting_idx + i] = vector_id
self._save()
logger.info(f"Inserted {len(vectors)} vectors into collection {self.collection_name}")
def search(
self, query: str, vectors: List[list], limit: int = 5, filters: Optional[Dict] = None
) -> List[OutputData]:
"""
Search for similar vectors.
Args:
query (str): Query (not used, kept for API compatibility).
vectors (List[list]): List of vectors to search.
limit (int, optional): Number of results to return. Defaults to 5.
filters (Optional[Dict], optional): Filters to apply to the search. Defaults to None.
Returns:
List[OutputData]: Search results.
"""
if self.index is None:
raise ValueError("Collection not initialized. Call create_col first.")
query_vectors = np.array(vectors, dtype=np.float32)
if len(query_vectors.shape) == 1:
query_vectors = query_vectors.reshape(1, -1)
if self.normalize_L2 and self.distance_strategy.lower() == "euclidean":
faiss.normalize_L2(query_vectors)
fetch_k = limit * 2 if filters else limit
scores, indices = self.index.search(query_vectors, fetch_k)
results = self._parse_output(scores[0], indices[0], limit)
if filters:
filtered_results = []
for result in results:
if self._apply_filters(result.payload, filters):
filtered_results.append(result)
if len(filtered_results) >= limit:
break
results = filtered_results[:limit]
return results
def _apply_filters(self, payload: Dict, filters: Dict) -> bool:
"""
Apply filters to a payload.
Args:
payload (Dict): Payload to filter.
filters (Dict): Filters to apply.
Returns:
bool: True if payload passes filters, False otherwise.
"""
if not filters or not payload:
return True
for key, value in filters.items():
if key not in payload:
return False
if isinstance(value, list):
if payload[key] not in value:
return False
elif payload[key] != value:
return False
return True
def delete(self, vector_id: str):
"""
Delete a vector by ID.
Args:
vector_id (str): ID of the vector to delete.
"""
if self.index is None:
raise ValueError("Collection not initialized. Call create_col first.")
index_to_delete = None
for idx, vid in self.index_to_id.items():
if vid == vector_id:
index_to_delete = idx
break
if index_to_delete is not None:
self.docstore.pop(vector_id, None)
self.index_to_id.pop(index_to_delete, None)
self._save()
logger.info(f"Deleted vector {vector_id} from collection {self.collection_name}")
else:
logger.warning(f"Vector {vector_id} not found in collection {self.collection_name}")
def update(
self,
vector_id: str,
vector: Optional[List[float]] = None,
payload: Optional[Dict] = None,
):
"""
Update a vector and its payload.
Args:
vector_id (str): ID of the vector to update.
vector (Optional[List[float]], optional): Updated vector. Defaults to None.
payload (Optional[Dict], optional): Updated payload. Defaults to None.
"""
if self.index is None:
raise ValueError("Collection not initialized. Call create_col first.")
if vector_id not in self.docstore:
raise ValueError(f"Vector {vector_id} not found")
current_payload = self.docstore[vector_id].copy()
if payload is not None:
self.docstore[vector_id] = payload.copy()
current_payload = self.docstore[vector_id].copy()
if vector is not None:
self.delete(vector_id)
self.insert([vector], [current_payload], [vector_id])
else:
self._save()
logger.info(f"Updated vector {vector_id} in collection {self.collection_name}")
def get(self, vector_id: str) -> OutputData:
"""
Retrieve a vector by ID.
Args:
vector_id (str): ID of the vector to retrieve.
Returns:
OutputData: Retrieved vector.
"""
if self.index is None:
raise ValueError("Collection not initialized. Call create_col first.")
if vector_id not in self.docstore:
return None
payload = self.docstore[vector_id].copy()
return OutputData(
id=vector_id,
score=None,
payload=payload,
)
def list_cols(self) -> List[str]:
"""
List all collections.
Returns:
List[str]: List of collection names.
"""
if not self.path:
return [self.collection_name] if self.index else []
try:
collections = []
path = Path(self.path).parent
for file in path.glob("*.faiss"):
collections.append(file.stem)
return collections
except Exception as e:
logger.warning(f"Failed to list collections: {e}")
return [self.collection_name] if self.index else []
def delete_col(self):
"""
Delete a collection.
"""
if self.path:
try:
index_path = f"{self.path}/{self.collection_name}.faiss"
docstore_path = f"{self.path}/{self.collection_name}.pkl"
if os.path.exists(index_path):
os.remove(index_path)
if os.path.exists(docstore_path):
os.remove(docstore_path)
logger.info(f"Deleted collection {self.collection_name}")
except Exception as e:
logger.warning(f"Failed to delete collection: {e}")
self.index = None
self.docstore = {}
self.index_to_id = {}
def col_info(self) -> Dict:
"""
Get information about a collection.
Returns:
Dict: Collection information.
"""
if self.index is None:
return {"name": self.collection_name, "count": 0}
return {
"name": self.collection_name,
"count": self.index.ntotal,
"dimension": self.index.d,
"distance": self.distance_strategy,
}
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[OutputData]:
"""
List all vectors in a collection.
Args:
filters (Optional[Dict], optional): Filters to apply to the list. Defaults to None.
limit (int, optional): Number of vectors to return. Defaults to 100.
Returns:
List[OutputData]: List of vectors.
"""
if self.index is None:
return []
results = []
count = 0
for vector_id, payload in self.docstore.items():
if filters and not self._apply_filters(payload, filters):
continue
payload_copy = payload.copy()
results.append(
OutputData(
id=vector_id,
score=None,
payload=payload_copy,
)
)
count += 1
if count >= limit:
break
return [results]

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