Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
884b597312 | ||
|
|
f1471bcc55 | ||
|
|
9ae23f9c88 | ||
|
|
cbecbb7b64 | ||
|
|
f8ad0c2b2c | ||
|
|
d0da9a1ae0 | ||
|
|
bc4ab3db77 | ||
|
|
0eb53b9f27 | ||
|
|
3cef3ed95e | ||
|
|
45e5f2af93 | ||
|
|
32ba13b3ea | ||
|
|
69a91d5cbb | ||
|
|
2004427acd | ||
|
|
2517ccd489 | ||
|
|
9d0300f774 | ||
|
|
366d263e0b | ||
|
|
4321d24284 | ||
|
|
9cb2a13f3b | ||
|
|
5ec7889d9a | ||
|
|
b54845bcc9 | ||
|
|
1ae2747ff8 | ||
|
|
953a5a4a2d | ||
|
|
2b49c9eedd | ||
|
|
9db5f62262 | ||
|
|
a1bd4285db | ||
|
|
e77a10a8da | ||
|
|
e4307ae420 | ||
|
|
7c89d00079 | ||
|
|
563eaae5ee | ||
|
|
6733f78f81 | ||
|
|
c11637bd2f | ||
|
|
ff30cb8ddd | ||
|
|
2e853c3d22 | ||
|
|
3cc7013fde | ||
|
|
8e6a08aa83 | ||
|
|
8b9a8e5825 | ||
|
|
afc630272d | ||
|
|
e33008e3a4 | ||
|
|
7b516328a8 | ||
|
|
6d5889d98f | ||
|
|
ee66e0c954 | ||
|
|
1aed611539 | ||
|
|
6c2b131d6e | ||
|
|
2ffe9922f3 | ||
|
|
540ada489b | ||
|
|
65cffa0369 | ||
|
|
9f937943ba | ||
|
|
51a68bf7c5 | ||
|
|
92541d8955 | ||
|
|
0e0be18ecc | ||
|
|
66d3f9b93c | ||
|
|
b8f40f728f | ||
|
|
00a2ea9ff0 | ||
|
|
9545836469 | ||
|
|
3acd9e20da | ||
|
|
d48ecd52ef | ||
|
|
2fbea7705b | ||
|
|
d7a26bd0c3 | ||
|
|
e25dc4b504 | ||
|
|
dab3349990 | ||
|
|
6db87e8d07 | ||
|
|
faf811ee2d | ||
|
|
ee80a43810 | ||
|
|
4be426f762 | ||
|
|
ba9c61938b | ||
|
|
65f826e064 | ||
|
|
b43363cdf3 | ||
|
|
89e786a88e | ||
|
|
2d5062bd40 | ||
|
|
b89628322d |
@@ -12,8 +12,8 @@ install:
|
||||
|
||||
install_all:
|
||||
poetry install
|
||||
poetry run pip install groq together boto3 litellm ollama chromadb sentence_transformers vertexai \
|
||||
google-generativeai elasticsearch opensearch-py vecs
|
||||
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 faiss-cpu
|
||||
|
||||
# Format code with ruff
|
||||
format:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: 'Feedback'
|
||||
openapi: post /v1/feedback/
|
||||
---
|
||||
@@ -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>
|
||||
|
||||
@@ -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_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 |
|
||||
| `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
| 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` |
|
||||
@@ -22,6 +22,7 @@ See the list of supported embedders below.
|
||||
<Card title="Gemini" href="/components/embedders/models/gemini"></Card>
|
||||
<Card title="Vertex AI" href="/components/embedders/models/vertexai"></Card>
|
||||
<Card title="Together" href="/components/embedders/models/together"></Card>
|
||||
<Card title="LM Studio" href="/components/embedders/models/lmstudio"></Card>
|
||||
</CardGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -29,7 +29,7 @@ iconType: "solid"
|
||||
Config values are applied in the following order of precedence (from highest to lowest):
|
||||
|
||||
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
|
||||
|
||||
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 |
|
||||
| `deepseek_base_url` | Base URL for DeepSeek API | DeepSeek |
|
||||
| `xai_base_url` | Base URL for XAI API | XAI |
|
||||
| `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
| 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/).
|
||||
|
||||
> **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
|
||||
|
||||
```python
|
||||
|
||||
@@ -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).
|
||||
@@ -32,6 +32,7 @@ To view all supported llms, visit the [Supported LLMs](./models).
|
||||
<Card title="Gemini" href="/components/llms/models/gemini" />
|
||||
<Card title="DeepSeek" href="/components/llms/models/deepseek" />
|
||||
<Card title="xAI" href="/components/llms/models/xAI" />
|
||||
<Card title="LM Studio" href="/components/llms/models/lmstudio" />
|
||||
</CardGroup>
|
||||
|
||||
## Structured vs Unstructured Outputs
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[Azure AI Search](https://learn.microsoft.com/en-us/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
|
||||
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx" #this key is used for embedding purpose
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx" # This key is used for embedding purpose
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
@@ -15,8 +15,7 @@ config = {
|
||||
"service_name": "ai-search-test",
|
||||
"api_key": "*****",
|
||||
"collection_name": "mem0",
|
||||
"embedding_model_dims": 1536 ,
|
||||
"use_compression": False
|
||||
"embedding_model_dims": 1536
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,20 +24,72 @@ 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": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
### Config
|
||||
## Using binary compression for large vector collections
|
||||
|
||||
Let's see the available parameters for the `qdrant` config:
|
||||
service_name (str): Azure Cognitive Search service name.
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `service_name` | Azure AI Search service name | `None` |
|
||||
| `api_key` | API key of the Azure AI Search service | `None` |
|
||||
| `collection_name` | The name of the collection/index to store the vectors, it will be created automatically if not exist | `mem0` |
|
||||
| `embedding_model_dims` | Dimensions of the embedding model | `1536` |
|
||||
| `use_compression` | Use scalar quantization vector compression | False |
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "azure_ai_search",
|
||||
"config": {
|
||||
"service_name": "ai-search-test",
|
||||
"api_key": "*****",
|
||||
"collection_name": "mem0",
|
||||
"embedding_model_dims": 1536,
|
||||
"compression_type": "binary",
|
||||
"use_float16": True # Use half precision for storage efficiency
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
| Parameter | Description | Default Value | Options |
|
||||
| --- | --- | --- | --- |
|
||||
| `service_name` | Azure AI Search service name | Required | - |
|
||||
| `api_key` | API key of the Azure AI Search service | Required | - |
|
||||
| `collection_name` | The name of the collection/index to store vectors | `mem0` | Any valid index name |
|
||||
| `embedding_model_dims` | Dimensions of the embedding model | `1536` | Any integer value |
|
||||
| `compression_type` | Type of vector compression to use | `none` | `none`, `scalar`, `binary` |
|
||||
| `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
|
||||
|
||||
- **compression_type**:
|
||||
- `none`: No compression, uses full vector precision
|
||||
- `scalar`: Scalar quantization with reasonable balance of speed and accuracy
|
||||
- `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.
|
||||
|
||||
- **Filterable Fields**: The implementation automatically extracts `user_id`, `run_id`, and `agent_id` fields from payloads for filtering.
|
||||
@@ -54,6 +54,7 @@ Let's see the available parameters for the `elasticsearch` config:
|
||||
| `password` | Password for basic authentication | `None` |
|
||||
| `verify_certs` | Whether to verify SSL certificates | `True` |
|
||||
| `auto_create_index` | Whether to automatically create the index | `True` |
|
||||
| `custom_search_query` | Function returning a custom search query | `None` |
|
||||
|
||||
### Features
|
||||
|
||||
@@ -62,3 +63,46 @@ Let's see the available parameters for the `elasticsearch` config:
|
||||
- Multiple authentication methods (Basic Auth, API Key)
|
||||
- Automatic index creation with optimized mappings for vector search
|
||||
- Memory isolation through payload filtering
|
||||
- Custom search query function to customize the search query
|
||||
|
||||
### Custom Search Query
|
||||
|
||||
The `custom_search_query` parameter allows you to customize the search query when `Memory.search` is called.
|
||||
|
||||
__Example__
|
||||
```python
|
||||
import os
|
||||
from typing import List, Optional, Dict
|
||||
from mem0 import Memory
|
||||
|
||||
def custom_search_query(query: List[float], limit: int, filters: Optional[Dict]) -> Dict:
|
||||
return {
|
||||
"knn": {
|
||||
"field": "vector",
|
||||
"query_vector": query,
|
||||
"k": limit,
|
||||
"num_candidates": limit * 2
|
||||
}
|
||||
}
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx"
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "elasticsearch",
|
||||
"config": {
|
||||
"collection_name": "mem0",
|
||||
"host": "localhost",
|
||||
"port": 9200,
|
||||
"embedding_model_dims": 1536,
|
||||
"custom_search_query": custom_search_query
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
It should be a function that takes the following parameters:
|
||||
- `query`: a query vector used in `Memory.search`
|
||||
- `limit`: a number of results used in `Memory.search`
|
||||
- `filters`: a dictionary of key-value pairs used in `Memory.search`. You can add custom pairs for the custom search query.
|
||||
|
||||
The function should return a query body for the Elasticsearch search API.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,92 @@
|
||||
[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
|
||||
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx"
|
||||
os.environ["PINECONE_API_KEY"] = "your-api-key"
|
||||
|
||||
# Example using serverless configuration
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "pinecone",
|
||||
"config": {
|
||||
"collection_name": "testing",
|
||||
"embedding_model_dims": 1536, # Matches OpenAI's text-embedding-3-small
|
||||
"serverless_config": {
|
||||
"cloud": "aws", # Choose between 'aws' or 'gcp' or 'azure'
|
||||
"region": "us-east-1"
|
||||
},
|
||||
"metric": "cosine"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring Pinecone:
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `collection_name` | Name of the index/collection | Required |
|
||||
| `embedding_model_dims` | Dimensions of the embedding model (must match your chosen embedding model) | Required |
|
||||
| `client` | Existing Pinecone client instance | `None` |
|
||||
| `api_key` | API key for Pinecone | Environment variable: `PINECONE_API_KEY` |
|
||||
| `environment` | Pinecone environment | `None` |
|
||||
| `serverless_config` | Configuration for serverless deployment (AWS or GCP or Azure) | `None` |
|
||||
| `pod_config` | Configuration for pod-based deployment | `None` |
|
||||
| `hybrid_search` | Whether to enable hybrid search | `False` |
|
||||
| `metric` | Distance metric for vector similarity | `"cosine"` |
|
||||
| `batch_size` | Batch size for operations | `100` |
|
||||
|
||||
> **Important**: You must choose either `serverless_config` or `pod_config` for your deployment, but not both.
|
||||
|
||||
#### Serverless Config Example
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "pinecone",
|
||||
"config": {
|
||||
"collection_name": "memory_index",
|
||||
"embedding_model_dims": 1536, # For OpenAI's text-embedding-3-small
|
||||
"serverless_config": {
|
||||
"cloud": "aws", # or "gcp" or "azure"
|
||||
"region": "us-east-1" # Choose appropriate region
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Pod Config Example
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "pinecone",
|
||||
"config": {
|
||||
"collection_name": "memory_index",
|
||||
"embedding_model_dims": 1536, # For OpenAI's text-embedding-ada-002
|
||||
"pod_config": {
|
||||
"environment": "gcp-starter",
|
||||
"replicas": 1,
|
||||
"pod_type": "starter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -4,7 +4,8 @@ Create a [Supabase](https://supabase.com/dashboard/projects) account and project
|
||||
|
||||
### Usage
|
||||
|
||||
```python
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
@@ -32,10 +33,90 @@ messages = [
|
||||
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
|
||||
|
||||
Here are the parameters available for configuring Supabase:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `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` |
|
||||
| `index_method` | Vector index method to use | `auto` |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
[Weaviate](https://weaviate.io/) is an open-source vector search engine. It allows efficient storage and retrieval of high-dimensional vector embeddings, enabling powerful search and retrieval capabilities.
|
||||
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
pip install weaviate weaviate-client
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx"
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "weaviate",
|
||||
"config": {
|
||||
"collection_name": "test",
|
||||
"cluster_url": "http://localhost:8080",
|
||||
"auth_client_secret": None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about a thriller movie? They can be quite engaging."},
|
||||
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
Let's see the available parameters for the `weaviate` config:
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `collection_name` | The name of the collection to store the vectors | `mem0` |
|
||||
| `embedding_model_dims` | Dimensions of the embedding model | `1536` |
|
||||
| `cluster_url` | URL for the Weaviate server | `None` |
|
||||
| `auth_client_secret` | API key for Weaviate authentication | `None` |
|
||||
@@ -19,12 +19,15 @@ See the list of supported vector databases below.
|
||||
<Card title="Chroma" href="/components/vectordbs/dbs/chroma"></Card>
|
||||
<Card title="Pgvector" href="/components/vectordbs/dbs/pgvector"></Card>
|
||||
<Card title="Milvus" href="/components/vectordbs/dbs/milvus"></Card>
|
||||
<Card title="Pinecone" href="/components/vectordbs/dbs/pinecone"></Card>
|
||||
<Card title="Azure AI Search" href="/components/vectordbs/dbs/azure_ai_search"></Card>
|
||||
<Card title="Redis" href="/components/vectordbs/dbs/redis"></Card>
|
||||
<Card title="Elasticsearch" href="/components/vectordbs/dbs/elasticsearch"></Card>
|
||||
<Card title="OpenSearch" href="/components/vectordbs/dbs/opensearch"></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="Weaviate" href="/components/vectordbs/dbs/weaviate"></Card>
|
||||
<Card title="FAISS" href="/components/vectordbs/dbs/faiss"></Card>
|
||||
</CardGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
+48
-6
@@ -47,6 +47,7 @@
|
||||
"pages": [
|
||||
"features/platform-overview",
|
||||
"features/advanced-retrieval",
|
||||
"features/contextual-add",
|
||||
"features/multimodal-support",
|
||||
"features/selective-memory",
|
||||
"features/custom-categories",
|
||||
@@ -54,7 +55,9 @@
|
||||
"features/direct-import",
|
||||
"features/async-client",
|
||||
"features/memory-export",
|
||||
"features/webhooks"
|
||||
"features/webhooks",
|
||||
"features/graph-memory",
|
||||
"features/feedback-mechanism"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -71,7 +74,8 @@
|
||||
"icon": "wrench",
|
||||
"pages": [
|
||||
"features/openai_compatibility",
|
||||
"features/custom-prompts",
|
||||
"features/custom-fact-extraction-prompt",
|
||||
"features/custom-update-memory-prompt",
|
||||
"open-source/multimodal-support",
|
||||
"open-source/features/rest-api"
|
||||
]
|
||||
@@ -125,12 +129,14 @@
|
||||
"components/vectordbs/dbs/chroma",
|
||||
"components/vectordbs/dbs/pgvector",
|
||||
"components/vectordbs/dbs/milvus",
|
||||
"components/vectordbs/dbs/pinecone",
|
||||
"components/vectordbs/dbs/azure_ai_search",
|
||||
"components/vectordbs/dbs/redis",
|
||||
"components/vectordbs/dbs/elasticsearch",
|
||||
"components/vectordbs/dbs/opensearch",
|
||||
"components/vectordbs/dbs/supabase",
|
||||
"components/vectordbs/dbs/vertex_ai_vector_search"
|
||||
"components/vectordbs/dbs/vertex_ai_vector_search",
|
||||
"components/vectordbs/dbs/weaviate"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -185,7 +191,12 @@
|
||||
"examples/chrome-extension",
|
||||
"examples/document-writing",
|
||||
"examples/multimodal-demo",
|
||||
"examples/personalized-deep-research"
|
||||
"examples/personalized-deep-research",
|
||||
"examples/mem0-agentic-tool",
|
||||
"examples/openai-inbuilt-tools",
|
||||
"examples/mem0-openai-voice-demo",
|
||||
"examples/mem0-livekit-voice-agent",
|
||||
"examples/email_processing"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -206,7 +217,9 @@
|
||||
"integrations/llama-index",
|
||||
"integrations/langchain-tools",
|
||||
"integrations/dify",
|
||||
"integrations/mcp-server"
|
||||
"integrations/mcp-server",
|
||||
"integrations/livekit",
|
||||
"integrations/elevenlabs"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -237,7 +250,8 @@
|
||||
"api-reference/memory/batch-delete",
|
||||
"api-reference/memory/delete-memories",
|
||||
"api-reference/memory/create-memory-export",
|
||||
"api-reference/memory/get-memory-export"
|
||||
"api-reference/memory/get-memory-export",
|
||||
"api-reference/memory/feedback"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -260,6 +274,18 @@
|
||||
"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",
|
||||
"icon": "webhook",
|
||||
@@ -273,6 +299,19 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Changelog",
|
||||
"icon": "clock",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Product Updates",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"changelog/overview"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -333,6 +372,9 @@
|
||||
"posthog": {
|
||||
"apiKey": "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX",
|
||||
"apiHost": "https://mango.mem0.ai"
|
||||
},
|
||||
"intercom": {
|
||||
"appId": "jjv2r0tt"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: Mem0 as an Agentic Tool
|
||||
---
|
||||
|
||||
Integrate Mem0's memory capabilities with OpenAI's Agents SDK to create AI agents with persistent memory.
|
||||
You can create agents that remember past conversations and use that context to provide better responses.
|
||||
|
||||
## Installation
|
||||
|
||||
First, install the required packages:
|
||||
```bash
|
||||
pip install mem0ai pydantic openai-agents
|
||||
```
|
||||
|
||||
You'll also need a custom agents framework for this implementation.
|
||||
|
||||
## Setting Up Environment Variables
|
||||
|
||||
Store your Mem0 API key as an environment variable:
|
||||
|
||||
```bash
|
||||
export MEM0_API_KEY="your_mem0_api_key"
|
||||
```
|
||||
|
||||
Or in your Python script:
|
||||
|
||||
```python
|
||||
import os
|
||||
os.environ["MEM0_API_KEY"] = "your_mem0_api_key"
|
||||
```
|
||||
|
||||
## Code Structure
|
||||
|
||||
The integration consists of three main components:
|
||||
|
||||
1. **Context Manager**: Defines user context for memory operations
|
||||
2. **Memory Tools**: Functions to add, search, and retrieve memories
|
||||
3. **Memory Agent**: An agent configured to use these memory tools
|
||||
|
||||
## Step-by-Step Implementation
|
||||
|
||||
### 1. Import Dependencies
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import asyncio
|
||||
from pydantic import BaseModel
|
||||
try:
|
||||
from mem0 import AsyncMemoryClient
|
||||
except ImportError:
|
||||
raise ImportError("mem0 is not installed. Please install it using 'pip install mem0ai'.")
|
||||
from agents import (
|
||||
Agent,
|
||||
ItemHelpers,
|
||||
MessageOutputItem,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
ToolCallItem,
|
||||
ToolCallOutputItem,
|
||||
TResponseInputItem,
|
||||
function_tool,
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Define Memory Context
|
||||
|
||||
```python
|
||||
class Mem0Context(BaseModel):
|
||||
user_id: str | None = None
|
||||
```
|
||||
|
||||
### 3. Initialize the Mem0 Client
|
||||
|
||||
```python
|
||||
client = AsyncMemoryClient(api_key=os.getenv("MEM0_API_KEY"))
|
||||
```
|
||||
|
||||
### 4. Create Memory Tools
|
||||
|
||||
#### Add to Memory
|
||||
|
||||
```python
|
||||
@function_tool
|
||||
async def add_to_memory(
|
||||
context: RunContextWrapper[Mem0Context],
|
||||
content: str,
|
||||
) -> str:
|
||||
"""
|
||||
Add a message to Mem0
|
||||
Args:
|
||||
content: The content to store in memory.
|
||||
"""
|
||||
messages = [{"role": "user", "content": content}]
|
||||
user_id = context.context.user_id or "default_user"
|
||||
await client.add(messages, user_id=user_id)
|
||||
return f"Stored message: {content}"
|
||||
```
|
||||
|
||||
#### Search Memory
|
||||
|
||||
```python
|
||||
@function_tool
|
||||
async def search_memory(
|
||||
context: RunContextWrapper[Mem0Context],
|
||||
query: str,
|
||||
) -> str:
|
||||
"""
|
||||
Search for memories in Mem0
|
||||
Args:
|
||||
query: The search query.
|
||||
"""
|
||||
user_id = context.context.user_id or "default_user"
|
||||
memories = await client.search(query, user_id=user_id, output_format="v1.1")
|
||||
results = '\n'.join([result["memory"] for result in memories["results"]])
|
||||
return str(results)
|
||||
```
|
||||
|
||||
#### Get All Memories
|
||||
|
||||
```python
|
||||
@function_tool
|
||||
async def get_all_memory(
|
||||
context: RunContextWrapper[Mem0Context],
|
||||
) -> str:
|
||||
"""Retrieve all memories from Mem0"""
|
||||
user_id = context.context.user_id or "default_user"
|
||||
memories = await client.get_all(user_id=user_id, output_format="v1.1")
|
||||
results = '\n'.join([result["memory"] for result in memories["results"]])
|
||||
return str(results)
|
||||
```
|
||||
|
||||
### 5. Configure the Memory Agent
|
||||
|
||||
```python
|
||||
memory_agent = Agent[Mem0Context](
|
||||
name="Memory Assistant",
|
||||
instructions="""You are a helpful assistant with memory capabilities. You can:
|
||||
1. Store new information using add_to_memory
|
||||
2. Search existing information using search_memory
|
||||
3. Retrieve all stored information using get_all_memory
|
||||
When users ask questions:
|
||||
- If they want to store information, use add_to_memory
|
||||
- If they're searching for specific information, use search_memory
|
||||
- If they want to see everything stored, use get_all_memory""",
|
||||
tools=[add_to_memory, search_memory, get_all_memory],
|
||||
)
|
||||
```
|
||||
|
||||
### 6. Implement the Main Runtime Loop
|
||||
|
||||
```python
|
||||
async def main():
|
||||
current_agent: Agent[Mem0Context] = memory_agent
|
||||
input_items: list[TResponseInputItem] = []
|
||||
context = Mem0Context()
|
||||
while True:
|
||||
user_input = input("Enter your message (or 'quit' to exit): ")
|
||||
if user_input.lower() == 'quit':
|
||||
break
|
||||
input_items.append({"content": user_input, "role": "user"})
|
||||
result = await Runner.run(current_agent, input_items, context=context)
|
||||
for new_item in result.new_items:
|
||||
agent_name = new_item.agent.name
|
||||
if isinstance(new_item, MessageOutputItem):
|
||||
print(f"{agent_name}: {ItemHelpers.text_message_output(new_item)}")
|
||||
elif isinstance(new_item, ToolCallItem):
|
||||
print(f"{agent_name}: Calling a tool")
|
||||
elif isinstance(new_item, ToolCallOutputItem):
|
||||
print(f"{agent_name}: Tool call output: {new_item.output}")
|
||||
else:
|
||||
print(f"{agent_name}: Skipping item: {new_item.__class__.__name__}")
|
||||
input_items = result.to_input_list()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Storing Information
|
||||
|
||||
```
|
||||
User: Remember that my favorite color is blue
|
||||
Agent: Calling a tool
|
||||
Agent: Tool call output: Stored message: my favorite color is blue
|
||||
Agent: I've stored that your favorite color is blue in my memory. I'll remember that for future conversations.
|
||||
```
|
||||
|
||||
### Searching Memory
|
||||
|
||||
```
|
||||
User: What's my favorite color?
|
||||
Agent: Calling a tool
|
||||
Agent: Tool call output: my favorite color is blue
|
||||
Agent: Your favorite color is blue, based on what you've told me earlier.
|
||||
```
|
||||
|
||||
### Retrieving All Memories
|
||||
|
||||
```
|
||||
User: What do you know about me?
|
||||
Agent: Calling a tool
|
||||
Agent: Tool call output: favorite color is blue
|
||||
my birthday is on March 15
|
||||
Agent: Based on our previous conversations, I know that:
|
||||
1. Your favorite color is blue
|
||||
2. Your birthday is on March 15
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom User IDs
|
||||
|
||||
You can specify different user IDs to maintain separate memory stores for multiple users:
|
||||
|
||||
```python
|
||||
context = Mem0Context(user_id="user123")
|
||||
```
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 Dashboard](https://app.mem0.ai/dashboard)
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
@@ -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...
|
||||
```
|
||||
@@ -0,0 +1,312 @@
|
||||
---
|
||||
title: OpenAI Inbuilt Tools
|
||||
---
|
||||
|
||||
Integrate Mem0’s memory capabilities with OpenAI’s Inbuilt Tools to create AI agents with persistent memory.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install mem0ai openai zod
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
Save your Mem0 and OpenAI API keys in a `.env` file:
|
||||
|
||||
```
|
||||
MEM0_API_KEY=your_mem0_api_key
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
```
|
||||
|
||||
Get your Mem0 API key from the [Mem0 Dashboard](https://app.mem0.ai/dashboard/api-keys).
|
||||
|
||||
### Configuration
|
||||
|
||||
```javascript
|
||||
const mem0Config = {
|
||||
apiKey: process.env.MEM0_API_KEY,
|
||||
user_id: "sample-user",
|
||||
};
|
||||
|
||||
const openAIClient = new OpenAI();
|
||||
const mem0Client = new MemoryClient(mem0Config);
|
||||
```
|
||||
|
||||
### Adding Memories
|
||||
|
||||
Store user preferences, past interactions, or any relevant information:
|
||||
<CodeGroup>
|
||||
```javascript JavaScript
|
||||
async function addUserPreferences() {
|
||||
const mem0Client = new MemoryClient(mem0Config);
|
||||
|
||||
const userPreferences = "I Love BMW, Audi and Porsche. I Hate Mercedes. I love Red cars and Maroon cars. I have a budget of 120K to 150K USD. I like Audi the most.";
|
||||
|
||||
await mem0Client.add([{
|
||||
role: "user",
|
||||
content: userPreferences,
|
||||
}], mem0Config);
|
||||
}
|
||||
|
||||
await addUserPreferences();
|
||||
```
|
||||
|
||||
```json Output (Memories)
|
||||
[
|
||||
{
|
||||
"id": "ff9f3367-9e83-415d-b9c5-dc8befd9a4b4",
|
||||
"data": { "memory": "Loves BMW, Audi, and Porsche" },
|
||||
"event": "ADD"
|
||||
},
|
||||
{
|
||||
"id": "04172ce6-3d7b-45a3-b4a1-ee9798593cb4",
|
||||
"data": { "memory": "Hates Mercedes" },
|
||||
"event": "ADD"
|
||||
},
|
||||
{
|
||||
"id": "db363a5d-d258-4953-9e4c-777c120de34d",
|
||||
"data": { "memory": "Loves red cars and maroon cars" },
|
||||
"event": "ADD"
|
||||
},
|
||||
{
|
||||
"id": "5519aaad-a2ac-4c0d-81d7-0d55c6ecdba8",
|
||||
"data": { "memory": "Has a budget of 120K to 150K USD" },
|
||||
"event": "ADD"
|
||||
},
|
||||
{
|
||||
"id": "523b7693-7344-4563-922f-5db08edc8634",
|
||||
"data": { "memory": "Likes Audi the most" },
|
||||
"event": "ADD"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
### Retrieving Memories
|
||||
|
||||
Search for relevant memories based on the current user input:
|
||||
|
||||
```javascript
|
||||
const relevantMemories = await mem0Client.search(userInput, mem0Config);
|
||||
```
|
||||
|
||||
### Structured Responses with Zod
|
||||
|
||||
Define structured response schemas to get consistent output formats:
|
||||
|
||||
```javascript
|
||||
// Define the schema for a car recommendation
|
||||
const CarSchema = z.object({
|
||||
car_name: z.string(),
|
||||
car_price: z.string(),
|
||||
car_url: z.string(),
|
||||
car_image: z.string(),
|
||||
car_description: z.string(),
|
||||
});
|
||||
|
||||
// Schema for a list of car recommendations
|
||||
const Cars = z.object({
|
||||
cars: z.array(CarSchema),
|
||||
});
|
||||
|
||||
// Create a function tool based on the schema
|
||||
const carRecommendationTool = zodResponsesFunction({
|
||||
name: "carRecommendations",
|
||||
parameters: Cars
|
||||
});
|
||||
|
||||
// Use the tool in your OpenAI request
|
||||
const response = await openAIClient.responses.create({
|
||||
model: "gpt-4o",
|
||||
tools: [{ type: "web_search_preview" }, carRecommendationTool],
|
||||
input: `${getMemoryString(relevantMemories)}\n${userInput}`,
|
||||
});
|
||||
```
|
||||
|
||||
### Using Web Search
|
||||
|
||||
Combine memory with web search for up-to-date recommendations:
|
||||
|
||||
```javascript
|
||||
const response = await openAIClient.responses.create({
|
||||
model: "gpt-4o",
|
||||
tools: [{ type: "web_search_preview" }, carRecommendationTool],
|
||||
input: `${getMemoryString(relevantMemories)}\n${userInput}`,
|
||||
});
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Complete Car Recommendation System
|
||||
|
||||
```javascript
|
||||
import MemoryClient from "mem0ai";
|
||||
import { OpenAI } from "openai";
|
||||
import { zodResponsesFunction } from "openai/helpers/zod";
|
||||
import { z } from "zod";
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const mem0Config = {
|
||||
apiKey: process.env.MEM0_API_KEY,
|
||||
user_id: "sample-user",
|
||||
};
|
||||
|
||||
async function run() {
|
||||
// Responses without memories
|
||||
console.log("\n\nRESPONSES WITHOUT MEMORIES\n\n");
|
||||
await main();
|
||||
|
||||
// Adding sample memories
|
||||
await addSampleMemories();
|
||||
|
||||
// Responses with memories
|
||||
console.log("\n\nRESPONSES WITH MEMORIES\n\n");
|
||||
await main(true);
|
||||
}
|
||||
|
||||
// OpenAI Response Schema
|
||||
const CarSchema = z.object({
|
||||
car_name: z.string(),
|
||||
car_price: z.string(),
|
||||
car_url: z.string(),
|
||||
car_image: z.string(),
|
||||
car_description: z.string(),
|
||||
});
|
||||
|
||||
const Cars = z.object({
|
||||
cars: z.array(CarSchema),
|
||||
});
|
||||
|
||||
async function main(memory = false) {
|
||||
const openAIClient = new OpenAI();
|
||||
const mem0Client = new MemoryClient(mem0Config);
|
||||
|
||||
const input = "Suggest me some cars that I can buy today.";
|
||||
|
||||
const tool = zodResponsesFunction({ name: "carRecommendations", parameters: Cars });
|
||||
|
||||
// Store the user input as a memory
|
||||
await mem0Client.add([{
|
||||
role: "user",
|
||||
content: input,
|
||||
}], mem0Config);
|
||||
|
||||
// Search for relevant memories
|
||||
let relevantMemories = []
|
||||
if (memory) {
|
||||
relevantMemories = await mem0Client.search(input, mem0Config);
|
||||
}
|
||||
|
||||
const response = await openAIClient.responses.create({
|
||||
model: "gpt-4o",
|
||||
tools: [{ type: "web_search_preview" }, tool],
|
||||
input: `${getMemoryString(relevantMemories)}\n${input}`,
|
||||
});
|
||||
|
||||
console.log(response.output);
|
||||
}
|
||||
|
||||
async function addSampleMemories() {
|
||||
const mem0Client = new MemoryClient(mem0Config);
|
||||
|
||||
const myInterests = "I Love BMW, Audi and Porsche. I Hate Mercedes. I love Red cars and Maroon cars. I have a budget of 120K to 150K USD. I like Audi the most.";
|
||||
|
||||
await mem0Client.add([{
|
||||
role: "user",
|
||||
content: myInterests,
|
||||
}], mem0Config);
|
||||
}
|
||||
|
||||
const getMemoryString = (memories) => {
|
||||
const MEMORY_STRING_PREFIX = "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 memoryString = memories.map((mem) => `${mem.memory}`).join("\n") ?? "";
|
||||
return memoryString.length > 0 ? `${MEMORY_STRING_PREFIX}${memoryString}` : "";
|
||||
};
|
||||
|
||||
run().catch(console.error);
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
<CodeGroup>
|
||||
```json Without Memories
|
||||
{
|
||||
"cars": [
|
||||
{
|
||||
"car_name": "Toyota Camry",
|
||||
"car_price": "$25,000",
|
||||
"car_url": "https://www.toyota.com/camry/",
|
||||
"car_image": "https://link-to-toyota-camry-image.com",
|
||||
"car_description": "Reliable mid-size sedan with great fuel efficiency."
|
||||
},
|
||||
{
|
||||
"car_name": "Honda Accord",
|
||||
"car_price": "$26,000",
|
||||
"car_url": "https://www.honda.com/accord/",
|
||||
"car_image": "https://link-to-honda-accord-image.com",
|
||||
"car_description": "Comfortable and spacious with advanced safety features."
|
||||
},
|
||||
{
|
||||
"car_name": "Ford Mustang",
|
||||
"car_price": "$28,000",
|
||||
"car_url": "https://www.ford.com/mustang/",
|
||||
"car_image": "https://link-to-ford-mustang-image.com",
|
||||
"car_description": "Iconic sports car with powerful engine options."
|
||||
},
|
||||
{
|
||||
"car_name": "Tesla Model 3",
|
||||
"car_price": "$38,000",
|
||||
"car_url": "https://www.tesla.com/model3",
|
||||
"car_image": "https://link-to-tesla-model3-image.com",
|
||||
"car_description": "Electric vehicle with advanced technology and long range."
|
||||
},
|
||||
{
|
||||
"car_name": "Chevrolet Equinox",
|
||||
"car_price": "$24,000",
|
||||
"car_url": "https://www.chevrolet.com/equinox/",
|
||||
"car_image": "https://link-to-chevron-equinox-image.com",
|
||||
"car_description": "Compact SUV with a spacious interior and user-friendly technology."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json With Memories
|
||||
{
|
||||
"cars": [
|
||||
{
|
||||
"car_name": "Audi RS7",
|
||||
"car_price": "$118,500",
|
||||
"car_url": "https://www.audiusa.com/us/web/en/models/rs7/2023/overview.html",
|
||||
"car_image": "https://www.audiusa.com/content/dam/nemo/us/models/rs7/my23/gallery/1920x1080_AOZ_A717_191004.jpg",
|
||||
"car_description": "The Audi RS7 is a high-performance hatchback with a sleek design, powerful 591-hp twin-turbo V8, and luxurious interior. It's available in various colors including red."
|
||||
},
|
||||
{
|
||||
"car_name": "Porsche Panamera GTS",
|
||||
"car_price": "$129,300",
|
||||
"car_url": "https://www.porsche.com/usa/models/panamera/panamera-models/panamera-gts/",
|
||||
"car_image": "https://files.porsche.com/filestore/image/multimedia/noneporsche-panamera-gts-sample-m02-high/normal/8a6327c3-6c7f-4c6f-a9a8-fb9f58b21795;sP;twebp/porsche-normal.webp",
|
||||
"car_description": "The Porsche Panamera GTS is a luxury sports sedan with a 473-hp V8 engine, exquisite handling, and available in stunning red. Balances sportiness and comfort."
|
||||
},
|
||||
{
|
||||
"car_name": "BMW M5",
|
||||
"car_price": "$105,500",
|
||||
"car_url": "https://www.bmwusa.com/vehicles/m-models/m5/sedan/overview.html",
|
||||
"car_image": "https://www.bmwusa.com/content/dam/bmwusa/M/m5/2023/bmw-my23-m5-sapphire-black-twilight-purple-exterior-02.jpg",
|
||||
"car_description": "The BMW M5 is a powerhouse sedan with a 600-hp V8 engine, known for its great handling and luxury. It comes in several distinctive colors including maroon."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Resources
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 Dashboard](https://app.mem0.ai/dashboard)
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
- [OpenAI Documentation](https://platform.openai.com/docs)
|
||||
@@ -55,7 +55,23 @@ 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.
|
||||
</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.
|
||||
</Card>
|
||||
|
||||
<Card title="Mem0 as an Agentic Tool" icon="robot" href="/examples/mem0-agentic-tool">
|
||||
Integrate Mem0's memory capabilities with OpenAI's Agents SDK to create AI agents with persistent memory.
|
||||
</Card>
|
||||
|
||||
<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.
|
||||
</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>
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
title: Contextual Add (ADD v2)
|
||||
icon: "square-plus"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
Mem0 now supports an contextual add version (v2). To use it, set `version="v2"` during the add call. The default version is v1, which is deprecated now. We recommend migrating to `v2` for new applications.
|
||||
|
||||
## Key Differences Between v1 and v2
|
||||
|
||||
### Version 1 (Legacy)
|
||||
In v1 (default), users needed to pass either the entire conversation history or past k messages with each new message to generate properly contextualized memories. This approach required:
|
||||
|
||||
- Manually tracking and sending previous messages using a sliding window approach
|
||||
- Increased payload sizes as conversations grew longer, requiring careful window size management
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# First interaction
|
||||
messages1 = [
|
||||
{"role": "user", "content": "Hi, I'm Alex and I live in San Francisco."},
|
||||
{"role": "assistant", "content": "Hello Alex! Nice to meet you. San Francisco is a beautiful city."}
|
||||
]
|
||||
client.add(messages1, user_id="alex")
|
||||
|
||||
# Second interaction - must include previous messages for context
|
||||
messages2 = [
|
||||
{"role": "user", "content": "Hi, I'm Alex and I live in San Francisco."},
|
||||
{"role": "assistant", "content": "Hello Alex! Nice to meet you. San Francisco is a beautiful city."},
|
||||
{"role": "user", "content": "I like to eat sushi, and yesterday I went to Sunnyvale to eat sushi with my friends."},
|
||||
{"role": "assistant", "content": "Sushi is really a tasty choice. What did you do this weekend?"}
|
||||
]
|
||||
client.add(messages2, user_id="alex")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// First interaction
|
||||
const messages1 = [
|
||||
{"role": "user", "content": "Hi, I'm Alex and I live in San Francisco."},
|
||||
{"role": "assistant", "content": "Hello Alex! Nice to meet you. San Francisco is a beautiful city."}
|
||||
];
|
||||
client.add(messages1, { user_id: "alex" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Second interaction - must include previous messages for context
|
||||
const messages2 = [
|
||||
{"role": "user", "content": "Hi, I'm Alex and I live in San Francisco."},
|
||||
{"role": "assistant", "content": "Hello Alex! Nice to meet you. San Francisco is a beautiful city."},
|
||||
{"role": "user", "content": "I like to eat sushi, and yesterday I went to Sunnyvale to eat sushi with my friends."},
|
||||
{"role": "assistant", "content": "Sushi is really a tasty choice. What did you do this weekend?"}
|
||||
];
|
||||
client.add(messages2, { user_id: "alex" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Version 2 (Recommended)
|
||||
In v2, Mem0 automatically manages conversation context. Users only need to send new messages, and the system will:
|
||||
|
||||
- Automatically retrieve relevant conversation history
|
||||
- Generate properly contextualized memories
|
||||
- Reduce payload sizes and simplify integration
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# First interaction
|
||||
messages1 = [
|
||||
{"role": "user", "content": "Hi, I'm Alex and I live in San Francisco."},
|
||||
{"role": "assistant", "content": "Hello Alex! Nice to meet you. San Francisco is a beautiful city."}
|
||||
]
|
||||
client.add(messages1, user_id="alex", version="v2")
|
||||
|
||||
# Second interaction - only need to send new messages
|
||||
messages2 = [
|
||||
{"role": "user", "content": "I like to eat sushi, and yesterday I went to Sunnyvale to eat sushi with my friends."},
|
||||
{"role": "assistant", "content": "Sushi is really a tasty choice. What did you do this weekend?"}
|
||||
]
|
||||
client.add(messages2, user_id="alex", version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// First interaction
|
||||
const messages1 = [
|
||||
{"role": "user", "content": "Hi, I'm Alex and I live in San Francisco."},
|
||||
{"role": "assistant", "content": "Hello Alex! Nice to meet you. San Francisco is a beautiful city."}
|
||||
];
|
||||
client.add(messages1, { user_id: "alex", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Second interaction - only need to send new messages
|
||||
const messages2 = [
|
||||
{"role": "user", "content": "I like to eat sushi, and yesterday I went to Sunnyvale to eat sushi with my friends."},
|
||||
{"role": "assistant", "content": "Sushi is really a tasty choice. What did you do this weekend?"}
|
||||
];
|
||||
client.add(messages2, { user_id: "alex", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Benefits of Using v2
|
||||
|
||||
1. **Simplified Integration**: No need to track and manage conversation history
|
||||
2. **Reduced Payload Size**: Only send new messages, not the entire conversation
|
||||
3. **Improved Memory Quality**: Automatic context retrieval ensures better memory generation
|
||||
|
||||
## Understanding ID Parameters in v2
|
||||
|
||||
When using contextual add v2, you have different options for how to organize and retrieve memories:
|
||||
|
||||
### Using Only `user_id`
|
||||
|
||||
When you provide only a `user_id`:
|
||||
|
||||
- Memories are associated with this user's long-term memory store
|
||||
- The system will automatically retrieve relevant context from all of the user's previous conversations
|
||||
- These memories persist indefinitely across all of the user's sessions
|
||||
- Ideal for maintaining persistent user information (preferences, personal details, etc.)
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Adding to long-term user memory
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm allergic to peanuts and shellfish."},
|
||||
{"role": "assistant", "content": "I've noted your allergies to peanuts and shellfish."}
|
||||
]
|
||||
client.add(messages, user_id="alex", version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Adding to long-term user memory
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm allergic to peanuts and shellfish."},
|
||||
{"role": "assistant", "content": "I've noted your allergies to peanuts and shellfish."}
|
||||
];
|
||||
client.add(messages, { user_id: "alex", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Using `user_id` with `run_id`
|
||||
|
||||
When you provide both `user_id` and `run_id`:
|
||||
|
||||
- Memories are associated with a specific conversation session or interaction
|
||||
- The system will retrieve context primarily from this specific session
|
||||
- These memories are still tied to the user but are organized by the specific session
|
||||
- Ideal for maintaining context within a specific conversation flow or task
|
||||
- Helps prevent context from different conversations from interfering with each other
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Adding to a specific conversation session
|
||||
messages = [
|
||||
{"role": "user", "content": "For this trip to Paris, I want to focus on art museums."},
|
||||
{"role": "assistant", "content": "Great! I'll help you plan your Paris trip with a focus on art museums."}
|
||||
]
|
||||
client.add(messages, user_id="alex", run_id="paris-trip-2024", version="v2")
|
||||
|
||||
# Later in the same conversation session
|
||||
messages2 = [
|
||||
{"role": "user", "content": "I'd like to visit the Louvre on Monday."},
|
||||
{"role": "assistant", "content": "The Louvre is a great choice for Monday. Would you like information about opening hours?"}
|
||||
]
|
||||
client.add(messages2, user_id="alex", run_id="paris-trip-2024", version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Adding to a specific conversation session
|
||||
const messages = [
|
||||
{"role": "user", "content": "For this trip to Paris, I want to focus on art museums."},
|
||||
{"role": "assistant", "content": "Great! I'll help you plan your Paris trip with a focus on art museums."}
|
||||
];
|
||||
client.add(messages, { user_id: "alex", run_id: "paris-trip-2024", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Later in the same conversation session
|
||||
const messages2 = [
|
||||
{"role": "user", "content": "I'd like to visit the Louvre on Monday."},
|
||||
{"role": "assistant", "content": "The Louvre is a great choice for Monday. Would you like information about opening hours?"}
|
||||
];
|
||||
client.add(messages2, { user_id: "alex", run_id: "paris-trip-2024", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Using `run_id` helps you organize memories into logical sessions or tasks, making it easier to maintain context for specific interactions while still associating everything with the user's overall profile.
|
||||
|
||||
If you have any questions, please feel free to reach out to us using one of the following methods:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
+11
-11
@@ -1,25 +1,25 @@
|
||||
---
|
||||
title: Custom Prompts
|
||||
description: 'Enhance your product experience by adding custom prompts tailored to your needs'
|
||||
title: Custom Fact Extraction Prompt
|
||||
description: 'Enhance your product experience by adding custom fact extraction prompt tailored to your needs'
|
||||
icon: "pencil"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
## Introduction to Custom Prompts
|
||||
## Introduction to Custom Fact Extraction Prompt
|
||||
|
||||
Custom prompts allow you to tailor the behavior of your Mem0 instance to specific use cases or domains.
|
||||
By defining a custom prompt, you can control how information is extracted, processed, and stored in your memory system.
|
||||
Custom fact extraction prompt allow you to tailor the behavior of your Mem0 instance to specific use cases or domains.
|
||||
By defining it, you can control how information is extracted from the user's message.
|
||||
|
||||
To create an effective custom prompt:
|
||||
To create an effective custom fact extraction prompt:
|
||||
1. Be specific about the information to extract.
|
||||
2. Provide few-shot examples to guide the LLM.
|
||||
3. Ensure examples follow the format shown below.
|
||||
|
||||
Example of a custom prompt:
|
||||
Example of a custom fact extraction prompt:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
custom_prompt = """
|
||||
custom_fact_extraction_prompt = """
|
||||
Please only extract entities containing customer support information, order details, and user information.
|
||||
Here are some few shot examples:
|
||||
|
||||
@@ -67,7 +67,7 @@ Return the facts and customer information in a json format as shown above.
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Here we initialize the custom prompt in the config:
|
||||
Here we initialize the custom fact extraction prompt in the config:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
@@ -82,7 +82,7 @@ config = {
|
||||
"max_tokens": 2000,
|
||||
}
|
||||
},
|
||||
"custom_prompt": custom_prompt,
|
||||
"custom_fact_extraction_prompt": custom_fact_extraction_prompt,
|
||||
"version": "v1.1"
|
||||
}
|
||||
|
||||
@@ -166,4 +166,4 @@ await memory.add('I like going to hikes', { userId: "user123" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The custom prompt will process both the user and assistant messages to extract relevant information according to the defined format.
|
||||
The custom fact extraction prompt will process both the user and assistant messages to extract relevant information according to the defined format.
|
||||
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: Custom Update Memory Prompt
|
||||
icon: "pencil"
|
||||
iconType: "solid"
|
||||
---
|
||||
Update memory prompt is a prompt used to determine the action to be performed on the memory.
|
||||
By customizing this prompt, you can control how the memory is updated.
|
||||
|
||||
|
||||
## Introduction
|
||||
Mem0 memory system compares the newly retrieved facts with the existing memory and determines the action to be performed on the memory.
|
||||
The kinds of actions are:
|
||||
- Add
|
||||
- Add the newly retrieved facts to the memory.
|
||||
- Update
|
||||
- Update the existing memory with the newly retrieved facts.
|
||||
- Delete
|
||||
- Delete the existing memory.
|
||||
- No Change
|
||||
- Do not make any changes to the memory.
|
||||
|
||||
### Example
|
||||
Example of a custom update memory prompt:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
UPDATE_MEMORY_PROMPT = """You are a smart memory manager which controls the memory of a system.
|
||||
You can perform four operations: (1) add into the memory, (2) update the memory, (3) delete from the memory, and (4) no change.
|
||||
|
||||
Based on the above four operations, the memory will change.
|
||||
|
||||
Compare newly retrieved facts with the existing memory. For each new fact, decide whether to:
|
||||
- ADD: Add it to the memory as a new element
|
||||
- UPDATE: Update an existing memory element
|
||||
- DELETE: Delete an existing memory element
|
||||
- NONE: Make no change (if the fact is already present or irrelevant)
|
||||
|
||||
There are specific guidelines to select which operation to perform:
|
||||
|
||||
1. **Add**: If the retrieved facts contain new information not present in the memory, then you have to add it by generating a new ID in the id field.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "User is a software engineer"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Name is John"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "User is a software engineer",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Name is John",
|
||||
"event" : "ADD"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
2. **Update**: If the retrieved facts contain information that is already present in the memory but the information is totally different, then you have to update it.
|
||||
If the retrieved fact contains information that conveys the same thing as the elements present in the memory, then you have to keep the fact which has the most information.
|
||||
Example (a) -- if the memory contains "User likes to play cricket" and the retrieved fact is "Loves to play cricket with friends", then update the memory with the retrieved facts.
|
||||
Example (b) -- if the memory contains "Likes cheese pizza" and the retrieved fact is "Loves cheese pizza", then you do not need to update it because they convey the same information.
|
||||
If the direction is to update the memory, then you have to update it.
|
||||
Please keep in mind while updating you have to keep the same ID.
|
||||
Please note to return the IDs in the output from the input IDs only and do not generate any new ID.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "I really like cheese pizza"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "User is a software engineer"
|
||||
},
|
||||
{
|
||||
"id" : "2",
|
||||
"text" : "User likes to play cricket"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Loves chicken pizza", "Loves to play cricket with friends"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Loves cheese and chicken pizza",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "I really like cheese pizza"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "User is a software engineer",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "2",
|
||||
"text" : "Loves to play cricket with friends",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "User likes to play cricket"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
3. **Delete**: If the retrieved facts contain information that contradicts the information present in the memory, then you have to delete it. Or if the direction is to delete the memory, then you have to delete it.
|
||||
Please note to return the IDs in the output from the input IDs only and do not generate any new ID.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Dislikes cheese pizza"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza",
|
||||
"event" : "DELETE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
4. **No Change**: If the retrieved facts contain information that is already present in the memory, then you do not need to make any changes.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Name is John"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza",
|
||||
"event" : "NONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Output format
|
||||
The prompt needs to guide the output to follow the structure as shown below:
|
||||
<CodeGroup>
|
||||
```json Add
|
||||
{
|
||||
"memory": [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "This information is new",
|
||||
"event" : "ADD"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json Update
|
||||
{
|
||||
"memory": [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "This information replaces the old information",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "Old information"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json Delete
|
||||
{
|
||||
"memory": [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "This information will be deleted",
|
||||
"event" : "DELETE"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json No Change
|
||||
{
|
||||
"memory": [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "No changes for this information",
|
||||
"event" : "NONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
## custom update memory prompt vs custom prompt
|
||||
|
||||
| Feature | `custom_update_memory_prompt` | `custom_prompt` |
|
||||
|---------|-------------------------------|-----------------|
|
||||
| Use case | Determine the action to be performed on the memory | Extract the facts from messages |
|
||||
| Reference | Retrieved facts from messages and old memory | Messages |
|
||||
| Output | Action to be performed on the memory | Extracted facts |
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Feedback Mechanism
|
||||
icon: "thumbs-up"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
Mem0's **Feedback Mechanism** allows you to provide feedback on the memories generated by your application. This feedback is used to improve the accuracy of the memories and the search results.
|
||||
|
||||
## How it works
|
||||
|
||||
The feedback mechanism is a simple API that allows you to provide feedback on the memories generated by your application. The feedback is stored in the database and is used to improve the accuracy of the memories and the search results. Over time, Mem0 continuously learns from this feedback, refining its memory generation and search capabilities for better performance.
|
||||
|
||||
## Give Feedback
|
||||
|
||||
You can give feedback on a memory by calling the `feedback` method on the Mem0 client.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
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.")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import MemoryClient from 'mem0ai';
|
||||
|
||||
const client = new MemoryClient({ apiKey: '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."
|
||||
})
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Feedback Types
|
||||
|
||||
The `feedback` parameter can be one of the following values:
|
||||
|
||||
- `POSITIVE`: The memory is useful.
|
||||
- `NEGATIVE`: The memory is not useful.
|
||||
- `VERY_NEGATIVE`: The memory is not useful at all.
|
||||
|
||||
## Parameters
|
||||
|
||||
The `feedback` method takes the following parameters:
|
||||
|
||||
- `memory_id`: The ID of the memory to give feedback on.
|
||||
- `feedback`: The feedback to give on the memory. (Optional)
|
||||
- `feedback_reason`: The reason for the feedback. (Optional)
|
||||
|
||||
The `feedback_reason` parameter is optional and can be used to provide a reason for the feedback.
|
||||
|
||||
<Note>
|
||||
You can pass `None` or `null` to the `feedback` and `feedback_reason` parameters to remove the feedback for a memory.
|
||||
</Note>
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
---
|
||||
title: Graph Memory
|
||||
icon: "circle-nodes"
|
||||
iconType: "solid"
|
||||
description: "Enable graph-based memory retrieval for more contextually relevant results"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Graph Memory enhances memory pipeline by creating relationships between entities in your data. It builds a network of interconnected information for more contextually relevant search results.
|
||||
|
||||
This feature allows your AI applications to understand connections between entities, providing richer context for responses. It's ideal for applications needing relationship tracking and nuanced information retrieval across related memories.
|
||||
|
||||
## How Graph Memory Works
|
||||
|
||||
The Graph Memory feature analyzes how each entity connects and relates to each other. When enabled:
|
||||
|
||||
1. Mem0 automatically builds a graph representation of entities
|
||||
2. Retrieval considers graph relationships between entities
|
||||
3. Results include entities that may be contextually important even if they're not direct semantic matches
|
||||
|
||||
## Using Graph Memory
|
||||
|
||||
To use Graph Memory, you need to enable it in your API calls by setting the `enable_graph=True` parameter. You'll also need to specify `output_format="v1.1"` to receive the enriched response format.
|
||||
|
||||
### Adding Memories with Graph Memory
|
||||
|
||||
When adding new memories, enable Graph Memory to automatically build relationships with existing memories:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(
|
||||
api_key="your-api-key",
|
||||
org_id="your-org-id",
|
||||
project_id="your-project-id"
|
||||
)
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "My name is Joseph"},
|
||||
{"role": "assistant", "content": "Hello Joseph, it's nice to meet you!"},
|
||||
{"role": "user", "content": "I'm from Seattle and I work as a software engineer"}
|
||||
]
|
||||
|
||||
# Enable graph memory when adding
|
||||
client.add(
|
||||
messages,
|
||||
user_id="joseph",
|
||||
version="v1",
|
||||
enable_graph=True,
|
||||
output_format="v1.1"
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import { MemoryClient } from "mem0";
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: "your-api-key",
|
||||
orgId: "your-org-id",
|
||||
projectId: "your-project-id"
|
||||
});
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "My name is Joseph" },
|
||||
{ role: "assistant", content: "Hello Joseph, it's nice to meet you!" },
|
||||
{ role: "user", content: "I'm from Seattle and I work as a software engineer" }
|
||||
];
|
||||
|
||||
// Enable graph memory when adding
|
||||
await client.add({
|
||||
messages,
|
||||
userId: "joseph",
|
||||
version: "v1",
|
||||
enableGraph: true,
|
||||
outputFormat: "v1.1"
|
||||
});
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"memory": "Name is Joseph",
|
||||
"event": "ADD",
|
||||
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438"
|
||||
},
|
||||
{
|
||||
"memory": "Is from Seattle",
|
||||
"event": "ADD",
|
||||
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d"
|
||||
},
|
||||
{
|
||||
"memory": "Is a software engineer",
|
||||
"event": "ADD",
|
||||
"id": "5f0a184e-ddea-4fe6-9b92-692d6a901df8"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The graph memory would look like this:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/graph-platform.png" alt="Graph Memory Visualization showing relationships between entities" />
|
||||
</Frame>
|
||||
|
||||
<Caption>Graph Memory creates a network of relationships between entities, enabling more contextual retrieval</Caption>
|
||||
|
||||
|
||||
<Note>
|
||||
Response for the graph memory's `add` operation will not be available directly in the response.
|
||||
As adding graph memories is an asynchronous operation due to heavy processing,
|
||||
you can use the `get_all()` endpoint to retrieve the memory with the graph metadata.
|
||||
</Note>
|
||||
|
||||
|
||||
### Searching with Graph Memory
|
||||
|
||||
When searching memories, Graph Memory helps retrieve entities that are contextually important even if they're not direct semantic matches.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Search with graph memory enabled
|
||||
results = client.search(
|
||||
"what is my name?",
|
||||
user_id="joseph",
|
||||
enable_graph=True,
|
||||
output_format="v1.1"
|
||||
)
|
||||
|
||||
print(results)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Search with graph memory enabled
|
||||
const results = await client.search({
|
||||
query: "what is my name?",
|
||||
userId: "joseph",
|
||||
enableGraph: true,
|
||||
outputFormat: "v1.1"
|
||||
});
|
||||
|
||||
console.log(results);
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438",
|
||||
"memory": "Name is Joseph",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.146390-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.146404-07:00",
|
||||
"score": 0.3621795393335552
|
||||
},
|
||||
{
|
||||
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d",
|
||||
"memory": "Is from Seattle",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.170680-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.170692-07:00",
|
||||
"score": 0.31212713194651254
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "name",
|
||||
"target": "joseph",
|
||||
"target_type": "person",
|
||||
"score": 0.39
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Retrieving All Memories with Graph Memory
|
||||
|
||||
When retrieving all memories, Graph Memory provides additional relationship context:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Get all memories with graph context
|
||||
memories = client.get_all(
|
||||
user_id="joseph",
|
||||
enable_graph=True,
|
||||
output_format="v1.1"
|
||||
)
|
||||
|
||||
print(memories)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Get all memories with graph context
|
||||
const memories = await client.getAll({
|
||||
userId: "joseph",
|
||||
enableGraph: true,
|
||||
outputFormat: "v1.1"
|
||||
});
|
||||
|
||||
console.log(memories);
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "5f0a184e-ddea-4fe6-9b92-692d6a901df8",
|
||||
"memory": "Is a software engineer",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["professional_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.194116-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.194128-07:00",
|
||||
},
|
||||
{
|
||||
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d",
|
||||
"memory": "Is from Seattle",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.170680-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.170692-07:00",
|
||||
},
|
||||
{
|
||||
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438",
|
||||
"memory": "Name is Joseph",
|
||||
"user_id": "joseph",
|
||||
"metadata": null,
|
||||
"categories": ["personal_details"],
|
||||
"immutable": false,
|
||||
"created_at": "2025-03-19T09:09:00.146390-07:00",
|
||||
"updated_at": "2025-03-19T09:09:00.146404-07:00",
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "name",
|
||||
"target": "joseph",
|
||||
"target_type": "person"
|
||||
},
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "city",
|
||||
"target": "seattle",
|
||||
"target_type": "city"
|
||||
},
|
||||
{
|
||||
"source": "joseph",
|
||||
"source_type": "person",
|
||||
"relationship": "job",
|
||||
"target": "software engineer",
|
||||
"target_type": "job"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Enable Graph Memory for applications where understanding context and relationships between memories is important
|
||||
- Graph Memory works best with a rich history of related conversations
|
||||
- Consider Graph Memory for long-running assistants that need to track evolving information
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
Graph Memory requires additional processing and may increase response times slightly for very large memory stores. However, for most use cases, the improved retrieval quality outweighs the minimal performance impact.
|
||||
|
||||
If you have any questions, please feel free to reach out to us using one of the following methods:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
|
||||
@@ -71,13 +71,32 @@ Here's an example schema for extracting professional profile information:
|
||||
|
||||
### Submit Export Job
|
||||
|
||||
You can optionally provide additional instructions to guide how memories are processed and structured during export using the `export_instructions` parameter.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Basic export request
|
||||
response = client.create_memory_export(
|
||||
schema=json_schema,
|
||||
user_id="user123"
|
||||
user_id="alice"
|
||||
)
|
||||
|
||||
# Export with custom instructions
|
||||
export_instructions = """
|
||||
1. Create a comprehensive profile with detailed information in each category
|
||||
2. Only mark fields as "None" when absolutely no relevant information exists
|
||||
3. Base all information directly on the user's memories
|
||||
4. When contradictions exist, prioritize the most recent information
|
||||
5. Clearly distinguish between factual statements and inferences
|
||||
"""
|
||||
|
||||
response = client.create_memory_export(
|
||||
schema=json_schema,
|
||||
user_id="alice",
|
||||
export_instructions=export_instructions
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
@@ -87,7 +106,8 @@ curl -X POST "https://api.mem0.ai/v1/memories/export/" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"schema": {json_schema},
|
||||
"user_id": "user123"
|
||||
"user_id": "alice",
|
||||
"export_instructions": "1. Create a comprehensive profile with detailed information\n2. Only mark fields as \"None\" when absolutely no relevant information exists"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -107,12 +127,12 @@ Once the export job is complete, you can retrieve the structured data:
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
response = client.get_memory_export(user_id="user123")
|
||||
response = client.get_memory_export(user_id="alice")
|
||||
print(response)
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/export/?user_id=user123" \
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/export/?user_id=alice" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
```
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
title: Multimodal Support
|
||||
description: Integrate images into your interactions with Mem0
|
||||
description: Integrate images and documents into your interactions with Mem0
|
||||
icon: "image"
|
||||
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
|
||||
|
||||
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>
|
||||
```python Python
|
||||
@@ -90,11 +90,18 @@ await client.add(messages, { user_id: "alice" })
|
||||
```
|
||||
</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.
|
||||
|
||||
@@ -115,7 +122,7 @@ image_message = {
|
||||
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.
|
||||
|
||||
@@ -164,7 +171,134 @@ const imageMessage = {
|
||||
await client.add([imageMessage], { user_id: "alice" })
|
||||
```
|
||||
</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:
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ Learn about the key features and capabilities that make Mem0 a powerful platform
|
||||
<Card title="Advanced Retrieval" icon="magnifying-glass" href="/features/advanced-retrieval">
|
||||
Superior search results using state-of-the-art algorithms, including keyword search, reranking, and filtering capabilities.
|
||||
</Card>
|
||||
<Card title="Contextual Add" icon="square-plus" href="/features/contextual-add">
|
||||
Only send your latest conversation history - we automatically retrieve the rest and generate properly contextualized memories.
|
||||
</Card>
|
||||
<Card title="Multimodal Support" icon="photo-film" href="/features/multimodal-support">
|
||||
Process and analyze various types of content including images.
|
||||
</Card>
|
||||
@@ -33,6 +36,9 @@ Learn about the key features and capabilities that make Mem0 a powerful platform
|
||||
<Card title="Memory Export" icon="file-export" href="/features/memory-export">
|
||||
Export memories in structured formats using customizable Pydantic schemas.
|
||||
</Card>
|
||||
<Card title="Graph Memory" icon="graph" href="/features/graph-memory">
|
||||
Add memories in the form of nodes and edges in a graph database and search for related memories.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Getting Help
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -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" />
|
||||
@@ -95,7 +95,7 @@ add_input = {
|
||||
{"role": "user", "content": "Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts."},
|
||||
{"role": "assistant", "content": "Hello Alex! I've noted that you're a vegetarian and have a nut allergy."}
|
||||
],
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"output_format": "v1.1",
|
||||
"metadata": {"food": "vegan"}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ search_input = {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"created_at": {"gte": "2024-07-20", "lte": "2024-12-10"}},
|
||||
{"user_id": "alex123"}
|
||||
{"user_id": "alex"}
|
||||
]
|
||||
},
|
||||
"version": "v2"
|
||||
@@ -186,7 +186,7 @@ result = search_tool.invoke(search_input)
|
||||
{
|
||||
"id": "1a75e827-7eca-45ea-8c5c-cfd43299f061",
|
||||
"memory": "Name is Alex",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d0fccc8fa47f7a149ee95750c37bb0ca",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
@@ -255,7 +255,7 @@ get_all_input = {
|
||||
"version": "v2",
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"user_id": "alex123"},
|
||||
{"user_id": "alex"},
|
||||
{"created_at": {"gte": "2024-07-01", "lte": "2024-12-31"}}
|
||||
]
|
||||
},
|
||||
@@ -274,7 +274,7 @@ get_all_result = get_all_tool.invoke(get_all_input)
|
||||
{
|
||||
"id": "1a75e827-7eca-45ea-8c5c-cfd43299f061",
|
||||
"memory": "Name is Alex",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d0fccc8fa47f7a149ee95750c37bb0ca",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
@@ -288,7 +288,7 @@ get_all_result = get_all_tool.invoke(get_all_input)
|
||||
{
|
||||
"id": "91509588-0b39-408a-8df3-84b3bce8c521",
|
||||
"memory": "Is a vegetarian",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "ce6b1c84586772ab9995a9477032df99",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
@@ -303,7 +303,7 @@ get_all_result = get_all_tool.invoke(get_all_input)
|
||||
{
|
||||
"id": "8d74f7a0-6107-4589-bd6f-210f6bf4fbbb",
|
||||
"memory": "Is allergic to nuts",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "7873cd0e5a29c513253d9fad038e758b",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
|
||||
@@ -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")
|
||||
```
|
||||
@@ -220,4 +220,49 @@ Here are the available integrations for Mem0:
|
||||
>
|
||||
Integrate Mem0 as an MCP Server in Cursor.
|
||||
</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>
|
||||
|
||||
@@ -301,6 +301,56 @@ await memory.deleteAll({ userId: "alice" });
|
||||
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
|
||||
|
||||
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 |
|
||||
</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">
|
||||
```typescript
|
||||
const config = {
|
||||
@@ -377,7 +435,15 @@ const config = {
|
||||
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.",
|
||||
}
|
||||
```
|
||||
|
||||
@@ -22,7 +22,11 @@ pip install mem0ai
|
||||
<Tabs>
|
||||
<Tab title="Basic">
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||
|
||||
m = Memory()
|
||||
```
|
||||
</Tab>
|
||||
@@ -42,8 +46,11 @@ docker run -p 6333:6333 -p 6334:6334 \
|
||||
Then, instantiate memory with qdrant server:
|
||||
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
@@ -61,8 +68,11 @@ m = Memory.from_config(config)
|
||||
<Tab title="Advanced (Graph Memory)">
|
||||
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||
|
||||
config = {
|
||||
"graph_store": {
|
||||
"provider": "neo4j",
|
||||
@@ -85,14 +95,18 @@ m = Memory.from_config(config_dict=config)
|
||||
|
||||
<CodeGroup>
|
||||
```python Code
|
||||
const messages = [
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
|
||||
# Store inferred memories (default behavior)
|
||||
result = m.add(messages, user_id="alice", metadata={"category": "movie_recommendations"})
|
||||
|
||||
# Store raw messages without inference
|
||||
# result = m.add(messages, user_id="alice", metadata={"category": "movie_recommendations"}, infer=False)
|
||||
```
|
||||
|
||||
```json Output
|
||||
@@ -368,7 +382,8 @@ Mem0 offers extensive configuration options to customize its behavior according
|
||||
|------------------|--------------------------------------|----------------------------|
|
||||
| `history_db_path` | Path to the history database | "{mem0_dir}/history.db" |
|
||||
| `version` | API version | "v1.1" |
|
||||
| `custom_prompt` | Custom prompt for memory processing | None |
|
||||
| `custom_fact_extraction_prompt` | Custom prompt for memory processing | None |
|
||||
| `custom_update_memory_prompt` | Custom prompt for update memory | None |
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Complete Configuration Example">
|
||||
@@ -405,7 +420,8 @@ config = {
|
||||
},
|
||||
"history_db_path": "/path/to/history.db",
|
||||
"version": "v1.1",
|
||||
"custom_prompt": "Optional custom prompt for memory processing"
|
||||
"custom_fact_extraction_prompt": "Optional custom prompt for fact extraction for memory",
|
||||
"custom_update_memory_prompt": "Optional custom prompt for update memory"
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
+180
-46
@@ -509,33 +509,33 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"tags": [
|
||||
"exports"
|
||||
],
|
||||
],
|
||||
"summary": "Create an export job with schema",
|
||||
"description": "Create a structured export of memories based on a provided schema.",
|
||||
"operationId": "exports_create",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["schema"],
|
||||
"properties": {
|
||||
"properties": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"description": "Schema definition for the export"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"type": "string",
|
||||
"description": "Filter exports by user ID"
|
||||
},
|
||||
},
|
||||
"run_id": {
|
||||
"type": "string",
|
||||
"type": "string",
|
||||
"description": "Filter exports by run ID"
|
||||
},
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"type": "string",
|
||||
"description": "Filter exports by session ID"
|
||||
},
|
||||
"app_id": {
|
||||
@@ -549,28 +549,28 @@
|
||||
"project_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by project ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Export created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Memory export request received. The export will be ready in a few seconds."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
},
|
||||
@@ -587,15 +587,15 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"type": "string",
|
||||
"example": "Schema is required and must be a valid object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-code-samples": [
|
||||
{
|
||||
"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();"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/memories/": {
|
||||
"get": {
|
||||
@@ -783,6 +783,14 @@
|
||||
"title": "Immutable",
|
||||
"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": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -932,27 +940,27 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmessages = [\n {\"role\": \"user\", \"content\": \"<user-message>\"},\n {\"role\": \"assistant\", \"content\": \"<assistant-response>\"}\n]\n\nclient.add(messages, user_id=\"<user-id>\")"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmessages = [\n {\"role\": \"user\", \"content\": \"<user-message>\"},\n {\"role\": \"assistant\", \"content\": \"<assistant-response>\"}\n]\n\nclient.add(messages, user_id=\"<user-id>\", version=\"v2\")"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst messages = [\n { role: \"user\", content: \"Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts.\" },\n { role: \"assistant\", content: \"Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions.\" }\n];\n\nclient.add(messages, { user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst messages = [\n { role: \"user\", content: \"Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts.\" },\n { role: \"assistant\", content: \"Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions.\" }\n];\n\nclient.add(messages, { user_id: \"<user_id>\", version: \"v2\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request POST \\\n --url https://api.mem0.ai/v1/memories/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"messages\": [\n {}\n ],\n \"agent_id\": \"<string>\",\n \"user_id\": \"<string>\",\n \"app_id\": \"<string>\",\n \"run_id\": \"<string>\",\n \"metadata\": {},\n \"includes\": \"<string>\",\n \"excludes\": \"<string>\",\n \"infer\": true,\n \"custom_categories\": {}, \n \"org_id\": \"<string>\",\n \"project_id\": \"<string>\"\n}'"
|
||||
"source": "curl --request POST \\\n --url https://api.mem0.ai/v1/memories/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"messages\": [\n {}\n ],\n \"agent_id\": \"<string>\",\n \"user_id\": \"<string>\",\n \"app_id\": \"<string>\",\n \"run_id\": \"<string>\",\n \"metadata\": {},\n \"includes\": \"<string>\",\n \"excludes\": \"<string>\",\n \"infer\": true,\n \"custom_categories\": {}, \n \"org_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"version\": \"v2\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/v1/memories/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"messages\\\": [\n {}\n ],\n \\\"agent_id\\\": \\\"<string>\\\",\n \\\"user_id\\\": \\\"<string>\\\",\n \\\"app_id\\\": \\\"<string>\\\",\n \\\"run_id\\\": \\\"<string>\\\",\n \\\"metadata\\\": {},\n \\\"includes\\\": \\\"<string>\\\",\n \\\"excludes\\\": \\\"<string>\\\",\n \\\"infer\\\": true,\n \\\"custom_categories\\\": {},\n \\\"org_id\\\": \\\"<string>\\\",\n \\\"project_id\\\": \\\"<string>\"\n}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/v1/memories/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"messages\\\": [\n {}\n ],\n \\\"agent_id\\\": \\\"<string>\\\",\n \\\"user_id\\\": \\\"<string>\\\",\n \\\"app_id\\\": \\\"<string>\\\",\n \\\"run_id\\\": \\\"<string>\\\",\n \\\"metadata\\\": {},\n \\\"includes\\\": \\\"<string>\\\",\n \\\"excludes\\\": \\\"<string>\\\",\n \\\"infer\\\": true,\n \\\"custom_categories\\\": {},\n \\\"org_id\\\": \\\"<string>\\\",\n \\\"project_id\\\": \\\"<string>\",\n \\\"version\\\": \"v2\"\n}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/memories/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => \"{\n \\\"messages\\\": [\n {}\n ],\n \\\"agent_id\\\": \\\"<string>\\\",\n \\\"user_id\\\": \\\"<string>\\\",\n \\\"app_id\\\": \\\"<string>\\\",\n \\\"run_id\\\": \\\"<string>\\\",\n \\\"metadata\\\": {},\n \\\"includes\\\": \\\"<string>\\\",\n \\\"excludes\\\": \\\"<string>\\\",\n \\\"infer\\\": true,\n \\\"custom_categories\\\": {}, \n \\\"org_id\\\": \\\"<string>\\\",\n \\\"project_id\\\": \\\"<string>\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/memories/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => \"{\n \\\"messages\\\": [\n {}\n ],\n \\\"agent_id\\\": \\\"<string>\\\",\n \\\"user_id\\\": \\\"<string>\\\",\n \\\"app_id\\\": \\\"<string>\\\",\n \\\"run_id\\\": \\\"<string>\\\",\n \\\"metadata\\\": {},\n \\\"includes\\\": \\\"<string>\\\",\n \\\"excludes\\\": \\\"<string>\\\",\n \\\"infer\\\": true,\n \\\"custom_categories\\\": {}, \n \\\"org_id\\\": \\\"<string>\\\",\n \\\"project_id\\\": \\\"<string>\",\n \\\"version\\\": \"v2\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "HttpResponse<String> response = Unirest.post(\"https://api.mem0.ai/v1/memories/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"messages\\\": [\n {}\n ],\n \\\"agent_id\\\": \\\"<string>\\\",\n \\\"user_id\\\": \\\"<string>\\\",\n \\\"app_id\\\": \\\"<string>\\\",\n \\\"run_id\\\": \\\"<string>\\\",\n \\\"metadata\\\": {},\n \\\"includes\\\": \\\"<string>\\\",\n \\\"excludes\\\": \\\"<string>\\\",\n \\\"infer\\\": true,\n \\\"custom_categories\\\": {}, \n \\\"org_id\\\": \\\"<string>\\\",\n \\\"project_id\\\": \\\"<string>\"\n}\")\n .asString();"
|
||||
"source": "HttpResponse<String> response = Unirest.post(\"https://api.mem0.ai/v1/memories/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"messages\\\": [\n {}\n ],\n \\\"agent_id\\\": \\\"<string>\\\",\n \\\"user_id\\\": \\\"<string>\\\",\n \\\"app_id\\\": \\\"<string>\\\",\n \\\"run_id\\\": \\\"<string>\\\",\n \\\"metadata\\\": {},\n \\\"includes\\\": \\\"<string>\\\",\n \\\"excludes\\\": \\\"<string>\\\",\n \\\"infer\\\": true,\n \\\"custom_categories\\\": {}, \n \\\"org_id\\\": \\\"<string>\\\",\n \\\"project_id\\\": \\\"<string>\",\n \\\"version\\\": \"v2\"\n}\")\n .asString();"
|
||||
}
|
||||
],
|
||||
"x-codegen-request-body-name": "data"
|
||||
@@ -1183,6 +1191,14 @@
|
||||
"title": "Immutable",
|
||||
"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": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1324,6 +1340,14 @@
|
||||
"title": "Immutable",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@@ -1451,6 +1475,14 @@
|
||||
"title": "Immutable",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@@ -1761,7 +1793,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@@ -2074,6 +2106,95 @@
|
||||
"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/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3112,7 +3233,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"type": "string",
|
||||
"example": "Unauthorized to create projects in this organization."
|
||||
}
|
||||
}
|
||||
@@ -3478,7 +3599,7 @@
|
||||
"description": "Unauthorized to modify this project",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -4798,6 +4919,13 @@
|
||||
"type": "boolean",
|
||||
"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": {
|
||||
"description": "The unique identifier of the organization associated with this memory.",
|
||||
"title": "Organization id",
|
||||
@@ -4809,6 +4937,12 @@
|
||||
"title": "Project id",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"version": {
|
||||
"description": "The version of the memory to use. The default version is v1, which is deprecated. We recommend using v2 for new applications.",
|
||||
"title": "Version",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+167
-76
@@ -87,10 +87,10 @@ messages = [
|
||||
]
|
||||
|
||||
# The default output_format is v1.0
|
||||
client.add(messages, user_id="alex", output_format="v1.0")
|
||||
client.add(messages, user_id="alex", output_format="v1.0", version="v2")
|
||||
|
||||
# To use the latest output_format, set the output_format parameter to "v1.1"
|
||||
client.add(messages, user_id="alex", output_format="v1.1", metadata={"food": "vegan"})
|
||||
client.add(messages, user_id="alex", output_format="v1.1", metadata={"food": "vegan"}, version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -98,7 +98,7 @@ const messages = [
|
||||
{"role": "user", "content": "Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts."},
|
||||
{"role": "assistant", "content": "Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions."}
|
||||
];
|
||||
client.add(messages, { user_id: "alex", output_format: "v1.1", metadata: { food: "vegan" } })
|
||||
client.add(messages, { user_id: "alex", output_format: "v1.1", metadata: { food: "vegan" }, version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -116,7 +116,8 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
"output_format": "v1.1",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
}
|
||||
},
|
||||
"version": "v2"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -191,10 +192,10 @@ messages = [
|
||||
]
|
||||
|
||||
# The default output_format is v1.0
|
||||
client.add(messages, user_id="alex123", run_id="trip-planning-2024", output_format="v1.0")
|
||||
client.add(messages, user_id="alex", run_id="trip-planning-2024", output_format="v1.0", version="v2")
|
||||
|
||||
# To use the latest output_format, set the output_format parameter to "v1.1"
|
||||
client.add(messages, user_id="alex123", run_id="trip-planning-2024", output_format="v1.1")
|
||||
client.add(messages, user_id="alex", run_id="trip-planning-2024", output_format="v1.1", version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -204,7 +205,7 @@ const messages = [
|
||||
{"role": "user", "content": "Yes, please! Especially in Tokyo."},
|
||||
{"role": "assistant", "content": "Great! I'll remember that you're interested in vegetarian restaurants in Tokyo for your upcoming trip. I'll prepare a list for you in our next interaction."}
|
||||
];
|
||||
client.add(messages, { user_id: "alex123", run_id: "trip-planning-2024", output_format: "v1.1" })
|
||||
client.add(messages, { user_id: "alex", run_id: "trip-planning-2024", output_format: "v1.1", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -220,9 +221,10 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
{"role": "user", "content": "Yes, please! Especially in Tokyo."},
|
||||
{"role": "assistant", "content": "Great! I'll remember that you're interested in vegetarian restaurants in Tokyo for your upcoming trip. I'll prepare a list for you in our next interaction."}
|
||||
],
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"run_id": "trip-planning-2024",
|
||||
"output_format": "v1.1"
|
||||
"output_format": "v1.1",
|
||||
"version": "v2"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -274,10 +276,10 @@ messages = [
|
||||
]
|
||||
|
||||
# The default output_format is v1.0
|
||||
client.add(messages, agent_id="ai-tutor", output_format="v1.0")
|
||||
client.add(messages, agent_id="ai-tutor", output_format="v1.0", version="v2")
|
||||
|
||||
# To use the latest output_format, set the output_format parameter to "v1.1"
|
||||
client.add(messages, agent_id="ai-tutor", output_format="v1.1")
|
||||
client.add(messages, agent_id="ai-tutor", output_format="v1.1", version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -285,7 +287,7 @@ const messages = [
|
||||
{"role": "system", "content": "You are an AI tutor with a personality. Give yourself a name for the user."},
|
||||
{"role": "assistant", "content": "Understood. I'm an AI tutor with a personality. My name is Alice."}
|
||||
];
|
||||
client.add(messages, { agent_id: "ai-tutor", output_format: "v1.1" })
|
||||
client.add(messages, { agent_id: "ai-tutor", output_format: "v1.1", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -300,7 +302,8 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
{"role": "assistant", "content": "Understood. I'm an AI tutor with a personality. My name is Alice."}
|
||||
],
|
||||
"agent_id": "ai-tutor",
|
||||
"output_format": "v1.1"
|
||||
"output_format": "v1.1",
|
||||
"version": "v2"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -368,7 +371,7 @@ messages = [
|
||||
{"role": "assistant", "content": "That's great! I'm going to Dubai next month."},
|
||||
]
|
||||
|
||||
client.add(messages=messages, user_id="user1", agent_id="agent1")
|
||||
client.add(messages=messages, user_id="user1", agent_id="agent1", version="v2")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -377,7 +380,7 @@ const messages = [
|
||||
{"role": "assistant", "content": "That's great! I'm going to Dubai next month."},
|
||||
]
|
||||
|
||||
client.add(messages, { user_id: "user1", agent_id: "agent1" })
|
||||
client.add(messages, { user_id: "user1", agent_id: "agent1", version: "v2" })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -392,7 +395,8 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
{"role": "assistant", "content": "That's great! I'm going to Dubai next month."},
|
||||
],
|
||||
"user_id": "user1",
|
||||
"agent_id": "agent1"
|
||||
"agent_id": "agent1",
|
||||
"version": "v2"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -412,6 +416,89 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
|
||||
</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
|
||||
|
||||
@@ -466,6 +553,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/" \
|
||||
"metadata": {"food": "vegan"},
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
@@ -482,6 +570,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/" \
|
||||
"metadata": {"food": "vegan"},
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
@@ -527,6 +616,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
|
||||
"metadata": {"food": "vegan"},
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
@@ -617,6 +707,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
|
||||
"metadata": null,
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
@@ -682,6 +773,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
|
||||
"metadata": null,
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
@@ -756,6 +848,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
|
||||
"metadata": {"food": "vegan"},
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
@@ -856,6 +949,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
|
||||
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-25T23:57:00.108347-07:00",
|
||||
"updated_at":"2024-07-25T23:57:00.108367-07:00",
|
||||
"categories":None
|
||||
@@ -867,6 +961,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
|
||||
"hash":"35a305373d639b0bffc6c2a3e2eb4244",
|
||||
"metadata":"None",
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:31:03.543759-07:00",
|
||||
"updated_at":"2024-07-26T00:31:03.543778-07:00",
|
||||
"categories":None
|
||||
@@ -890,6 +985,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
|
||||
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-25T23:57:00.108347-07:00",
|
||||
"updated_at":"2024-07-25T23:57:00.108367-07:00",
|
||||
"categories":None
|
||||
@@ -901,6 +997,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&page=1&page_size=50"
|
||||
"hash":"35a305373d639b0bffc6c2a3e2eb4244",
|
||||
"metadata":"None",
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:31:03.543759-07:00",
|
||||
"updated_at":"2024-07-26T00:31:03.543778-07:00",
|
||||
"categories":None
|
||||
@@ -945,6 +1042,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
|
||||
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-25T23:57:00.108347-07:00",
|
||||
"updated_at":"2024-07-25T23:57:00.108367-07:00",
|
||||
"categories":None
|
||||
@@ -956,6 +1054,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
|
||||
"hash":"35a305373d639b0bffc6c2a3e2eb4244",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:31:03.543759-07:00",
|
||||
"updated_at":"2024-07-26T00:31:03.543778-07:00",
|
||||
"categories":None
|
||||
@@ -979,6 +1078,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
|
||||
"hash": "62bc074f56d1f909f1b4c2b639f56f6a",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-25T23:57:00.108347-07:00",
|
||||
"updated_at": "2024-07-25T23:57:00.108367-07:00"
|
||||
},
|
||||
@@ -989,6 +1089,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
|
||||
"hash": "35a305373d639b0bffc6c2a3e2eb4244",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T00:31:03.543759-07:00",
|
||||
"updated_at": "2024-07-26T00:31:03.543778-07:00"
|
||||
}
|
||||
@@ -1004,17 +1105,17 @@ curl -X GET "https://api.mem0.ai/v1/memories/?agent_id=ai-tutor&page=1&page_size
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
short_term_memories = client.get_all(user_id="alex123", run_id="trip-planning-2024", page=1, page_size=50)
|
||||
short_term_memories = client.get_all(user_id="alex", run_id="trip-planning-2024", page=1, page_size=50)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
client.getAll({ user_id: "alex123", run_id: "trip-planning-2024", page: 1, page_size: 50 })
|
||||
client.getAll({ user_id: "alex", run_id: "trip-planning-2024", page: 1, page_size: 50 })
|
||||
.then(memories => console.log(memories))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planning-2024&page=1&page_size=50" \
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&run_id=trip-planning-2024&page=1&page_size=50" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
```
|
||||
|
||||
@@ -1027,10 +1128,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planni
|
||||
{
|
||||
"id":"06d8df63-7bd2-4fad-9acb-60871bcecee0",
|
||||
"memory":"Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id":"alex123",
|
||||
"user_id":"alex",
|
||||
"hash":"d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:25:16.566471-07:00",
|
||||
"updated_at":"2024-07-26T00:25:16.566492-07:00",
|
||||
"categories":None
|
||||
@@ -1038,10 +1140,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planni
|
||||
{
|
||||
"id":"b4229775-d860-4ccb-983f-0f628ca112f5",
|
||||
"memory":"Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id":"alex123",
|
||||
"user_id":"alex",
|
||||
"hash":"d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:33:20.350542-07:00",
|
||||
"updated_at":"2024-07-26T00:33:20.350560-07:00",
|
||||
"categories":None
|
||||
@@ -1049,10 +1152,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planni
|
||||
{
|
||||
"id":"df1aca24-76cf-4b92-9f58-d03857efcb64",
|
||||
"memory":"Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id":"alex123",
|
||||
"user_id":"alex",
|
||||
"hash":"d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:51:09.642275-07:00",
|
||||
"updated_at":"2024-07-26T00:51:09.642295-07:00",
|
||||
"categories":None
|
||||
@@ -1072,10 +1176,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planni
|
||||
{
|
||||
"id": "06d8df63-7bd2-4fad-9acb-60871bcecee0",
|
||||
"memory": "Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T00:25:16.566471-07:00",
|
||||
"updated_at": "2024-07-26T00:25:16.566492-07:00",
|
||||
"categories": ["food_preferences"]
|
||||
@@ -1083,10 +1188,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planni
|
||||
{
|
||||
"id": "b4229775-d860-4ccb-983f-0f628ca112f5",
|
||||
"memory": "Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T00:33:20.350542-07:00",
|
||||
"updated_at": "2024-07-26T00:33:20.350560-07:00",
|
||||
"categories": ["food_preferences"]
|
||||
@@ -1094,10 +1200,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&run_id=trip-planni
|
||||
{
|
||||
"id": "df1aca24-76cf-4b92-9f58-d03857efcb64",
|
||||
"memory": "Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":None,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T00:51:09.642275-07:00",
|
||||
"updated_at": "2024-07-26T00:51:09.642295-07:00",
|
||||
"categories": None
|
||||
@@ -1133,10 +1240,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/582bbe6d-506b-48c6-a4c6-5df3b1e6342
|
||||
{
|
||||
"id":"06d8df63-7bd2-4fad-9acb-60871bcecee0",
|
||||
"memory":"Planning a trip to Japan next month. Interested in vegetarian restaurants in Tokyo.",
|
||||
"user_id":"alex123",
|
||||
"user_id":"alex",
|
||||
"hash":"d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata":"None",
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-26T00:25:16.566471-07:00",
|
||||
"updated_at":"2024-07-26T00:25:16.566492-07:00",
|
||||
"categories": ["travel"]
|
||||
@@ -1152,55 +1260,55 @@ You can filter memories by their categories when using get_all:
|
||||
|
||||
```python Python
|
||||
# Get memories with specific categories
|
||||
memories = client.get_all(user_id="alex123", categories=["likes"])
|
||||
memories = client.get_all(user_id="alex", categories=["likes"])
|
||||
|
||||
# Get memories with multiple categories
|
||||
memories = client.get_all(user_id="alex123", categories=["likes", "food_preferences"])
|
||||
memories = client.get_all(user_id="alex", categories=["likes", "food_preferences"])
|
||||
|
||||
# Custom pagination with categories
|
||||
memories = client.get_all(user_id="alex123", categories=["likes"], page=1, page_size=50)
|
||||
memories = client.get_all(user_id="alex", categories=["likes"], page=1, page_size=50)
|
||||
|
||||
# Get memories with specific keywords
|
||||
memories = client.get_all(user_id="alex123", keywords="to play", page=1, page_size=50)
|
||||
memories = client.get_all(user_id="alex", keywords="to play", page=1, page_size=50)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// Get memories with specific categories
|
||||
client.getAll({ user_id: "alex123", categories: ["likes"] })
|
||||
client.getAll({ user_id: "alex", categories: ["likes"] })
|
||||
.then(memories => console.log(memories))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Get memories with multiple categories
|
||||
client.getAll({ user_id: "alex123", categories: ["likes", "food_preferences"] })
|
||||
client.getAll({ user_id: "alex", categories: ["likes", "food_preferences"] })
|
||||
.then(memories => console.log(memories))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Custom pagination with categories
|
||||
client.getAll({ user_id: "alex123", categories: ["likes"], page: 1, page_size: 50 })
|
||||
client.getAll({ user_id: "alex", categories: ["likes"], page: 1, page_size: 50 })
|
||||
.then(memories => console.log(memories))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Get memories with specific keywords
|
||||
client.getAll({ user_id: "alex123", keywords: "to play", page: 1, page_size: 50 })
|
||||
client.getAll({ user_id: "alex", keywords: "to play", page: 1, page_size: 50 })
|
||||
.then(memories => console.log(memories))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
# Get memories with specific categories
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&categories=likes" \
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&categories=likes" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
|
||||
# Get memories with multiple categories
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&categories=likes,food_preferences" \
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&categories=likes,food_preferences" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
|
||||
# Custom pagination with categories
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&categories=likes&page=1&page_size=50" \
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&categories=likes&page=1&page_size=50" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
|
||||
# Get memories with specific keywords
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&keywords=to play&page=1&page_size=50" \
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&keywords=to play&page=1&page_size=50" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
```
|
||||
|
||||
@@ -1213,10 +1321,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&keywords=to play&p
|
||||
{
|
||||
"id": "06d8df63-7bd2-4fad-9acb-60871bcecee0",
|
||||
"memory": "Likes pizza and pasta",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata": null,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T00:25:16.566471-07:00",
|
||||
"updated_at": "2024-07-26T00:25:16.566492-07:00",
|
||||
"categories": ["likes", "food_preferences"]
|
||||
@@ -1224,10 +1333,11 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex123&keywords=to play&p
|
||||
{
|
||||
"id": "b4229775-d860-4ccb-983f-0f628ca112f5",
|
||||
"memory": "Likes to travel to beach destinations",
|
||||
"user_id": "alex123",
|
||||
"user_id": "alex",
|
||||
"hash": "d2088c936e259f2f5d2d75543d31401c",
|
||||
"metadata": null,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T00:33:20.350542-07:00",
|
||||
"updated_at": "2024-07-26T00:33:20.350560-07:00",
|
||||
"categories": ["likes"]
|
||||
@@ -1355,6 +1465,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
|
||||
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
|
||||
"metadata":null,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-25T23:57:00.108347-07:00",
|
||||
"updated_at":"2024-07-25T23:57:00.108367-07:00",
|
||||
"categories": ["food_preferences"]
|
||||
@@ -1375,6 +1486,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
|
||||
"hash":"62bc074f56d1f909f1b4c2b639f56f6a",
|
||||
"metadata":null,
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at":"2024-07-25T23:57:00.108347-07:00",
|
||||
"updated_at":"2024-07-25T23:57:00.108367-07:00",
|
||||
"categories": ["food_preferences"]
|
||||
@@ -1464,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",
|
||||
"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"]
|
||||
}
|
||||
{
|
||||
"id": "654fee-b411-4afe-b7e5-35789b72c4a5",
|
||||
"memory": "Name: Alex. Vegetarian. Allergic to nuts.",
|
||||
"user_id": "alex",
|
||||
"metadata": {"food": "vegan"},
|
||||
"categories": ["food_preferences"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"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>
|
||||
|
||||
|
||||
### 4.5 Memory History
|
||||
|
||||
Get history of how a memory has changed over time.
|
||||
@@ -1585,7 +1677,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/<memory-id-here>/history/" \
|
||||
],
|
||||
"old_memory":"None",
|
||||
"new_memory":"Turned vegetarian.",
|
||||
"user_id":"alex123456",
|
||||
"user_id":"alex",
|
||||
"event":"ADD",
|
||||
"metadata":"None",
|
||||
"created_at":"2024-07-26T01:02:41.737310-07:00",
|
||||
@@ -1815,8 +1907,7 @@ response = client.batch_update(update_memories)
|
||||
print(response)
|
||||
```
|
||||
```javascript JavaScript
|
||||
const updateMemories = [
|
||||
{"memory_id": "285ed74b-6e05-4043-b16b-3abd5b533496",
|
||||
const updateMemories = [{"memory_id": "285ed74b-6e05-4043-b16b-3abd5b533496",
|
||||
text: "Watches football"
|
||||
},
|
||||
{"memory_id": "2c9bd859-d1b7-4d33-a6b8-94e0147c4f07",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
|
||||
@@ -16,7 +17,18 @@ class OpenAIEmbedder(BaseEmbedder):
|
||||
self.config.model = "text-embedding-ada-002"
|
||||
|
||||
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:
|
||||
raise ValueError("OPENAI_API_KEY or OPENAI_ORGANIZATION environment variables not provided") # noqa:E501
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import warnings
|
||||
from typing import Any, Callable, Dict, Optional, Type, Union
|
||||
|
||||
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
|
||||
@@ -58,7 +59,19 @@ class OpenAILlm(BaseLlm):
|
||||
"model_kwargs": config.model_kwargs or {},
|
||||
}
|
||||
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:
|
||||
kwargs["top_p"] = config.top_p
|
||||
if config.default_headers:
|
||||
|
||||
Generated
+564
-87
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "embedchain"
|
||||
version = "0.1.127"
|
||||
version = "0.1.128"
|
||||
description = "Simplest open source retrieval (RAG) framework"
|
||||
authors = [
|
||||
"Taranjeet Singh <taranjeet@embedchain.ai>",
|
||||
@@ -91,7 +91,7 @@ exclude = '''
|
||||
color = true
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<=3.13"
|
||||
python = ">=3.9,<=3.13.2"
|
||||
python-dotenv = "^1.0.0"
|
||||
langchain = "^0.3.1"
|
||||
requests = "^2.31.0"
|
||||
@@ -139,7 +139,7 @@ alembic = "^1.13.1"
|
||||
langchain-cohere = "^0.3.0"
|
||||
langchain-community = "^0.3.1"
|
||||
langchain-aws = {version = "^0.2.1", optional = true}
|
||||
langsmith = "^0.1.17"
|
||||
langsmith = "^0.3.18"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.3.0"
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Thread } from "@/components/assistant-ui/thread";
|
||||
import { ThreadList } from "@/components/assistant-ui/thread-list";
|
||||
import { useEffect, useState } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Sun, Moon, MessageSquare } from "lucide-react";
|
||||
import { Sun, Moon, AlignJustify } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import ThemeAwareLogo from "@/components/mem0/theme-aware-logo";
|
||||
import Link from "next/link";
|
||||
@@ -63,7 +63,7 @@ export const Assistant = () => {
|
||||
|
||||
return (
|
||||
<AssistantRuntimeProvider runtime={runtime}>
|
||||
<div className={`h-dvh bg-[#f8fafc] dark:bg-zinc-900 text-[#1e293b] ${isDarkMode ? "dark" : ""}`}>
|
||||
<div className={`bg-[#f8fafc] dark:bg-zinc-900 text-[#1e293b] ${isDarkMode ? "dark" : ""}`}>
|
||||
<header className="h-16 border-b border-[#e2e8f0] flex items-center justify-between px-4 sm:px-6 bg-white dark:bg-zinc-900 dark:border-zinc-800 dark:text-white">
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="flex items-center">
|
||||
@@ -71,29 +71,34 @@ export const Assistant = () => {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="text-[#475569] dark:text-zinc-300 md:hidden"
|
||||
>
|
||||
<MessageSquare className="w-10 h-10" />
|
||||
</Button>
|
||||
<AlignJustify size={24} className="md:hidden" />
|
||||
</Button>
|
||||
|
||||
|
||||
<div className="md:flex items-center hidden">
|
||||
<button
|
||||
className="p-2 rounded-full hover:bg-[#eef2ff] dark:hover:bg-zinc-800 text-[#475569] dark:text-zinc-300"
|
||||
onClick={toggleDarkMode}
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{isDarkMode ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
||||
{isDarkMode ? <Sun className="w-6 h-6" /> : <Moon className="w-6 h-6" />}
|
||||
</button>
|
||||
<GithubButton url="https://github.com/mem0ai/mem0/tree/main/examples" />
|
||||
|
||||
<Link href={"https://app.mem0.ai/"} target="_blank" className="py-2 ml-2 px-4 font-semibold dark:bg-zinc-100 dark:hover:bg-zinc-200 bg-zinc-800 text-white rounded-full hover:bg-zinc-900 dark:text-[#475569]">
|
||||
Save Memories
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-[260px_1fr] gap-x-0 h-[calc(100vh-8rem)] md:h-[calc(100vh-4rem)]">
|
||||
<div className="grid grid-cols-1 md:grid-cols-[260px_1fr] gap-x-0 h-[calc(100dvh-4rem)]">
|
||||
<ThreadList onResetUserId={resetUserId} isDarkMode={isDarkMode} />
|
||||
<Thread sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} onResetUserId={resetUserId} isDarkMode={isDarkMode} />
|
||||
<Thread sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} onResetUserId={resetUserId} isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} />
|
||||
</div>
|
||||
</div>
|
||||
</AssistantRuntimeProvider>
|
||||
|
||||
@@ -19,14 +19,14 @@ import {
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import ThemeAwareLogo from "@/components/assistant-ui/theme-aware-logo";
|
||||
import Link from "next/link";
|
||||
// import ThemeAwareLogo from "@/components/assistant-ui/theme-aware-logo";
|
||||
// import Link from "next/link";
|
||||
interface ThreadListProps {
|
||||
onResetUserId?: () => void;
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const ThreadList: FC<ThreadListProps> = ({ onResetUserId, isDarkMode }) => {
|
||||
export const ThreadList: FC<ThreadListProps> = ({ onResetUserId }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -79,13 +79,7 @@ export const ThreadList: FC<ThreadListProps> = ({ onResetUserId, isDarkMode }) =
|
||||
</div>
|
||||
<ThreadListItems />
|
||||
</div>
|
||||
<div>
|
||||
<Link href="https://www.assistant-ui.com/" target="_blank" className="flex justify-center items-center gap-2">
|
||||
<h1 className="text-sm text-[#475569] dark:text-zinc-300 text-center">built using</h1>
|
||||
<ThemeAwareLogo width={24} height={24} isDarkMode={isDarkMode} />
|
||||
<p className="text-md font-bold dark:text-zinc-300">assistant-ui</p>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</ThreadListPrimitive.Root>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,9 +22,12 @@ import {
|
||||
SendHorizontalIcon,
|
||||
ArchiveIcon,
|
||||
PlusIcon,
|
||||
Sun,
|
||||
Moon,
|
||||
SaveIcon,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import { Dispatch, SetStateAction, useState, useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||
@@ -42,14 +45,14 @@ import {
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import GithubButton from "../mem0/github-button";
|
||||
import Link from "next/link";
|
||||
import ThemeAwareLogo from "./theme-aware-logo";
|
||||
|
||||
interface ThreadProps {
|
||||
sidebarOpen: boolean;
|
||||
setSidebarOpen: Dispatch<SetStateAction<boolean>>;
|
||||
onResetUserId?: () => void;
|
||||
isDarkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
export const Thread: FC<ThreadProps> = ({
|
||||
@@ -57,12 +60,14 @@ export const Thread: FC<ThreadProps> = ({
|
||||
setSidebarOpen,
|
||||
onResetUserId,
|
||||
isDarkMode,
|
||||
toggleDarkMode
|
||||
}) => {
|
||||
const [resetDialogOpen, setResetDialogOpen] = useState(false);
|
||||
const composerInputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
return (
|
||||
<ThreadPrimitive.Root
|
||||
className="bg-[#f8fafc] dark:bg-zinc-900 box-border h-full flex flex-col overflow-hidden relative"
|
||||
className="bg-[#f8fafc] dark:bg-zinc-900 box-border flex flex-col overflow-hidden relative h-[calc(100dvh-4rem)] pb-4 md:h-full"
|
||||
style={{
|
||||
["--thread-max-width" as string]: "42rem",
|
||||
}}
|
||||
@@ -78,13 +83,13 @@ export const Thread: FC<ThreadProps> = ({
|
||||
{/* Mobile sidebar drawer */}
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-y-0 left-0 z-40 w-[85%] bg-white dark:bg-zinc-900 transform transition-transform duration-300 ease-in-out md:hidden",
|
||||
"fixed inset-y-0 left-0 z-40 w-[75%] bg-white shadow-lg rounded-r-lg dark:bg-zinc-900 transform transition-transform duration-300 ease-in-out md:hidden",
|
||||
sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||
)}
|
||||
>
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex items-center justify-between border-b dark:text-white border-[#e2e8f0] dark:border-zinc-800 p-4">
|
||||
<h2 className="font-medium">Recent Chats</h2>
|
||||
<h2 className="font-medium">Settings</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
{onResetUserId && (
|
||||
<AlertDialog
|
||||
@@ -141,13 +146,44 @@ export const Thread: FC<ThreadProps> = ({
|
||||
<div className="flex flex-col justify-between items-stretch gap-1.5 h-full dark:text-white">
|
||||
<ThreadListPrimitive.Root className="flex flex-col items-stretch gap-1.5 h-full dark:text-white">
|
||||
<ThreadListPrimitive.New asChild>
|
||||
<div className="flex items-center flex-col gap-2 w-full">
|
||||
<Button
|
||||
className="hover:bg-[#eef2ff] dark:hover:bg-zinc-800 dark:data-[active]:bg-zinc-800 flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start bg-[#4f46e5] text-white dark:bg-[#6366f1]"
|
||||
className="hover:bg-zinc-600 w-full dark:hover:bg-zinc-800 dark:data-[active]:bg-zinc-800 flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start bg-[#4f46e5] text-white dark:bg-[#6366f1]"
|
||||
variant="default"
|
||||
>
|
||||
<PlusIcon className="w-4 h-4" />
|
||||
New Thread
|
||||
</Button>
|
||||
<Button
|
||||
className="hover:bg-zinc-600 w-full dark:hover:bg-zinc-700 dark:data-[active]:bg-zinc-800 flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start bg-zinc-800 text-white"
|
||||
onClick={toggleDarkMode}
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{isDarkMode ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Sun className="w-6 h-6" />
|
||||
<span>Toggle Light Mode</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Moon className="w-6 h-6" />
|
||||
<span>Toggle Dark Mode</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
<GithubButton url="https://github.com/mem0ai/mem0/tree/main/examples" className="w-full rounded-lg h-9 pl-2 text-sm font-semibold bg-zinc-800 dark:border-zinc-800 dark:text-white text-white hover:bg-zinc-900" text="View on Github" />
|
||||
|
||||
<Link
|
||||
href={"https://app.mem0.ai/"}
|
||||
target="_blank"
|
||||
className="py-2 px-4 w-full rounded-lg h-9 pl-3 text-sm font-semibold dark:bg-zinc-800 dark:hover:bg-zinc-700 bg-zinc-800 text-white hover:bg-zinc-900 dark:text-white"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<SaveIcon className="w-4 h-4" />
|
||||
Save Memories
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</ThreadListPrimitive.New>
|
||||
<div className="mt-4 mb-2">
|
||||
<h2 className="text-sm font-medium text-[#475569] dark:text-zinc-300 px-2.5">
|
||||
@@ -156,29 +192,18 @@ export const Thread: FC<ThreadProps> = ({
|
||||
</div>
|
||||
<ThreadListPrimitive.Items components={{ ThreadListItem }} />
|
||||
</ThreadListPrimitive.Root>
|
||||
<div>
|
||||
<Link
|
||||
href="https://www.assistant-ui.com/"
|
||||
target="_blank"
|
||||
className="flex justify-center items-center gap-2"
|
||||
>
|
||||
<h1 className="text-sm text-[#475569] dark:text-zinc-300 text-center">
|
||||
built using
|
||||
</h1>
|
||||
<ThemeAwareLogo width={24} height={24} isDarkMode={isDarkMode} />
|
||||
<p className="text-md font-bold dark:text-zinc-300">
|
||||
assistant-ui
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="flex h-full flex-col items-center px-4 pt-8 justify-end">
|
||||
<ThreadWelcome />
|
||||
<ScrollArea className="flex-1 w-full">
|
||||
<div className="flex h-full flex-col w-full items-center px-4 pt-8 justify-end">
|
||||
<ThreadWelcome
|
||||
composerInputRef={
|
||||
composerInputRef as React.RefObject<HTMLTextAreaElement>
|
||||
}
|
||||
/>
|
||||
|
||||
<ThreadPrimitive.Messages
|
||||
components={{
|
||||
@@ -194,9 +219,13 @@ export const Thread: FC<ThreadProps> = ({
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<div className="sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit px-4 pb-4 mx-auto">
|
||||
<div className="sticky bottom-0 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit px-4 md:pb-4 mx-auto">
|
||||
<ThreadScrollToBottom />
|
||||
<Composer />
|
||||
<Composer
|
||||
composerInputRef={
|
||||
composerInputRef as React.RefObject<HTMLTextAreaElement>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</ThreadPrimitive.Root>
|
||||
);
|
||||
@@ -216,57 +245,74 @@ const ThreadScrollToBottom: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ThreadWelcome: FC = () => {
|
||||
interface ThreadWelcomeProps {
|
||||
composerInputRef: React.RefObject<HTMLTextAreaElement>;
|
||||
}
|
||||
|
||||
const ThreadWelcome: FC<ThreadWelcomeProps> = ({ composerInputRef }) => {
|
||||
return (
|
||||
<ThreadPrimitive.Empty>
|
||||
<div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-start h-[calc(100vh-23rem)] md:h-[calc(100vh-18rem)]">
|
||||
<div className="flex w-full flex-grow flex-col mt-8 md:h-[calc(100vh-15rem)]">
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-start">
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<div className="text-2xl md:text-4xl font-bold text-[#1e293b] dark:text-white mb-2">
|
||||
<div className="text-[2rem] leading-[1] tracking-[-0.02em] md:text-4xl font-bold text-[#1e293b] dark:text-white mb-2 text-center md:w-full w-5/6">
|
||||
Mem0 - ChatGPT with memory
|
||||
</div>
|
||||
<p className="text-center text-sm text-[#1e293b] dark:text-white mb-2 w-3/4">
|
||||
<p className="text-center text-md text-[#1e293b] dark:text-white mb-2 md:w-3/4 w-5/6">
|
||||
A personalized AI chat app powered by Mem0 that remembers your
|
||||
preferences, facts, and memories.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-center mt-16">
|
||||
<p className="mt-4 font-medium text-[#1e293b] dark:text-white">
|
||||
How can I help you today?
|
||||
</p>
|
||||
<ThreadWelcomeSuggestions />
|
||||
<ThreadWelcomeSuggestions composerInputRef={composerInputRef} />
|
||||
</div>
|
||||
</div>
|
||||
</ThreadPrimitive.Empty>
|
||||
);
|
||||
};
|
||||
|
||||
const ThreadWelcomeSuggestions: FC = () => {
|
||||
interface ThreadWelcomeSuggestionsProps {
|
||||
composerInputRef: React.RefObject<HTMLTextAreaElement>;
|
||||
}
|
||||
|
||||
const ThreadWelcomeSuggestions: FC<ThreadWelcomeSuggestionsProps> = ({ composerInputRef }) => {
|
||||
return (
|
||||
<div className="mt-3 flex w-full items-stretch justify-center gap-4 dark:text-white">
|
||||
<div className="mt-3 flex flex-col md:flex-row w-full md:items-stretch justify-center gap-4 dark:text-white items-center">
|
||||
<ThreadPrimitive.Suggestion
|
||||
className="hover:bg-[#eef2ff] dark:hover:bg-zinc-800 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-[2rem] border border-[#e2e8f0] dark:border-zinc-700 p-3 transition-colors ease-in"
|
||||
className="hover:bg-[#eef2ff] w-full dark:hover:bg-zinc-800 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-[2rem] border border-[#e2e8f0] dark:border-zinc-700 p-3 transition-colors ease-in"
|
||||
prompt="I like to travel to "
|
||||
method="replace"
|
||||
onClick={() => {
|
||||
composerInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
||||
Travel
|
||||
</span>
|
||||
</ThreadPrimitive.Suggestion>
|
||||
<ThreadPrimitive.Suggestion
|
||||
className="hover:bg-[#eef2ff] dark:hover:bg-zinc-800 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-[2rem] border border-[#e2e8f0] dark:border-zinc-700 p-3 transition-colors ease-in"
|
||||
className="hover:bg-[#eef2ff] w-full dark:hover:bg-zinc-800 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-[2rem] border border-[#e2e8f0] dark:border-zinc-700 p-3 transition-colors ease-in"
|
||||
prompt="I like to eat "
|
||||
method="replace"
|
||||
onClick={() => {
|
||||
composerInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
||||
Food
|
||||
</span>
|
||||
</ThreadPrimitive.Suggestion>
|
||||
<ThreadPrimitive.Suggestion
|
||||
className="hover:bg-[#eef2ff] dark:hover:bg-zinc-800 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-[2rem] border border-[#e2e8f0] dark:border-zinc-700 p-3 transition-colors ease-in"
|
||||
className="hover:bg-[#eef2ff] w-full dark:hover:bg-zinc-800 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-[2rem] border border-[#e2e8f0] dark:border-zinc-700 p-3 transition-colors ease-in"
|
||||
prompt="I am working on "
|
||||
method="replace"
|
||||
onClick={() => {
|
||||
composerInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
||||
Project details
|
||||
@@ -276,7 +322,11 @@ const ThreadWelcomeSuggestions: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Composer: FC = () => {
|
||||
interface ComposerProps {
|
||||
composerInputRef: React.RefObject<HTMLTextAreaElement>;
|
||||
}
|
||||
|
||||
const Composer: FC<ComposerProps> = ({ composerInputRef }) => {
|
||||
return (
|
||||
<ComposerPrimitive.Root className="focus-within:border-[#4f46e5]/20 dark:focus-within:border-[#6366f1]/20 flex w-full flex-wrap items-end rounded-full border border-[#e2e8f0] dark:border-zinc-700 bg-white dark:bg-zinc-800 px-2.5 shadow-sm transition-colors ease-in">
|
||||
<ComposerPrimitive.Input
|
||||
@@ -284,6 +334,7 @@ const Composer: FC = () => {
|
||||
autoFocus
|
||||
placeholder="Message to Mem0..."
|
||||
className="placeholder:text-zinc-400 dark:placeholder:text-zinc-500 max-h-40 flex-grow resize-none border-none bg-transparent px-2 py-4 text-sm outline-none focus:ring-0 disabled:cursor-not-allowed text-[#1e293b] dark:text-zinc-200"
|
||||
ref={composerInputRef}
|
||||
/>
|
||||
<ComposerAction />
|
||||
</ComposerPrimitive.Root>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
const GithubButton = ({ url }: { url: string }) => {
|
||||
const GithubButton = ({ url, className, text }: { url: string, className?: string, text?: string }) => {
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center bg-black text-white rounded-full shadow-lg hover:bg-gray-800 transition border border-gray-700"
|
||||
className={cn("flex items-center bg-black text-white rounded-full shadow-lg hover:bg-gray-800 transition border border-gray-700", className)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="white"
|
||||
className="w-6 h-6"
|
||||
className="w-5 h-5 md:w-6 md:h-6"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
@@ -20,6 +20,7 @@ const GithubButton = ({ url }: { url: string }) => {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{text && <span className="ml-2">{text}</span>}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import MemoryClient from "mem0ai";
|
||||
import { OpenAI } from "openai";
|
||||
import { zodResponsesFunction } from "openai/helpers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const mem0Config = {
|
||||
apiKey: process.env.MEM0_API_KEY, // GET THIS API KEY FROM MEM0 (https://app.mem0.ai/dashboard/api-keys)
|
||||
user_id: "sample-user",
|
||||
};
|
||||
|
||||
async function run() {
|
||||
// RESPONES WITHOUT MEMORIES
|
||||
console.log("\n\nRESPONES WITHOUT MEMORIES\n\n");
|
||||
await main();
|
||||
|
||||
// ADDING SOME SAMPLE MEMORIES
|
||||
await addSampleMemories();
|
||||
|
||||
// RESPONES WITH MEMORIES
|
||||
console.log("\n\nRESPONES WITH MEMORIES\n\n");
|
||||
await main(true);
|
||||
}
|
||||
|
||||
// OpenAI Response Schema
|
||||
const CarSchema = z.object({
|
||||
car_name: z.string(),
|
||||
car_price: z.string(),
|
||||
car_url: z.string(),
|
||||
car_image: z.string(),
|
||||
car_description: z.string(),
|
||||
});
|
||||
|
||||
const Cars = z.object({
|
||||
cars: z.array(CarSchema),
|
||||
});
|
||||
|
||||
async function main(memory = false) {
|
||||
const openAIClient = new OpenAI();
|
||||
const mem0Client = new MemoryClient(mem0Config);
|
||||
|
||||
const input = "Suggest me some cars that I can buy today.";
|
||||
|
||||
const tool = zodResponsesFunction({ name: "carRecommendations", parameters: Cars });
|
||||
|
||||
// First, let's store the user's memories from user input if any
|
||||
await mem0Client.add([{
|
||||
role: "user",
|
||||
content: input,
|
||||
}], mem0Config);
|
||||
|
||||
// Then search for relevant memories
|
||||
let relevantMemories = []
|
||||
if (memory) {
|
||||
relevantMemories = await mem0Client.search(input, mem0Config);
|
||||
}
|
||||
|
||||
const response = await openAIClient.responses.create({
|
||||
model: "gpt-4o",
|
||||
tools: [{ type: "web_search_preview" }, tool],
|
||||
input: `${getMemoryString(relevantMemories)}\n${input}`,
|
||||
});
|
||||
|
||||
console.log(response.output);
|
||||
}
|
||||
|
||||
async function addSampleMemories() {
|
||||
const mem0Client = new MemoryClient(mem0Config);
|
||||
|
||||
const myInterests = "I Love BMW, Audi and Porsche. I Hate Mercedes. I love Red cars and Maroon cars. I have a budget of 120K to 150K USD. I like Audi the most.";
|
||||
|
||||
await mem0Client.add([{
|
||||
role: "user",
|
||||
content: myInterests,
|
||||
}], mem0Config);
|
||||
}
|
||||
|
||||
const getMemoryString = (memories) => {
|
||||
const MEMORY_STRING_PREFIX = "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 memoryString = memories.map((mem) => `${mem.memory}`).join("\n") ?? "";
|
||||
return memoryString.length > 0 ? `${MEMORY_STRING_PREFIX}${memoryString}` : "";
|
||||
};
|
||||
|
||||
run().catch(console.error);
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "openai-inbuilt-tools",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
|
||||
"dependencies": {
|
||||
"mem0ai": "^2.1.2",
|
||||
"openai": "^4.87.2",
|
||||
"zod": "^3.24.2"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
+18
-17
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.12",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -31,9 +31,11 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist",
|
||||
"build": "npm run clean && prettier --check . && tsup",
|
||||
"dev": "nodemon",
|
||||
"clean": "rimraf dist",
|
||||
"build": "npm run clean && npx prettier --check . && npx tsup",
|
||||
"dev": "npx nodemon",
|
||||
"start": "pnpm run example memory",
|
||||
"example": "ts-node src/oss/examples/vector-stores/index.ts",
|
||||
"test": "jest",
|
||||
"test:ts": "jest --config jest.config.js",
|
||||
"test:watch": "jest --config jest.config.js --watch",
|
||||
@@ -55,7 +57,13 @@
|
||||
"sourcemap": true,
|
||||
"clean": true,
|
||||
"treeshake": true,
|
||||
"minify": false
|
||||
"minify": false,
|
||||
"external": [
|
||||
"@mem0/community"
|
||||
],
|
||||
"noExternal": [
|
||||
"!src/community/**"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"mem0",
|
||||
@@ -74,7 +82,9 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"fix-tsup-cjs": "^1.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"prettier": "^3.5.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"ts-jest": "^29.2.6",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsup": "^8.3.0",
|
||||
@@ -82,7 +92,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"neo4j-driver": "^5.28.1",
|
||||
"openai": "4.28.0",
|
||||
"uuid": "9.0.1",
|
||||
"zod": "3.22.4"
|
||||
@@ -90,25 +99,17 @@
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": "0.18.0",
|
||||
"@qdrant/js-client-rest": "1.13.0",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/pg": "8.11.0",
|
||||
"@types/sqlite3": "3.1.11",
|
||||
"groq-sdk": "0.3.0",
|
||||
"neo4j-driver": "^5.28.1",
|
||||
"ollama": "^0.5.14",
|
||||
"pg": "8.11.3",
|
||||
"redis": "4.7.0",
|
||||
"sqlite3": "5.1.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"posthog-node": {
|
||||
"optional": true
|
||||
},
|
||||
"posthog-js": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"posthog-js": "^1.116.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
||||
Generated
+7564
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,4 @@
|
||||
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";
|
||||
|
||||
// Re-export all types from mem0.types
|
||||
@@ -23,14 +17,10 @@ export type {
|
||||
Message,
|
||||
AllUsers,
|
||||
User,
|
||||
FeedbackPayload,
|
||||
Feedback,
|
||||
} from "./mem0.types";
|
||||
|
||||
// Export telemetry types
|
||||
export type { TelemetryClient, TelemetryInstance };
|
||||
|
||||
// Export telemetry implementation
|
||||
export { telemetry, captureClientEvent, generateHash };
|
||||
|
||||
// Export the main client
|
||||
export { MemoryClient };
|
||||
export default MemoryClient;
|
||||
|
||||
+79
-49
@@ -12,6 +12,7 @@ import {
|
||||
Webhook,
|
||||
WebhookPayload,
|
||||
Message,
|
||||
FeedbackPayload,
|
||||
} from "./mem0.types";
|
||||
import { captureClientEvent, generateHash } from "./telemetry";
|
||||
|
||||
@@ -61,7 +62,7 @@ export default class MemoryClient {
|
||||
(this.organizationName !== null && this.projectName === null)
|
||||
) {
|
||||
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.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ export default class MemoryClient {
|
||||
(this.organizationId !== null && this.projectId === null)
|
||||
) {
|
||||
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.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -107,59 +108,42 @@ export default class MemoryClient {
|
||||
|
||||
private async _initializeClient() {
|
||||
try {
|
||||
// do this only in browser
|
||||
if (typeof window !== "undefined") {
|
||||
this.telemetryId = await generateHash(this.apiKey);
|
||||
await captureClientEvent("init", this);
|
||||
}
|
||||
// Generate telemetry ID
|
||||
this.telemetryId = generateHash(this.apiKey);
|
||||
|
||||
// Wrap methods after initialization
|
||||
this.add = this.wrapMethod("add", this.add);
|
||||
this.get = this.wrapMethod("get", this.get);
|
||||
this.getAll = this.wrapMethod("get_all", this.getAll);
|
||||
this.search = this.wrapMethod("search", this.search);
|
||||
this.delete = this.wrapMethod("delete", this.delete);
|
||||
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) {
|
||||
// Capture initialization event
|
||||
await captureClientEvent("init", this, {
|
||||
api_version: "v1",
|
||||
client_type: "MemoryClient",
|
||||
});
|
||||
} catch (error: any) {
|
||||
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) {
|
||||
return async function (...args: any) {
|
||||
// @ts-ignore
|
||||
await captureClientEvent(methodName, this);
|
||||
// @ts-ignore
|
||||
return method.apply(this, args);
|
||||
}.bind(this);
|
||||
private _captureEvent(methodName: string, args: any[]) {
|
||||
captureClientEvent(methodName, this, {
|
||||
success: true,
|
||||
args_count: args.length,
|
||||
keys: args.length > 0 ? args[0] : [],
|
||||
}).catch((error: any) => {
|
||||
console.error("Failed to capture event:", error);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const errorData = await response.text();
|
||||
throw new APIError(`API request failed: ${errorData}`);
|
||||
@@ -205,7 +189,16 @@ export default class MemoryClient {
|
||||
if (options.project_name) delete options.project_name;
|
||||
}
|
||||
|
||||
if (options.api_version) {
|
||||
options.version = options.api_version.toString();
|
||||
}
|
||||
|
||||
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(
|
||||
`${this.host}/v1/memories/`,
|
||||
{
|
||||
@@ -222,6 +215,10 @@ export default class MemoryClient {
|
||||
const payload = {
|
||||
text: message,
|
||||
};
|
||||
|
||||
const payloadKeys = Object.keys(payload);
|
||||
this._captureEvent("update", [payloadKeys]);
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/memories/${memoryId}/`,
|
||||
{
|
||||
@@ -234,6 +231,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async get(memoryId: string): Promise<Memory> {
|
||||
this._captureEvent("get", []);
|
||||
return this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/memories/${memoryId}/`,
|
||||
{
|
||||
@@ -244,6 +242,8 @@ export default class MemoryClient {
|
||||
|
||||
async getAll(options?: SearchOptions): Promise<Array<Memory>> {
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_all", [payloadKeys]);
|
||||
const { api_version, page, page_size, ...otherOptions } = options!;
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
otherOptions.org_name = this.organizationName;
|
||||
@@ -289,6 +289,8 @@ export default class MemoryClient {
|
||||
|
||||
async search(query: string, options?: SearchOptions): Promise<Array<Memory>> {
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("search", [payloadKeys]);
|
||||
const { api_version, ...otherOptions } = options!;
|
||||
const payload = { query, ...otherOptions };
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
@@ -317,6 +319,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async delete(memoryId: string): Promise<{ message: string }> {
|
||||
this._captureEvent("delete", []);
|
||||
return this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/memories/${memoryId}/`,
|
||||
{
|
||||
@@ -328,6 +331,8 @@ export default class MemoryClient {
|
||||
|
||||
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
|
||||
this._validateOrgProject();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("delete_all", [payloadKeys]);
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
options.project_name = this.projectName;
|
||||
@@ -353,6 +358,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async history(memoryId: string): Promise<Array<MemoryHistory>> {
|
||||
this._captureEvent("history", []);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/memories/${memoryId}/history/`,
|
||||
{
|
||||
@@ -364,6 +370,7 @@ export default class MemoryClient {
|
||||
|
||||
async users(): Promise<AllUsers> {
|
||||
this._validateOrgProject();
|
||||
this._captureEvent("users", []);
|
||||
const options: MemoryOptions = {};
|
||||
if (this.organizationName != null && this.projectName != null) {
|
||||
options.org_name = this.organizationName;
|
||||
@@ -392,6 +399,7 @@ export default class MemoryClient {
|
||||
entityId: string,
|
||||
entity: { type: string } = { type: "user" },
|
||||
): Promise<{ message: string }> {
|
||||
this._captureEvent("delete_user", []);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/entities/${entity.type}/${entityId}/`,
|
||||
{
|
||||
@@ -404,6 +412,7 @@ export default class MemoryClient {
|
||||
|
||||
async deleteUsers(): Promise<{ message: string }> {
|
||||
this._validateOrgProject();
|
||||
this._captureEvent("delete_users", []);
|
||||
const entities = await this.users();
|
||||
|
||||
for (const entity of entities.results) {
|
||||
@@ -428,6 +437,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async batchUpdate(memories: Array<MemoryUpdateBody>): Promise<string> {
|
||||
this._captureEvent("batch_update", []);
|
||||
const memoriesBody = memories.map((memory) => ({
|
||||
memory_id: memory.memoryId,
|
||||
text: memory.text,
|
||||
@@ -444,6 +454,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async batchDelete(memories: Array<string>): Promise<string> {
|
||||
this._captureEvent("batch_delete", []);
|
||||
const memoriesBody = memories.map((memory) => ({
|
||||
memory_id: memory,
|
||||
}));
|
||||
@@ -460,7 +471,8 @@ export default class MemoryClient {
|
||||
|
||||
async getProject(options: ProjectOptions): Promise<ProjectResponse> {
|
||||
this._validateOrgProject();
|
||||
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_project", [payloadKeys]);
|
||||
const { fields } = options;
|
||||
|
||||
if (!(this.organizationId && this.projectId)) {
|
||||
@@ -485,7 +497,7 @@ export default class MemoryClient {
|
||||
prompts: PromptUpdatePayload,
|
||||
): Promise<Record<string, any>> {
|
||||
this._validateOrgProject();
|
||||
|
||||
this._captureEvent("update_project", []);
|
||||
if (!(this.organizationId && this.projectId)) {
|
||||
throw new Error(
|
||||
"organizationId and projectId must be set to update instructions or categories",
|
||||
@@ -505,6 +517,7 @@ export default class MemoryClient {
|
||||
|
||||
// WebHooks
|
||||
async getWebhooks(data?: { projectId?: string }): Promise<Array<Webhook>> {
|
||||
this._captureEvent("get_webhooks", []);
|
||||
const project_id = data?.projectId || this.projectId;
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/webhooks/projects/${project_id}/`,
|
||||
@@ -516,6 +529,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async createWebhook(webhook: WebhookPayload): Promise<Webhook> {
|
||||
this._captureEvent("create_webhook", []);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/webhooks/projects/${this.projectId}/`,
|
||||
{
|
||||
@@ -528,6 +542,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async updateWebhook(webhook: WebhookPayload): Promise<{ message: string }> {
|
||||
this._captureEvent("update_webhook", []);
|
||||
const project_id = webhook.projectId || this.projectId;
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/webhooks/${webhook.webhookId}/`,
|
||||
@@ -546,6 +561,7 @@ export default class MemoryClient {
|
||||
async deleteWebhook(data: {
|
||||
webhookId: string;
|
||||
}): Promise<{ message: string }> {
|
||||
this._captureEvent("delete_webhook", []);
|
||||
const webhook_id = data.webhookId || data;
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/webhooks/${webhook_id}/`,
|
||||
@@ -556,6 +572,20 @@ export default class MemoryClient {
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
async feedback(data: FeedbackPayload): Promise<{ message: string }> {
|
||||
const payloadKeys = Object.keys(data || {});
|
||||
this._captureEvent("feedback", [payloadKeys]);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/feedback/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export { MemoryClient };
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export interface MemoryOptions {
|
||||
api_version?: API_VERSION | string;
|
||||
version?: API_VERSION | string;
|
||||
user_id?: string;
|
||||
agent_id?: string;
|
||||
app_id?: string;
|
||||
@@ -17,6 +19,7 @@ export interface MemoryOptions {
|
||||
enable_graph?: boolean;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
}
|
||||
|
||||
export interface ProjectOptions {
|
||||
@@ -28,6 +31,12 @@ export enum API_VERSION {
|
||||
V2 = "v2",
|
||||
}
|
||||
|
||||
export enum Feedback {
|
||||
POSITIVE = "POSITIVE",
|
||||
NEGATIVE = "NEGATIVE",
|
||||
VERY_NEGATIVE = "VERY_NEGATIVE",
|
||||
}
|
||||
|
||||
export interface MultiModalMessages {
|
||||
type: "image_url";
|
||||
image_url: {
|
||||
@@ -161,3 +170,9 @@ export interface WebhookPayload {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface FeedbackPayload {
|
||||
memory_id: string;
|
||||
feedback?: Feedback | null;
|
||||
feedback_reason?: string | null;
|
||||
}
|
||||
|
||||
@@ -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 = process.env.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 };
|
||||
@@ -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 };
|
||||
@@ -1,3 +1,97 @@
|
||||
// @ts-nocheck
|
||||
// Re-export browser telemetry by default
|
||||
export * from "./telemetry.browser";
|
||||
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
|
||||
|
||||
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 };
|
||||
|
||||
@@ -12,4 +12,23 @@ export interface TelemetryInstance {
|
||||
constructor: {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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*
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -116,6 +116,36 @@ async function runTests(memory: Memory) {
|
||||
}
|
||||
}
|
||||
|
||||
async function demoLocalMemory() {
|
||||
console.log("\n=== Testing In-Memory Vector Store with Ollama===\n");
|
||||
|
||||
const memory = new Memory({
|
||||
version: "v1.1",
|
||||
embedder: {
|
||||
provider: "ollama",
|
||||
config: {
|
||||
model: "nomic-embed-text:latest",
|
||||
},
|
||||
},
|
||||
vectorStore: {
|
||||
provider: "memory",
|
||||
config: {
|
||||
collectionName: "memories",
|
||||
dimension: 768, // 768 is the dimension of the nomic-embed-text model
|
||||
},
|
||||
},
|
||||
llm: {
|
||||
provider: "ollama",
|
||||
config: {
|
||||
model: "llama3.1:8b",
|
||||
},
|
||||
},
|
||||
// historyDbPath: "memory.db",
|
||||
});
|
||||
|
||||
await runTests(memory);
|
||||
}
|
||||
|
||||
async function demoMemoryStore() {
|
||||
console.log("\n=== Testing In-Memory Vector Store ===\n");
|
||||
|
||||
@@ -346,6 +376,9 @@ async function main() {
|
||||
// Test in-memory store
|
||||
await demoMemoryStore();
|
||||
|
||||
// Test in-memory store with Ollama
|
||||
await demoLocalMemory();
|
||||
|
||||
// Test graph memory if Neo4j environment variables are set
|
||||
if (
|
||||
process.env.NEO4J_URL &&
|
||||
@@ -384,4 +417,4 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// main();
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Memory } from "../src";
|
||||
import { Ollama } from "ollama";
|
||||
import * as readline from "readline";
|
||||
|
||||
const memory = new Memory({
|
||||
embedder: {
|
||||
provider: "ollama",
|
||||
config: {
|
||||
model: "nomic-embed-text:latest",
|
||||
},
|
||||
},
|
||||
vectorStore: {
|
||||
provider: "memory",
|
||||
config: {
|
||||
collectionName: "memories",
|
||||
dimension: 768, // since we are using nomic-embed-text
|
||||
},
|
||||
},
|
||||
llm: {
|
||||
provider: "ollama",
|
||||
config: {
|
||||
model: "llama3.1:8b",
|
||||
},
|
||||
},
|
||||
historyDbPath: "local-llms.db",
|
||||
});
|
||||
|
||||
async function chatWithMemories(message: string, userId = "default_user") {
|
||||
const relevantMemories = await memory.search(message, { userId: userId });
|
||||
|
||||
const memoriesStr = relevantMemories.results
|
||||
.map((entry) => `- ${entry.memory}`)
|
||||
.join("\n");
|
||||
|
||||
const systemPrompt = `You are a helpful AI. Answer the question based on query and memories.
|
||||
User Memories:
|
||||
${memoriesStr}`;
|
||||
|
||||
const messages = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: message },
|
||||
];
|
||||
|
||||
const ollama = new Ollama();
|
||||
const response = await ollama.chat({
|
||||
model: "llama3.1:8b",
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
const assistantResponse = response.message.content || "";
|
||||
|
||||
messages.push({ role: "assistant", content: assistantResponse });
|
||||
await memory.add(messages, { userId: userId });
|
||||
|
||||
return assistantResponse;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
console.log("Chat with AI (type 'exit' to quit)");
|
||||
|
||||
const askQuestion = (): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question("You: ", (input) => {
|
||||
resolve(input.trim());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const userInput = await askQuestion();
|
||||
|
||||
if (userInput.toLowerCase() === "exit") {
|
||||
console.log("Goodbye!");
|
||||
rl.close();
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await chatWithMemories(userInput, "sample_user");
|
||||
console.log(`AI: ${response}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error occurred:", error);
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -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();
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"start": "ts-node examples/basic.ts",
|
||||
"example": "ts-node examples/basic.ts",
|
||||
"start": "pnpm run example memory",
|
||||
"example": "ts-node examples/vector-stores/index.ts",
|
||||
"clean": "rimraf dist",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MemoryConfig } from "../types";
|
||||
|
||||
export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
|
||||
disableHistory: false,
|
||||
version: "v1.1",
|
||||
embedder: {
|
||||
provider: "openai",
|
||||
@@ -38,5 +39,10 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
historyDbPath: "memory.db",
|
||||
historyStore: {
|
||||
provider: "sqlite",
|
||||
config: {
|
||||
historyDbPath: "memory.db",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -51,6 +51,12 @@ export class ConfigManager {
|
||||
...DEFAULT_MEMORY_CONFIG.graphStore,
|
||||
...userConfig.graphStore,
|
||||
},
|
||||
historyStore: {
|
||||
...DEFAULT_MEMORY_CONFIG.historyStore,
|
||||
...userConfig.historyStore,
|
||||
},
|
||||
disableHistory:
|
||||
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
|
||||
enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Ollama } from "ollama";
|
||||
import { Embedder } from "./base";
|
||||
import { EmbeddingConfig } from "../types";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
export class OllamaEmbedder implements Embedder {
|
||||
private ollama: Ollama;
|
||||
private model: string;
|
||||
// Using this variable to avoid calling the Ollama server multiple times
|
||||
private initialized: boolean = false;
|
||||
|
||||
constructor(config: EmbeddingConfig) {
|
||||
this.ollama = new Ollama({
|
||||
host: config.url || "http://localhost:11434",
|
||||
});
|
||||
this.model = config.model || "nomic-embed-text:latest";
|
||||
this.ensureModelExists().catch((err) => {
|
||||
logger.error(`Error ensuring model exists: ${err}`);
|
||||
});
|
||||
}
|
||||
|
||||
async embed(text: string): Promise<number[]> {
|
||||
try {
|
||||
await this.ensureModelExists();
|
||||
} catch (err) {
|
||||
logger.error(`Error ensuring model exists: ${err}`);
|
||||
}
|
||||
const response = await this.ollama.embeddings({
|
||||
model: this.model,
|
||||
prompt: text,
|
||||
});
|
||||
return response.embedding;
|
||||
}
|
||||
|
||||
async embedBatch(texts: string[]): Promise<number[][]> {
|
||||
const response = await Promise.all(texts.map((text) => this.embed(text)));
|
||||
return response;
|
||||
}
|
||||
|
||||
private async ensureModelExists(): Promise<boolean> {
|
||||
if (this.initialized) {
|
||||
return true;
|
||||
}
|
||||
const local_models = await this.ollama.list();
|
||||
if (!local_models.models.find((m: any) => m.name === this.model)) {
|
||||
logger.info(`Pulling model ${this.model}...`);
|
||||
await this.ollama.pull({ model: this.model });
|
||||
}
|
||||
this.initialized = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@ export * from "./memory/memory.types";
|
||||
export * from "./types";
|
||||
export * from "./embeddings/base";
|
||||
export * from "./embeddings/openai";
|
||||
export * from "./embeddings/ollama";
|
||||
export * from "./llms/base";
|
||||
export * from "./llms/openai";
|
||||
export * from "./llms/openai_structured";
|
||||
export * from "./llms/anthropic";
|
||||
export * from "./llms/groq";
|
||||
export * from "./llms/ollama";
|
||||
export * from "./vector_stores/base";
|
||||
export * from "./vector_stores/memory";
|
||||
export * from "./vector_stores/qdrant";
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Ollama } from "ollama";
|
||||
import { LLM, LLMResponse } from "./base";
|
||||
import { LLMConfig, Message } from "../types";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
export class OllamaLLM implements LLM {
|
||||
private ollama: Ollama;
|
||||
private model: string;
|
||||
// Using this variable to avoid calling the Ollama server multiple times
|
||||
private initialized: boolean = false;
|
||||
|
||||
constructor(config: LLMConfig) {
|
||||
this.ollama = new Ollama({
|
||||
host: config.config?.url || "http://localhost:11434",
|
||||
});
|
||||
this.model = config.model || "llama3.1:8b";
|
||||
this.ensureModelExists().catch((err) => {
|
||||
logger.error(`Error ensuring model exists: ${err}`);
|
||||
});
|
||||
}
|
||||
|
||||
async generateResponse(
|
||||
messages: Message[],
|
||||
responseFormat?: { type: string },
|
||||
tools?: any[],
|
||||
): Promise<string | LLMResponse> {
|
||||
try {
|
||||
await this.ensureModelExists();
|
||||
} catch (err) {
|
||||
logger.error(`Error ensuring model exists: ${err}`);
|
||||
}
|
||||
|
||||
const completion = await this.ollama.chat({
|
||||
model: this.model,
|
||||
messages: messages.map((msg) => {
|
||||
const role = msg.role as "system" | "user" | "assistant";
|
||||
return {
|
||||
role,
|
||||
content:
|
||||
typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content),
|
||||
};
|
||||
}),
|
||||
...(responseFormat?.type === "json_object" && { format: "json" }),
|
||||
...(tools && { tools, tool_choice: "auto" }),
|
||||
});
|
||||
|
||||
const response = completion.message;
|
||||
|
||||
if (response.tool_calls) {
|
||||
return {
|
||||
content: response.content || "",
|
||||
role: response.role,
|
||||
toolCalls: response.tool_calls.map((call) => ({
|
||||
name: call.function.name,
|
||||
arguments: JSON.stringify(call.function.arguments),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return response.content || "";
|
||||
}
|
||||
|
||||
async generateChat(messages: Message[]): Promise<LLMResponse> {
|
||||
try {
|
||||
await this.ensureModelExists();
|
||||
} catch (err) {
|
||||
logger.error(`Error ensuring model exists: ${err}`);
|
||||
}
|
||||
|
||||
const completion = await this.ollama.chat({
|
||||
messages: messages.map((msg) => {
|
||||
const role = msg.role as "system" | "user" | "assistant";
|
||||
return {
|
||||
role,
|
||||
content:
|
||||
typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content),
|
||||
};
|
||||
}),
|
||||
model: this.model,
|
||||
});
|
||||
const response = completion.message;
|
||||
return {
|
||||
content: response.content || "",
|
||||
role: response.role,
|
||||
};
|
||||
}
|
||||
|
||||
private async ensureModelExists(): Promise<boolean> {
|
||||
if (this.initialized) {
|
||||
return true;
|
||||
}
|
||||
const local_models = await this.ollama.list();
|
||||
if (!local_models.models.find((m: any) => m.name === this.model)) {
|
||||
logger.info(`Pulling model ${this.model}...`);
|
||||
await this.ollama.pull({ model: this.model });
|
||||
}
|
||||
this.initialized = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
EmbedderFactory,
|
||||
LLMFactory,
|
||||
VectorStoreFactory,
|
||||
HistoryManagerFactory,
|
||||
} from "../utils/factory";
|
||||
import {
|
||||
getFactRetrievalMessages,
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
parseMessages,
|
||||
removeCodeBlocks,
|
||||
} from "../prompts";
|
||||
import { SQLiteManager } from "../storage";
|
||||
import { DummyHistoryManager } from "../storage/DummyHistoryManager";
|
||||
import { Embedder } from "../embeddings/base";
|
||||
import { LLM } from "../llms/base";
|
||||
import { VectorStore } from "../vector_stores/base";
|
||||
@@ -32,14 +33,14 @@ import {
|
||||
GetAllMemoryOptions,
|
||||
} from "./memory.types";
|
||||
import { parse_vision_messages } from "../utils/memory";
|
||||
|
||||
import { HistoryManager } from "../storage/base";
|
||||
export class Memory {
|
||||
private config: MemoryConfig;
|
||||
private customPrompt: string | undefined;
|
||||
private embedder: Embedder;
|
||||
private vectorStore: VectorStore;
|
||||
private llm: LLM;
|
||||
private db: SQLiteManager;
|
||||
private db: HistoryManager;
|
||||
private collectionName: string;
|
||||
private apiVersion: string;
|
||||
private graphMemory?: MemoryGraph;
|
||||
@@ -62,7 +63,25 @@ export class Memory {
|
||||
this.config.llm.provider,
|
||||
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.apiVersion = this.config.version || "v1.0";
|
||||
this.enableGraph = this.config.enableGraph || false;
|
||||
@@ -93,7 +112,7 @@ export class Memory {
|
||||
runId,
|
||||
metadata = {},
|
||||
filters = {},
|
||||
prompt,
|
||||
infer = true,
|
||||
} = config;
|
||||
|
||||
if (userId) filters.userId = metadata.userId = userId;
|
||||
@@ -117,6 +136,7 @@ export class Memory {
|
||||
final_parsedMessages,
|
||||
metadata,
|
||||
filters,
|
||||
infer,
|
||||
);
|
||||
|
||||
// Add to graph store if available
|
||||
@@ -142,7 +162,27 @@ export class Memory {
|
||||
messages: Message[],
|
||||
metadata: Record<string, any>,
|
||||
filters: SearchFilters,
|
||||
infer: boolean,
|
||||
): 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");
|
||||
|
||||
// Get prompts
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface Entity {
|
||||
export interface AddMemoryOptions extends Entity {
|
||||
metadata?: Record<string, any>;
|
||||
filters?: SearchFilters;
|
||||
prompt?: string;
|
||||
infer?: boolean;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import sqlite3 from "sqlite3";
|
||||
import { promisify } from "util";
|
||||
import { HistoryManager } from "./base";
|
||||
|
||||
export class SQLiteManager {
|
||||
export class SQLiteManager implements HistoryManager {
|
||||
private db: sqlite3.Database;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1 +1,5 @@
|
||||
export * from "./SQLiteManager";
|
||||
export * from "./DummyHistoryManager";
|
||||
export * from "./SupabaseHistoryManager";
|
||||
export * from "./MemoryHistoryManager";
|
||||
export * from "./base";
|
||||
|
||||
@@ -13,8 +13,9 @@ export interface Message {
|
||||
}
|
||||
|
||||
export interface EmbeddingConfig {
|
||||
apiKey: string;
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface VectorStoreConfig {
|
||||
@@ -23,6 +24,16 @@ export interface VectorStoreConfig {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface HistoryStoreConfig {
|
||||
provider: string;
|
||||
config: {
|
||||
historyDbPath?: string;
|
||||
supabaseUrl?: string;
|
||||
supabaseKey?: string;
|
||||
tableName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LLMConfig {
|
||||
provider?: string;
|
||||
config?: Record<string, any>;
|
||||
@@ -57,6 +68,8 @@ export interface MemoryConfig {
|
||||
provider: string;
|
||||
config: LLMConfig;
|
||||
};
|
||||
historyStore?: HistoryStoreConfig;
|
||||
disableHistory?: boolean;
|
||||
historyDbPath?: string;
|
||||
customPrompt?: string;
|
||||
graphStore?: GraphStoreConfig;
|
||||
@@ -136,4 +149,11 @@ export const MemoryConfigSchema = z.object({
|
||||
customPrompt: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
historyStore: z
|
||||
.object({
|
||||
provider: z.string(),
|
||||
config: z.record(z.string(), z.any()),
|
||||
})
|
||||
.optional(),
|
||||
disableHistory: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
import { OpenAIEmbedder } from "../embeddings/openai";
|
||||
import { OllamaEmbedder } from "../embeddings/ollama";
|
||||
import { OpenAILLM } from "../llms/openai";
|
||||
import { OpenAIStructuredLLM } from "../llms/openai_structured";
|
||||
import { AnthropicLLM } from "../llms/anthropic";
|
||||
import { GroqLLM } from "../llms/groq";
|
||||
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 { LLM } from "../llms/base";
|
||||
import { VectorStore } from "../vector_stores/base";
|
||||
import { Qdrant } from "../vector_stores/qdrant";
|
||||
import { RedisDB } from "../vector_stores/redis";
|
||||
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 {
|
||||
static create(provider: string, config: EmbeddingConfig): Embedder {
|
||||
switch (provider.toLowerCase()) {
|
||||
case "openai":
|
||||
return new OpenAIEmbedder(config);
|
||||
case "ollama":
|
||||
return new OllamaEmbedder(config);
|
||||
default:
|
||||
throw new Error(`Unsupported embedder provider: ${provider}`);
|
||||
}
|
||||
@@ -33,6 +47,8 @@ export class LLMFactory {
|
||||
return new AnthropicLLM(config);
|
||||
case "groq":
|
||||
return new GroqLLM(config);
|
||||
case "ollama":
|
||||
return new OllamaLLM(config);
|
||||
default:
|
||||
throw new Error(`Unsupported LLM provider: ${provider}`);
|
||||
}
|
||||
@@ -48,8 +64,29 @@ export class VectorStoreFactory {
|
||||
return new Qdrant(config as any); // Type assertion needed as config is extended
|
||||
case "redis":
|
||||
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:
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ interface RedisDocument {
|
||||
run_id?: string;
|
||||
user_id?: string;
|
||||
metadata?: string;
|
||||
vector_score?: number;
|
||||
__vector_score?: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,6 +108,30 @@ const EXCLUDED_KEYS = new Set([
|
||||
"updated_at",
|
||||
]);
|
||||
|
||||
// Utility function to convert object keys to snake_case
|
||||
function toSnakeCase(obj: Record<string, any>): Record<string, any> {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// Utility function to convert object keys to camelCase
|
||||
function toCamelCase(obj: Record<string, any>): Record<string, any> {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
export class RedisDB implements VectorStore {
|
||||
private client: RedisClientType<
|
||||
RedisDefaultModules & RedisModules & RedisFunctions & RedisScripts
|
||||
@@ -272,7 +296,7 @@ export class RedisDB implements VectorStore {
|
||||
payloads: Record<string, any>[],
|
||||
): Promise<void> {
|
||||
const data = vectors.map((vector, idx) => {
|
||||
const payload = payloads[idx];
|
||||
const payload = toSnakeCase(payloads[idx]);
|
||||
const id = ids[idx];
|
||||
|
||||
// Create entry with required fields
|
||||
@@ -322,8 +346,9 @@ export class RedisDB implements VectorStore {
|
||||
limit: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
const filterExpr = filters
|
||||
? Object.entries(filters)
|
||||
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
|
||||
const filterExpr = snakeFilters
|
||||
? Object.entries(snakeFilters)
|
||||
.filter(([_, value]) => value !== null)
|
||||
.map(([key, value]) => `@${key}:{${value}}`)
|
||||
.join(" ")
|
||||
@@ -344,8 +369,9 @@ export class RedisDB implements VectorStore {
|
||||
"memory",
|
||||
"metadata",
|
||||
"created_at",
|
||||
"__vector_score",
|
||||
],
|
||||
SORTBY: "vector_score",
|
||||
SORTBY: "__vector_score",
|
||||
DIALECT: 2,
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
@@ -356,12 +382,12 @@ export class RedisDB implements VectorStore {
|
||||
try {
|
||||
const results = (await this.client.ft.search(
|
||||
this.indexName,
|
||||
`${filterExpr} =>[KNN ${limit} @embedding $vec AS vector_score]`,
|
||||
`${filterExpr} =>[KNN ${limit} @embedding $vec AS __vector_score]`,
|
||||
searchOptions,
|
||||
)) as unknown as RedisSearchResult;
|
||||
|
||||
return results.documents.map((doc) => {
|
||||
const payload = {
|
||||
const resultPayload = {
|
||||
hash: doc.value.hash,
|
||||
data: doc.value.memory,
|
||||
created_at: new Date(parseInt(doc.value.created_at)).toISOString(),
|
||||
@@ -376,8 +402,8 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
return {
|
||||
id: doc.value.memory_id,
|
||||
payload,
|
||||
score: doc.value.vector_score,
|
||||
payload: toCamelCase(resultPayload),
|
||||
score: Number(doc.value.__vector_score) ?? 0,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -493,26 +519,27 @@ export class RedisDB implements VectorStore {
|
||||
vector: number[],
|
||||
payload: Record<string, any>,
|
||||
): Promise<void> {
|
||||
const snakePayload = toSnakeCase(payload);
|
||||
const entry: Record<string, any> = {
|
||||
memory_id: vectorId,
|
||||
hash: payload.hash,
|
||||
memory: payload.data,
|
||||
created_at: new Date(payload.created_at).getTime(),
|
||||
updated_at: new Date(payload.updated_at).getTime(),
|
||||
hash: snakePayload.hash,
|
||||
memory: snakePayload.data,
|
||||
created_at: new Date(snakePayload.created_at).getTime(),
|
||||
updated_at: new Date(snakePayload.updated_at).getTime(),
|
||||
embedding: Buffer.from(new Float32Array(vector).buffer),
|
||||
};
|
||||
|
||||
// Add optional fields
|
||||
["agent_id", "run_id", "user_id"].forEach((field) => {
|
||||
if (field in payload) {
|
||||
entry[field] = payload[field];
|
||||
if (field in snakePayload) {
|
||||
entry[field] = snakePayload[field];
|
||||
}
|
||||
});
|
||||
|
||||
// Add metadata excluding specific keys
|
||||
entry.metadata = JSON.stringify(
|
||||
Object.fromEntries(
|
||||
Object.entries(payload).filter(([key]) => !EXCLUDED_KEYS.has(key)),
|
||||
Object.entries(snakePayload).filter(([key]) => !EXCLUDED_KEYS.has(key)),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -557,8 +584,9 @@ export class RedisDB implements VectorStore {
|
||||
filters?: SearchFilters,
|
||||
limit: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
const filterExpr = filters
|
||||
? Object.entries(filters)
|
||||
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
|
||||
const filterExpr = snakeFilters
|
||||
? Object.entries(snakeFilters)
|
||||
.filter(([_, value]) => value !== null)
|
||||
.map(([key, value]) => `@${key}:{${value}}`)
|
||||
.join(" ")
|
||||
@@ -581,7 +609,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
const items = results.documents.map((doc) => ({
|
||||
id: doc.value.memory_id,
|
||||
payload: {
|
||||
payload: toCamelCase({
|
||||
hash: doc.value.hash,
|
||||
data: doc.value.memory,
|
||||
created_at: new Date(parseInt(doc.value.created_at)).toISOString(),
|
||||
@@ -592,7 +620,7 @@ export class RedisDB implements VectorStore {
|
||||
...(doc.value.run_id && { run_id: doc.value.run_id }),
|
||||
...(doc.value.user_id && { user_id: doc.value.user_id }),
|
||||
...JSON.parse(doc.value.metadata || "{}"),
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
return [items, results.total];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-2
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from mem0.memory.setup import setup_config, get_user_id
|
||||
from mem0.memory.setup import get_user_id, setup_config
|
||||
from mem0.memory.telemetry import capture_client_event
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -110,7 +110,7 @@ class MemoryClient:
|
||||
try:
|
||||
error_data = e.response.json()
|
||||
error_message = error_data.get("detail", str(e))
|
||||
except:
|
||||
except Exception:
|
||||
error_message = str(e)
|
||||
raise ValueError(f"Error: {error_message}")
|
||||
|
||||
@@ -617,6 +617,23 @@ class MemoryClient:
|
||||
capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id})
|
||||
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(
|
||||
self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
@@ -998,3 +1015,20 @@ class AsyncMemoryClient:
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.delete_webhook", self.sync_client, {"webhook_id": webhook_id})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
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"}
|
||||
|
||||
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 = await self.async_client.post("/v1/feedback/", json=data)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.feedback", self.sync_client, data)
|
||||
return response.json()
|
||||
|
||||
@@ -48,8 +48,12 @@ class MemoryConfig(BaseModel):
|
||||
description="The version of the API",
|
||||
default="v1.1",
|
||||
)
|
||||
custom_prompt: Optional[str] = Field(
|
||||
description="Custom prompt for the memory",
|
||||
custom_fact_extraction_prompt: Optional[str] = Field(
|
||||
description="Custom prompt for the fact extraction",
|
||||
default=None,
|
||||
)
|
||||
custom_update_memory_prompt: Optional[str] = Field(
|
||||
description="Custom prompt for the update memory",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ class BaseEmbedderConfig(ABC):
|
||||
memory_add_embedding_type: Optional[str] = None,
|
||||
memory_update_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.
|
||||
@@ -58,6 +60,8 @@ class BaseEmbedderConfig(ABC):
|
||||
: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
|
||||
: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
|
||||
@@ -82,3 +86,6 @@ class BaseEmbedderConfig(ABC):
|
||||
self.memory_add_embedding_type = memory_add_embedding_type
|
||||
self.memory_update_embedding_type = memory_update_embedding_type
|
||||
self.memory_search_embedding_type = memory_search_embedding_type
|
||||
|
||||
# LM Studio specific
|
||||
self.lmstudio_base_url = lmstudio_base_url
|
||||
|
||||
@@ -39,6 +39,8 @@ class BaseLlmConfig(ABC):
|
||||
deepseek_base_url: Optional[str] = None,
|
||||
# XAI specific
|
||||
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.
|
||||
@@ -83,6 +85,8 @@ class BaseLlmConfig(ABC):
|
||||
:type deepseek_base_url: Optional[str], optional
|
||||
:param xai_base_url: XAI base URL to be use, defaults to None
|
||||
: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
|
||||
@@ -116,3 +120,6 @@ class BaseLlmConfig(ABC):
|
||||
|
||||
# XAI specific
|
||||
self.xai_base_url = xai_base_url
|
||||
|
||||
# LM Studio specific
|
||||
self.lmstudio_base_url = lmstudio_base_url
|
||||
|
||||
+165
-144
@@ -58,169 +58,190 @@ Following is a conversation between the user and the assistant. You have to extr
|
||||
You should detect the language of the user input and record the facts in the same language.
|
||||
"""
|
||||
|
||||
DEFAULT_UPDATE_MEMORY_PROMPT = """You are a smart memory manager which controls the memory of a system.
|
||||
You can perform four operations: (1) add into the memory, (2) update the memory, (3) delete from the memory, and (4) no change.
|
||||
|
||||
def get_update_memory_messages(retrieved_old_memory_dict, response_content):
|
||||
return f"""You are a smart memory manager which controls the memory of a system.
|
||||
You can perform four operations: (1) add into the memory, (2) update the memory, (3) delete from the memory, and (4) no change.
|
||||
Based on the above four operations, the memory will change.
|
||||
|
||||
Based on the above four operations, the memory will change.
|
||||
Compare newly retrieved facts with the existing memory. For each new fact, decide whether to:
|
||||
- ADD: Add it to the memory as a new element
|
||||
- UPDATE: Update an existing memory element
|
||||
- DELETE: Delete an existing memory element
|
||||
- NONE: Make no change (if the fact is already present or irrelevant)
|
||||
|
||||
Compare newly retrieved facts with the existing memory. For each new fact, decide whether to:
|
||||
- ADD: Add it to the memory as a new element
|
||||
- UPDATE: Update an existing memory element
|
||||
- DELETE: Delete an existing memory element
|
||||
- NONE: Make no change (if the fact is already present or irrelevant)
|
||||
There are specific guidelines to select which operation to perform:
|
||||
|
||||
There are specific guidelines to select which operation to perform:
|
||||
1. **Add**: If the retrieved facts contain new information not present in the memory, then you have to add it by generating a new ID in the id field.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "User is a software engineer"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Name is John"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "User is a software engineer",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Name is John",
|
||||
"event" : "ADD"
|
||||
}
|
||||
]
|
||||
|
||||
1. **Add**: If the retrieved facts contain new information not present in the memory, then you have to add it by generating a new ID in the id field.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "User is a software engineer"
|
||||
}}
|
||||
]
|
||||
- Retrieved facts: ["Name is John"]
|
||||
- New Memory:
|
||||
{{
|
||||
"memory" : [
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "User is a software engineer",
|
||||
"event" : "NONE"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "Name is John",
|
||||
"event" : "ADD"
|
||||
}}
|
||||
]
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
2. **Update**: If the retrieved facts contain information that is already present in the memory but the information is totally different, then you have to update it.
|
||||
If the retrieved fact contains information that conveys the same thing as the elements present in the memory, then you have to keep the fact which has the most information.
|
||||
Example (a) -- if the memory contains "User likes to play cricket" and the retrieved fact is "Loves to play cricket with friends", then update the memory with the retrieved facts.
|
||||
Example (b) -- if the memory contains "Likes cheese pizza" and the retrieved fact is "Loves cheese pizza", then you do not need to update it because they convey the same information.
|
||||
If the direction is to update the memory, then you have to update it.
|
||||
Please keep in mind while updating you have to keep the same ID.
|
||||
Please note to return the IDs in the output from the input IDs only and do not generate any new ID.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "I really like cheese pizza"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "User is a software engineer"
|
||||
}},
|
||||
{{
|
||||
"id" : "2",
|
||||
"text" : "User likes to play cricket"
|
||||
}}
|
||||
]
|
||||
- Retrieved facts: ["Loves chicken pizza", "Loves to play cricket with friends"]
|
||||
- New Memory:
|
||||
{{
|
||||
"memory" : [
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "Loves cheese and chicken pizza",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "I really like cheese pizza"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "User is a software engineer",
|
||||
"event" : "NONE"
|
||||
}},
|
||||
{{
|
||||
"id" : "2",
|
||||
"text" : "Loves to play cricket with friends",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "User likes to play cricket"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
2. **Update**: If the retrieved facts contain information that is already present in the memory but the information is totally different, then you have to update it.
|
||||
If the retrieved fact contains information that conveys the same thing as the elements present in the memory, then you have to keep the fact which has the most information.
|
||||
Example (a) -- if the memory contains "User likes to play cricket" and the retrieved fact is "Loves to play cricket with friends", then update the memory with the retrieved facts.
|
||||
Example (b) -- if the memory contains "Likes cheese pizza" and the retrieved fact is "Loves cheese pizza", then you do not need to update it because they convey the same information.
|
||||
If the direction is to update the memory, then you have to update it.
|
||||
Please keep in mind while updating you have to keep the same ID.
|
||||
Please note to return the IDs in the output from the input IDs only and do not generate any new ID.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "I really like cheese pizza"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "User is a software engineer"
|
||||
},
|
||||
{
|
||||
"id" : "2",
|
||||
"text" : "User likes to play cricket"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Loves chicken pizza", "Loves to play cricket with friends"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Loves cheese and chicken pizza",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "I really like cheese pizza"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "User is a software engineer",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "2",
|
||||
"text" : "Loves to play cricket with friends",
|
||||
"event" : "UPDATE",
|
||||
"old_memory" : "User likes to play cricket"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
3. **Delete**: If the retrieved facts contain information that contradicts the information present in the memory, then you have to delete it. Or if the direction is to delete the memory, then you have to delete it.
|
||||
Please note to return the IDs in the output from the input IDs only and do not generate any new ID.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "Name is John"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza"
|
||||
}}
|
||||
]
|
||||
- Retrieved facts: ["Dislikes cheese pizza"]
|
||||
- New Memory:
|
||||
{{
|
||||
"memory" : [
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "Name is John",
|
||||
"event" : "NONE"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza",
|
||||
"event" : "DELETE"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
3. **Delete**: If the retrieved facts contain information that contradicts the information present in the memory, then you have to delete it. Or if the direction is to delete the memory, then you have to delete it.
|
||||
Please note to return the IDs in the output from the input IDs only and do not generate any new ID.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Dislikes cheese pizza"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza",
|
||||
"event" : "DELETE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
4. **No Change**: If the retrieved facts contain information that is already present in the memory, then you do not need to make any changes.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "Name is John"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza"
|
||||
}}
|
||||
]
|
||||
- Retrieved facts: ["Name is John"]
|
||||
- New Memory:
|
||||
{{
|
||||
"memory" : [
|
||||
{{
|
||||
"id" : "0",
|
||||
"text" : "Name is John",
|
||||
"event" : "NONE"
|
||||
}},
|
||||
{{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza",
|
||||
"event" : "NONE"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
4. **No Change**: If the retrieved facts contain information that is already present in the memory, then you do not need to make any changes.
|
||||
- **Example**:
|
||||
- Old Memory:
|
||||
[
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza"
|
||||
}
|
||||
]
|
||||
- Retrieved facts: ["Name is John"]
|
||||
- New Memory:
|
||||
{
|
||||
"memory" : [
|
||||
{
|
||||
"id" : "0",
|
||||
"text" : "Name is John",
|
||||
"event" : "NONE"
|
||||
},
|
||||
{
|
||||
"id" : "1",
|
||||
"text" : "Loves cheese pizza",
|
||||
"event" : "NONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def get_update_memory_messages(retrieved_old_memory_dict, response_content, custom_update_memory_prompt=None):
|
||||
if custom_update_memory_prompt is None:
|
||||
global DEFAULT_UPDATE_MEMORY_PROMPT
|
||||
custom_update_memory_prompt = DEFAULT_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:
|
||||
|
||||
``
|
||||
```
|
||||
{retrieved_old_memory_dict}
|
||||
``
|
||||
```
|
||||
|
||||
The new retrieved facts are mentioned in the triple backticks. You have to analyze the new retrieved facts and determine whether these facts should be added, updated, or deleted in the memory.
|
||||
|
||||
```
|
||||
{response_content}
|
||||
```
|
||||
|
||||
|
||||
You must return your response in the following JSON structure only:
|
||||
|
||||
{{
|
||||
"memory" : [
|
||||
{{
|
||||
"id" : "<ID of the memory>", # Use existing ID for updates/deletes, or new ID for additions
|
||||
"text" : "<Content of the memory>", # Content of the memory
|
||||
"event" : "<Operation to be performed>", # Must be "ADD", "UPDATE", "DELETE", or "NONE"
|
||||
"old_memory" : "<Old memory content>" # Required only if the event is "UPDATE"
|
||||
}},
|
||||
...
|
||||
]
|
||||
}}
|
||||
|
||||
Follow the instruction mentioned below:
|
||||
- Do not return anything from the custom few shot prompts provided above.
|
||||
- If the current memory is empty, then you have to add the new retrieved facts to the memory.
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
|
||||
class AzureAISearchConfig(BaseModel):
|
||||
collection_name: str = Field("mem0", description="Name of the collection")
|
||||
service_name: str = Field(None, description="Azure Cognitive Search service name")
|
||||
api_key: str = Field(None, description="API key for the Azure Cognitive Search service")
|
||||
service_name: str = Field(None, description="Azure AI Search service name")
|
||||
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")
|
||||
use_compression: bool = Field(False, description="Whether to use scalar quantization vector compression.")
|
||||
compression_type: Optional[str] = Field(
|
||||
None, description="Type of vector compression to use. Options: 'scalar', 'binary', or None"
|
||||
)
|
||||
use_float16: bool = Field(
|
||||
False,
|
||||
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")
|
||||
@classmethod
|
||||
@@ -16,10 +27,30 @@ class AzureAISearchConfig(BaseModel):
|
||||
allowed_fields = set(cls.model_fields.keys())
|
||||
input_fields = set(values.keys())
|
||||
extra_fields = input_fields - allowed_fields
|
||||
|
||||
# Check for use_compression to provide a helpful error
|
||||
if "use_compression" in extra_fields:
|
||||
raise ValueError(
|
||||
"The parameter 'use_compression' is no longer supported. "
|
||||
"Please use 'compression_type=\"scalar\"' instead of 'use_compression=True' "
|
||||
"or 'compression_type=None' instead of 'use_compression=False'."
|
||||
)
|
||||
|
||||
if extra_fields:
|
||||
raise ValueError(
|
||||
f"Extra fields not allowed: {', '.join(extra_fields)}. Please input only the following fields: {', '.join(allowed_fields)}"
|
||||
f"Extra fields not allowed: {', '.join(extra_fields)}. "
|
||||
f"Please input only the following fields: {', '.join(allowed_fields)}"
|
||||
)
|
||||
|
||||
# Validate compression_type values
|
||||
if "compression_type" in values and values["compression_type"] is not None:
|
||||
valid_types = ["scalar", "binary"]
|
||||
if values["compression_type"].lower() not in valid_types:
|
||||
raise ValueError(
|
||||
f"Invalid compression_type: {values['compression_type']}. "
|
||||
f"Must be one of: {', '.join(valid_types)}, or None"
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
model_config = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user