Compare commits

..
55 Commits
Author SHA1 Message Date
Dev KhantandGitHub df43f904d1 deploy minor version -> 0.1.107rc2 (#2953) 2025-06-13 12:09:54 +05:30
Prateek ChhikaraandGitHub a5a07d711b Updates in client to support summary (#2951) 2025-06-13 12:04:38 +05:30
Dev KhantandGitHub a40268dd51 Fix: Migration in storage and version bump -> -0.1.107 (#2943) 2025-06-11 21:48:43 +05:30
c59752c6d6 Update Categorisation Flow (#2922)
Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
2025-06-11 21:24:15 +05:30
Saket AryanandGitHub aa334fb569 Updated Docs for OMM Hosted Version (#2945) 2025-06-11 08:19:03 -07:00
Antaripa SahaandGitHub 40a5e87022 Livekit Docs Update (#2933) 2025-06-09 10:20:41 -07:00
Akshat JainandGitHub 4dec9ace88 Update support for unique user IDs (#2921) 2025-06-07 21:20:40 +05:30
Dev KhantandGitHub e1dc27276b Formatting and version bump -> 0.1.107 (#2927) 2025-06-07 12:27:22 +05:30
Saket AryanandGitHub 9a12ea7b3c Version Bump/Formatting (#2923) 2025-06-06 21:49:03 +05:30
Mrinank BhowmickandGitHub e10a509645 Added cloudflare vector-store (#2607) 2025-06-06 21:35:40 +05:30
Prateek ChhikaraandGitHub fe3f10adb8 Add Wildcard Character Support Documentation for v2 Memory APIs (#2919) 2025-06-06 12:23:04 +05:30
Akshat JainandGitHub 53c91fb107 Doc : Update Readme Docs for OpenMemory environment setup (#2913) 2025-06-05 21:52:04 +05:30
Prateek ChhikaraandGitHub ecc596b11f fix error of wrong exception (#2911) 2025-06-04 16:56:52 -07:00
Prateek ChhikaraandGitHub be37fca1bb Added threshold to search (#2899) 2025-06-03 02:58:21 -07:00
Dev KhantandGitHub 849452cc93 version bump -> 0.1.104 (#2897) 2025-06-03 01:15:16 +05:30
Dev KhantandGitHub 1f2df450bb Fix: GET_ALL for faiss and opensearch (#2896) 2025-06-03 01:10:07 +05:30
Dev KhantandGitHub 06d86996f2 version bump -> 0.1.103 (#2894) 2025-06-02 22:26:37 +05:30
Dev KhantandGitHub bb14cc42a0 Doc: update for enable_graph and Version bump -> 0.1.103 (#2893) 2025-06-02 22:23:22 +05:30
Saket AryanandGitHub fbee8d5c20 Added Async Mode Param (#2882) 2025-05-30 09:06:41 -07:00
Saket AryanandGitHub 855c322da6 deps(ts-sdk): Updates Google SDK Peer Dependency Version (#2878) 2025-05-30 09:51:01 +05:30
Prateek ChhikaraandGitHub 240acca3de Fix: Improve clarity and conciseness of Graph Memory features documen… (#2874) 2025-05-29 13:47:16 -07:00
Saket AryanandGitHub 7ef1378304 Fixed Broken Links (#2871) 2025-05-29 21:20:30 +05:30
9622ac7dff feat: support openai compatible llm provider by adding baseUrl to config (#2674)
Signed-off-by: frank-zsy <syzhao1988@126.com>
Co-authored-by: Saket Aryan <94069182+whysosaket@users.noreply.github.com>
Co-authored-by: Saket Aryan <saketaryan2002@gmail.com>
2025-05-27 00:25:23 +05:30
Dev KhantandGitHub 8a280b4a54 version bump -> 0.1.102 (#2805) 2025-05-26 23:24:51 +05:30
Antaripa SahaandGitHub 1ba9c71f54 Add support for sarvam-m model (#2802) 2025-05-26 23:19:37 +05:30
Saket AryanandGitHub 5c6fbcaab0 Feature (OpenMemory): Add support for LLM and Embedding Providers in OpenMemory (#2794) 2025-05-25 01:01:23 -07:00
Olivier BlinandGitHub b339cab3c1 Fix: Typos in openmemory MCP tool description (#2793) 2025-05-24 15:17:00 -07:00
Dev KhantandGitHub a952df0953 Doc: Add NOT filter for Search and GetAll V2 (#2785) 2025-05-23 23:29:21 +05:30
Dev KhantandGitHub 6cebddebbe Doc: Mastra and Raycast (#2781) 2025-05-23 16:10:47 +05:30
b3d340f59c Fix: Prevent saving prompt artifacts as memory when no new facts are … (#2744)
Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
2025-05-23 15:05:07 +05:30
Dev KhantandGitHub 78e2efc0f2 Doc: update messages in api reference (#2777) 2025-05-23 14:41:13 +05:30
Saket AryanandGitHub d21970efcc feat(ai-sdk): Added Support for Google Provider in AI SDK (#2771) 2025-05-23 00:37:58 +05:30
Prateek ChhikaraandGitHub 816039036d Improve documentation on role-based memory attribution rules (#2770) 2025-05-22 12:07:18 -07:00
Dev KhantandGitHub faf1a34f70 Doc: announce claude 4 (#2769) 2025-05-22 22:48:04 +05:30
Prateek ChhikaraandGitHub 6986153c90 Improve documentation on role-based memory attribution rules (#2768) 2025-05-22 09:39:50 -07:00
Saket AryanandGitHub 8048e0b32f fix(ts-sdk): Fixed Types from Message Interface (#2763) 2025-05-22 21:56:45 +05:30
Dev KhantandGitHub af1cfd8139 Doc: Update output of Org/Proj creation APIs (#2761) 2025-05-22 15:05:23 +05:30
Dev KhantandGitHub f5c3804f79 Doc: Update API Reference (#2760) 2025-05-22 11:54:44 +05:30
Dev KhantandGitHub 443816365a Doc: Feature docs changes (#2756) 2025-05-22 10:59:18 +05:30
Dev KhantandGitHub 097959d5cc Remove support for passing string as input in the client.add() (#2749) 2025-05-22 10:16:32 +05:30
Tomaz BratanicandGitHub bad6e12972 Add neo4j example (#2738) 2025-05-21 17:58:11 -07:00
Dev KhantandGitHub d85fcda037 Formatting (#2750) 2025-05-22 01:17:29 +05:30
Dev KhantandGitHub dff91154a7 Doc: Update memory export (#2741) 2025-05-21 13:14:34 +05:30
Dev KhantandGitHub c3f3f82a3e Migrate to Hatch and version bump -> 0.1.101 (#2727) 2025-05-20 22:58:51 +05:30
Saket AryanandGitHub 70af43c08c improvement(OMM): Added CurL Command to Easy Install OMM (#2731) 2025-05-20 20:18:07 +05:30
Tomaz BratanicandGitHub 1786d907f7 Add neo4j base label config (#2675) 2025-05-19 18:22:20 -07:00
Prateek ChhikaraandGitHub 12a268da30 Added docs for criteria based filtering (#2726) 2025-05-19 14:52:43 -07:00
Chaithanya KumarandGitHub 0aefdf5251 Refactored collaborative task agent documentation to enhance clarity and simplified (#2725) 2025-05-19 09:58:22 -07:00
Antaripa SahaandGitHub df72245b6b Update Index of Healthcare Example in docs (#2722) 2025-05-19 02:18:14 -07:00
Dev KhantandGitHub fe872d0776 Update Changelog (#2720) 2025-05-19 12:54:15 +05:30
Dev KhantandGitHub 052d31939d version bump -> 0.1.100 (#2719) 2025-05-19 12:27:38 +05:30
Antaripa SahaandGitHub 1c44b675d9 Healthcare assistant using Mem0 and Google ADK (#2705) 2025-05-18 07:50:06 -07:00
Chaithanya KumarandGitHub a1c9a63074 # feat: Add Group Chat Memory Feature support to Python SDK enhancing mem0 (#2669) 2025-05-16 11:08:36 -07:00
931df14e25 fix(OMM): Memories not appearing in MCP clients added from Dashboard (#2704)
Co-authored-by: Deshraj Yadav <deshraj@gatech.edu>
2025-05-16 22:11:22 +05:30
hengandGitHub 1b0d8bdd2e improvement(OMM)- fix the sse failed to connect issue (#2696) 2025-05-16 15:57:46 +05:30
174 changed files with 8326 additions and 2600 deletions
+4 -7
View File
@@ -18,20 +18,17 @@ jobs:
with:
python-version: '3.11'
- name: Install Poetry
- name: Install Hatch
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
pip install hatch
- name: Install dependencies
run: |
cd mem0
poetry install
hatch env create
- name: Build a binary wheel and a source tarball
run: |
cd mem0
poetry build
hatch build --clean
# TODO: Needs to setup mem0 repo on Test PyPI
# - name: Publish distribution 📦 to Test PyPI
+17 -22
View File
@@ -44,25 +44,24 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
uses: snok/install-poetry@v1
with:
version: 1.4.2
virtualenvs-create: true
virtualenvs-in-project: true
- name: Install Hatch
run: pip install hatch
- name: Load cached venv
id: cached-poetry-dependencies
id: cached-hatch-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-mem0-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
key: venv-mem0-${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}
- name: Install dependencies
run: make install_all
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
make install_all
pip install -e ".[test]"
pip install pinecone pinecone-text
if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
- name: Run Formatting
run: |
mkdir -p mem0/.ruff_cache && chmod -R 777 mem0/.ruff_cache
cd mem0 && poetry run ruff check . --select F
mkdir -p .ruff_cache && chmod -R 777 .ruff_cache
hatch run format
- name: Run tests and generate coverage report
run: make test
@@ -79,25 +78,21 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
uses: snok/install-poetry@v1
with:
version: 1.4.2
virtualenvs-create: true
virtualenvs-in-project: true
- name: Install Hatch
run: pip install hatch
- name: Load cached venv
id: cached-poetry-dependencies
id: cached-hatch-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-embedchain-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
key: venv-embedchain-${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}
- name: Install dependencies
run: cd embedchain && make install_all
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
- name: Run Formatting
run: |
mkdir -p embedchain/.ruff_cache && chmod -R 777 embedchain/.ruff_cache
cd embedchain && poetry run ruff check . --select F
cd embedchain && hatch run format
- name: Lint with ruff
run: cd embedchain && make lint
- name: Run tests and generate coverage report
+10 -11
View File
@@ -8,37 +8,36 @@ PROJECT_NAME := mem0ai
all: format sort lint
install:
poetry install
hatch env create
install_all:
poetry install
poetry run pip install ruff==0.6.9 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
pip install ruff==0.6.9 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
google-generativeai elasticsearch opensearch-py vecs pinecone pinecone-text faiss-cpu langchain-community \
upstash-vector azure-search-documents langchain-memgraph
upstash-vector azure-search-documents langchain-memgraph langchain-neo4j rank-bm25
# Format code with ruff
format:
poetry run ruff format mem0/
hatch run format
# Sort imports with isort
sort:
poetry run isort mem0/
hatch run isort mem0/
# Lint code with ruff
lint:
poetry run ruff check mem0/
hatch run lint
docs:
cd docs && mintlify dev
build:
poetry build
hatch build
publish:
poetry publish
hatch publish
clean:
poetry run rm -rf dist
rm -rf dist
test:
poetry run pytest tests
hatch run test
+11 -25
View File
@@ -13,7 +13,7 @@
"import anthropic\n",
"\n",
"# Set up environment variables\n",
"os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # needed for embedding model\n",
"os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # needed for embedding model\n",
"os.environ[\"ANTHROPIC_API_KEY\"] = \"your_anthropic_api_key\""
]
},
@@ -33,7 +33,7 @@
" \"model\": \"claude-3-5-sonnet-latest\",\n",
" \"temperature\": 0.1,\n",
" \"max_tokens\": 2000,\n",
" }\n",
" },\n",
" }\n",
" }\n",
" self.client = anthropic.Client(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n",
@@ -50,11 +50,7 @@
" - Keep track of open issues and follow-ups\n",
" \"\"\"\n",
"\n",
" def store_customer_interaction(self,\n",
" user_id: str,\n",
" message: str,\n",
" response: str,\n",
" metadata: Dict = None):\n",
" def store_customer_interaction(self, user_id: str, message: str, response: str, metadata: Dict = None):\n",
" \"\"\"Store customer interaction in memory.\"\"\"\n",
" if metadata is None:\n",
" metadata = {}\n",
@@ -63,24 +59,17 @@
" metadata[\"timestamp\"] = datetime.now().isoformat()\n",
"\n",
" # Format conversation for storage\n",
" conversation = [\n",
" {\"role\": \"user\", \"content\": message},\n",
" {\"role\": \"assistant\", \"content\": response}\n",
" ]\n",
" conversation = [{\"role\": \"user\", \"content\": message}, {\"role\": \"assistant\", \"content\": response}]\n",
"\n",
" # Store in Mem0\n",
" self.memory.add(\n",
" conversation,\n",
" user_id=user_id,\n",
" metadata=metadata\n",
" )\n",
" self.memory.add(conversation, user_id=user_id, metadata=metadata)\n",
"\n",
" def get_relevant_history(self, user_id: str, query: str) -> List[Dict]:\n",
" \"\"\"Retrieve relevant past interactions.\"\"\"\n",
" return self.memory.search(\n",
" query=query,\n",
" user_id=user_id,\n",
" limit=5 # Adjust based on needs\n",
" limit=5, # Adjust based on needs\n",
" )\n",
"\n",
" def handle_customer_query(self, user_id: str, query: str) -> str:\n",
@@ -112,15 +101,12 @@
" model=\"claude-3-5-sonnet-latest\",\n",
" messages=[{\"role\": \"user\", \"content\": prompt}],\n",
" max_tokens=2000,\n",
" temperature=0.1\n",
" temperature=0.1,\n",
" )\n",
"\n",
" # Store interaction\n",
" self.store_customer_interaction(\n",
" user_id=user_id,\n",
" message=query,\n",
" response=response,\n",
" metadata={\"type\": \"support_query\"}\n",
" user_id=user_id, message=query, response=response, metadata={\"type\": \"support_query\"}\n",
" )\n",
"\n",
" return response.content[0].text"
@@ -203,12 +189,12 @@
" # Get user input\n",
" query = input()\n",
" print(\"Customer:\", query)\n",
" \n",
"\n",
" # Check if user wants to exit\n",
" if query.lower() == 'exit':\n",
" if query.lower() == \"exit\":\n",
" print(\"Thank you for using our support service. Goodbye!\")\n",
" break\n",
" \n",
"\n",
" # Handle the query and print the response\n",
" response = chatbot.handle_customer_query(user_id, query)\n",
" print(\"Support:\", response, \"\\n\\n\")"
+14 -16
View File
@@ -25,7 +25,8 @@
"source": [
"# Set up ENV Vars\n",
"import os\n",
"os.environ['OPENAI_API_KEY'] = \"sk-xxx\"\n"
"\n",
"os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\""
]
},
{
@@ -133,11 +134,9 @@
"assistant_id = os.environ.get(\"ASSISTANT_ID\", None)\n",
"\n",
"# LLM Configuration\n",
"CACHE_SEED = 42 # choose your poison\n",
"CACHE_SEED = 42 # choose your poison\n",
"llm_config = {\n",
" \"config_list\": [\n",
" {\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}\n",
" ],\n",
" \"config_list\": [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}],\n",
" \"cache_seed\": CACHE_SEED,\n",
" \"timeout\": 120,\n",
" \"temperature\": 0.0,\n",
@@ -348,7 +347,7 @@
"source": [
"# Retrieve the memory\n",
"relevant_memories = MEM0_MEMORY_CLIENT.search(user_query, user_id=USER_ID, limit=3)\n",
"relevant_memories_text = '\\n'.join(mem['memory'] for mem in relevant_memories)\n",
"relevant_memories_text = \"\\n\".join(mem[\"memory\"] for mem in relevant_memories)\n",
"print(\"Relevant memories:\")\n",
"print(relevant_memories_text)\n",
"\n",
@@ -389,8 +388,8 @@
"# - Enables more context-aware and personalized agent responses.\n",
"# - Bridges the gap between human input and AI processing in complex workflows.\n",
"\n",
"class Mem0ProxyCoderAgent(UserProxyAgent):\n",
"\n",
"class Mem0ProxyCoderAgent(UserProxyAgent):\n",
" def __init__(self, *args, **kwargs):\n",
" super().__init__(*args, **kwargs)\n",
" self.memory = MEM0_MEMORY_CLIENT\n",
@@ -399,15 +398,14 @@
" def initiate_chat(self, assistant, message):\n",
" # Retrieve memory for the agent\n",
" agent_memories = self.memory.search(message, agent_id=self.agent_id, limit=3)\n",
" agent_memories_txt = '\\n'.join(mem['memory'] for mem in agent_memories)\n",
" agent_memories_txt = \"\\n\".join(mem[\"memory\"] for mem in agent_memories)\n",
" prompt = f\"{message}\\n Coding Preferences: \\n{str(agent_memories_txt)}\"\n",
" response = super().initiate_chat(assistant, message=prompt)\n",
" # Add new memory after processing the message\n",
" response_dist = response.__dict__ if not isinstance(response, dict) else response\n",
" MEMORY_DATA = [{\"role\": \"user\", \"content\": message}, {\"role\": \"assistant\", \"content\": response_dist}]\n",
" self.memory.add(MEMORY_DATA, agent_id=self.agent_id)\n",
" return response\n",
" "
" return response"
]
},
{
@@ -560,12 +558,12 @@
"from cookbooks.helper.mem0_teachability import Mem0Teachability\n",
"\n",
"teachability = Mem0Teachability(\n",
" verbosity=2, # for visibility of what's happening\n",
" recall_threshold=0.5,\n",
" reset_db=False, # Use True to force-reset the memo DB, and False to use an existing DB.\n",
" agent_id=AGENT_ID,\n",
" memory_client = MEM0_MEMORY_CLIENT,\n",
" )\n",
" verbosity=2, # for visibility of what's happening\n",
" recall_threshold=0.5,\n",
" reset_db=False, # Use True to force-reset the memo DB, and False to use an existing DB.\n",
" agent_id=AGENT_ID,\n",
" memory_client=MEM0_MEMORY_CLIENT,\n",
")\n",
"teachability.add_to_agent(user_proxy)"
]
},
+23 -1
View File
@@ -3,12 +3,15 @@ title: 'Get Memories (v2)'
openapi: post /v2/memories/
---
The v2 get memories API is powerful and flexible, allowing for more precise memory listing without the need for a search query. It supports complex logical operations (AND, OR) and comparison operators for advanced filtering capabilities. The comparison operators include:
The v2 get memories API is powerful and flexible, allowing for more precise memory listing without the need for a search query. It supports complex logical operations (AND, OR, NOT) and comparison operators for advanced filtering capabilities. The comparison operators include:
- `in`: Matches any of the values specified
- `gte`: Greater than or equal to
- `lte`: Less than or equal to
- `gt`: Greater than
- `lt`: Less than
- `ne`: Not equal to
- `icontains`: Case-insensitive containment check
- `*`: Wildcard character that matches everything
<CodeGroup>
```python Code
@@ -41,3 +44,22 @@ memories = m.get_all(
]
```
</CodeGroup>
<CodeGroup>
```python Wildcard Example
# Using wildcard to get all memories for a specific user across all run_ids
memories = m.get_all(
filters={
"AND": [
{
"user_id": "alex"
},
{
"run_id": "*"
}
]
},
version="v2"
)
```
</CodeGroup>
@@ -3,7 +3,7 @@ title: 'Search Memories (v2)'
openapi: post /v2/memories/search/
---
The v2 search API is powerful and flexible, allowing for more precise memory retrieval. It supports complex logical operations (AND, OR) and comparison operators for advanced filtering capabilities. The comparison operators include:
The v2 search API is powerful and flexible, allowing for more precise memory retrieval. It supports complex logical operations (AND, OR, NOT) and comparison operators for advanced filtering capabilities. The comparison operators include:
- `in`: Matches any of the values specified
- `gte`: Greater than or equal to
- `lte`: Less than or equal to
@@ -11,6 +11,7 @@ The v2 search API is powerful and flexible, allowing for more precise memory ret
- `lt`: Less than
- `ne`: Not equal to
- `icontains`: Case-insensitive containment check
- `*`: Wildcard character that matches everything
<CodeGroup>
```python Code
@@ -49,3 +50,23 @@ The v2 search API is powerful and flexible, allowing for more precise memory ret
}
```
</CodeGroup>
<CodeGroup>
```python Wildcard Example
# Using wildcard to match all run_ids for a specific user
all_memories = m.search(
query="What are Alice's hobbies?",
version="v2",
filters={
"AND": [
{
"user_id": "alice"
},
{
"run_id": "*"
}
]
},
)
```
</CodeGroup>
+210
View File
@@ -8,6 +8,85 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2025-05-10" description="v0.1.100">
**New Features:**
- **Memory:** Added Group Chat Memory Feature support
- **Examples:** Added Healthcare assistant using Mem0 and Google ADK
**Bug Fixes:**
- **SSE:** Fixed SSE connection issues
- **MCP:** Fixed memories not appearing in MCP clients added from Dashboard
</Update>
<Update label="2025-05-07" description="v0.1.99">
**New Features:**
- **OpenMemory:** Added OpenMemory support
- **Neo4j:** Added weights to Neo4j model
- **AWS:** Added support for Opsearch Serverless
- **Examples:** Added ElizaOS Example
**Improvements:**
- **Documentation:** Updated Azure AI documentation
- **AI SDK:** Added missing parameters and updated demo application
- **OSS:** Fixed AOSS and AWS BedRock LLM
</Update>
<Update label="2025-04-30" description="v0.1.98">
**New Features:**
- **Neo4j:** Added support for Neo4j database
- **AWS:** Added support for AWS Bedrock Embeddings
**Improvements:**
- **Client:** Updated delete_users() to use V2 API endpoints
- **Documentation:** Updated timestamp and dual-identity memory management docs
- **Neo4j:** Improved Neo4j queries and removed warnings
- **AI SDK:** Added support for graceful failure when services are down
**Bug Fixes:**
- Fixed AI SDK filters
- Fixed new memories wrong type
- Fixed duplicated metadata issue while adding/updating memories
</Update>
<Update label="2025-04-23" description="v0.1.97">
**New Features:**
- **HuggingFace:** Added support for HF Inference
**Bug Fixes:**
- Fixed proxy for Mem0
</Update>
<Update label="2025-04-16" description="v0.1.96">
**New Features:**
- **Vercel AI SDK:** Added Graph Memory support
**Improvements:**
- **Documentation:** Fixed timestamp and README links
- **Client:** Updated TS client to use proper types for deleteUsers
- **Dependencies:** Removed unnecessary dependencies from base package
</Update>
<Update label="2025-04-09" description="v0.1.95">
**Improvements:**
- **Client:** Fixed Ping Method for using default org_id and project_id
- **Documentation:** Updated documentation
**Bug Fixes:**
- Fixed mem0-migrations issue
</Update>
<Update label="2025-04-26" description="v0.1.94">
**New Features:**
@@ -209,6 +288,34 @@ mode: "wide"
<Tab title="TypeScript">
<Update label="2025-06-06" description="v2.1.30">
**New Features:**
- **OSS:** Added Cloudflare support
**Improvements:**
- **OSS:** Fixed baseURL param in LLM Config.
</Update>
<Update label="2025-05-30" description="v2.1.29">
**Improvements:**
- **Client:** Added Async Mode Param for `add` method.
</Update>
<Update label="2025-05-30" description="v2.1.28">
**Improvements:**
- **SDK:** Update Google SDK Peer Dependency Version.
</Update>
<Update label="2025-05-27" description="v2.1.27">
**Improvements:**
- **OSS:** Added baseURL param in LLM Config.
</Update>
<Update label="2025-05-23" description="v2.1.26">
**Improvements:**
- **Client:** Removed type `string` from `messages` interface
</Update>
<Update label="2025-05-08" description="v2.1.25">
**Improvements:**
- **Client:** Improved error handling in client.
@@ -330,6 +437,104 @@ mode: "wide"
<Tab title="Platform">
<Update label="2025-05-19" description="">
**Bug Fixes:**
- **Core:** Fixed unicode error in user_id, agent_id, run_id and app_id
</Update>
<Update label="2025-05-17" description="">
**New Features:**
- **Graph:** Added Neo4J Graph Migration
- **API:** Added API to set custom instructions
</Update>
<Update label="2025-05-16" description="">
**New Features:**
- **API:** Added Org-wide API Limit and Usage
**Improvements:**
- **Database:** Added migration for "is_deleted" column
- **Graph:** Improved graph queries
</Update>
<Update label="2025-05-15" description="">
**New Features:**
- **Lambda:** Added actions to lambda
- **Core:** Added background runs support
- **Models:** Added o4-mini for pro users
</Update>
<Update label="2025-05-10" description="">
**New Features:**
- **Integrations:** Added Intercom Events integration
- **Billing:** Added prefilled email for payments
- **Organizations:** Added Pro organization marking
**Improvements:**
- **UI:** Fixed loading jitter for organization selection
- **Infrastructure:** Improved production scaling
</Update>
<Update label="2025-05-09" description="">
**Improvements:**
- **Memory:** Fixed filters in Memory Page
- **Deployment:** Added custom categories for on-premise
</Update>
<Update label="2025-05-08" description="">
**Improvements:**
- **Backend:** Updated Django settings for metrics
- **Memory:** Added retries to memory filtering
- **Search:** Added scoring mechanism
</Update>
<Update label="2025-05-07" description="">
**Improvements:**
- **Deployment:** Updated deployment scripts
- **Testing:** Added code coverage tracking
- **Memory:** Added background cron job for memory quality
</Update>
<Update label="2025-05-06" description="">
**New Features:**
- **Models:** Added support for 4.1-mini model
**Improvements:**
- **Infrastructure:** Increased instance count
- **API:** Added V2 for Manage Entities
</Update>
<Update label="2025-05-04" description="">
**New Features:**
- **Testing:** Added code coverage tracking
- **AI:** Added Keywords AI integration
**Improvements:**
- **UI:** Updated UI with tabs
- **Database:** Added migrations for custom instructions
- **Search:** Added criteria filtering
</Update>
<Update label="2025-04-26" description="">
**Improvements:**
@@ -487,6 +692,11 @@ mode: "wide"
<Tab title="Vercel AI SDK">
<Update label="2025-05-23" description="v1.0.5">
**New Features:**
- **Vercel AI SDK:** Added support for Google provider.
</Update>
<Update label="2025-05-10" description="v1.0.4">
**New Features:**
- **Vercel AI SDK:** Added support for new param `output_format`.
+6
View File
@@ -110,6 +110,12 @@ 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 |
| `sarvam_base_url` | Base URL for Sarvam API | Sarvam |
| `reasoning_effort` | Reasoning level (low, medium, high) | Sarvam |
| `frequency_penalty` | Penalize frequent tokens (-2.0 to 2.0) | Sarvam |
| `presence_penalty` | Penalize existing tokens (-2.0 to 2.0) | Sarvam |
| `seed` | Seed for deterministic sampling | Sarvam |
| `stop` | Stop sequences (max 4) | Sarvam |
| `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
</Tab>
<Tab title="TypeScript">
+2 -2
View File
@@ -20,7 +20,7 @@ config = {
"llm": {
"provider": "anthropic",
"config": {
"model": "claude-3-7-sonnet-latest",
"model": "claude-sonnet-4-20250514",
"temperature": 0.1,
"max_tokens": 2000,
}
@@ -45,7 +45,7 @@ const config = {
provider: 'anthropic',
config: {
apiKey: process.env.ANTHROPIC_API_KEY || '',
model: 'claude-3-7-sonnet-latest',
model: 'claude-sonnet-4-20250514',
temperature: 0.1,
maxTokens: 2000,
},
+75
View File
@@ -0,0 +1,75 @@
---
title: Sarvam AI
---
<Snippet file="paper-release.mdx" />
**Sarvam AI** is an Indian AI company developing language models with a focus on Indian languages and cultural context. Their latest model **Sarvam-M** is designed to understand and generate content in multiple Indian languages while maintaining high performance in English.
To use Sarvam AI's models, please set the `SARVAM_API_KEY` which you can get from their [platform](https://dashboard.sarvam.ai/).
## Usage
```python
import os
from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your-api-key" # used for embedding model
os.environ["SARVAM_API_KEY"] = "your-api-key"
config = {
"llm": {
"provider": "sarvam",
"config": {
"model": "sarvam-m",
"temperature": 0.7,
}
}
}
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="alex")
```
## Advanced Usage with Sarvam-Specific Features
```python
import os
from mem0 import Memory
config = {
"llm": {
"provider": "sarvam",
"config": {
"model": {
"name": "sarvam-m",
"reasoning_effort": "high", # Enable advanced reasoning
"frequency_penalty": 0.1, # Reduce repetition
"seed": 42 # For deterministic outputs
},
"temperature": 0.3,
"max_tokens": 2000,
"api_key": "your-sarvam-api-key"
}
}
}
m = Memory.from_config(config)
# Example with Hindi conversation
messages = [
{"role": "user", "content": "मैं SBI में joint account खोलना चाहता हूँ।"},
{"role": "assistant", "content": "SBI में joint account खोलने के लिए आपको कुछ documents की जरूरत होगी। क्या आप जानना चाहते हैं कि कौन से documents चाहिए?"}
]
m.add(messages, user_id="rajesh", metadata={"language": "hindi", "topic": "banking"})
```
## Config
All available parameters for the `sarvam` config are present in [Master List of All Params in Config](../config).
+1
View File
@@ -34,6 +34,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="Sarvam AI" href="/components/llms/models/sarvam" />
<Card title="LM Studio" href="/components/llms/models/lmstudio" />
<Card title="Langchain" href="/components/llms/models/langchain" />
</CardGroup>
@@ -0,0 +1,45 @@
[Cloudflare Vectorize](https://developers.cloudflare.com/vectorize/) is a vector database offering from Cloudflare, allowing you to build AI-powered applications with vector embeddings.
### Usage
<CodeGroup>
```typescript TypeScript
import { Memory } from 'mem0ai/oss';
const config = {
vectorStore: {
provider: 'vectorize',
config: {
indexName: 'my-memory-index',
accountId: 'your-cloudflare-account-id',
apiKey: 'your-cloudflare-api-key',
dimension: 1536, // Optional: defaults to 1536
},
},
};
const memory = new Memory(config);
const messages = [
{"role": "user", "content": "I'm looking for a good book to read."},
{"role": "assistant", "content": "Sure, what genre are you interested in?"},
{"role": "user", "content": "I enjoy fantasy novels with strong world-building."},
{"role": "assistant", "content": "Great! I'll keep that in mind for future recommendations."}
]
await memory.add(messages, { userId: "bob", metadata: { interest: "books" } });
```
</CodeGroup>
### Config
Let's see the available parameters for the `vectorize` config:
<Tabs>
<Tab title="TypeScript">
| Parameter | Description | Default Value |
| --- | --- | --- |
| `indexName` | The name of the Vectorize index | `None` (Required) |
| `accountId` | Your Cloudflare account ID | `None` (Required) |
| `apiKey` | Your Cloudflare API token | `None` (Required) |
| `dimension` | Dimensions of the embedding model | `1536` |
</Tab>
</Tabs>
+1 -1
View File
@@ -13,7 +13,7 @@ Mem0 includes built-in support for various popular databases. Memory can utilize
See the list of supported vector databases below.
<Note>
The following vector databases are supported in the Python implementation. The TypeScript implementation currently only supports Qdrant, Redis and in-memory vector database.
The following vector databases are supported in the Python implementation. The TypeScript implementation currently only supports Qdrant, Redis,Vectorize and in-memory vector database.
</Note>
<CardGroup cols={3}>
+5 -5
View File
@@ -29,7 +29,7 @@ For detailed guidance on pull requests, refer to [GitHub's documentation](https:
## 📦 Dependency Management
We use `poetry` as our package manager. Install it by following the [official instructions](https://python-poetry.org/docs/#installation).
We use `hatch` as our package manager. Install it by following the [official instructions](https://hatch.pypa.io/latest/install/).
⚠️ **Do NOT use `pip` or `conda` for dependency management.** Instead, run:
@@ -37,7 +37,7 @@ We use `poetry` as our package manager. Install it by following the [official in
make install_all
# Activate virtual environment
poetry shell
hatch shell
```
---
@@ -60,9 +60,9 @@ Run the linter and fix any reported issues before submitting your PR:
make lint
```
### 🎨 Code Formatting with `black`
### 🎨 Code Formatting
To maintain a consistent code style, format your code using `black`:
To maintain a consistent code style, format your code:
```bash
make format
@@ -76,7 +76,7 @@ Run tests to verify functionality before submitting your PR:
make test
```
💡 **Note:** Some dependencies have been removed from Poetry to reduce package size. Run `make install_all` to install necessary dependencies before running tests.
💡 **Note:** Some dependencies have been removed from the main dependencies to reduce package size. Run `make install_all` to install necessary dependencies before running tests.
---
+26 -20
View File
@@ -45,21 +45,22 @@
"group": "Features",
"icon": "star",
"pages": [
"features/platform-overview",
"features/advanced-retrieval",
"features/contextual-add",
"features/multimodal-support",
"features/timestamp",
"features/selective-memory",
"features/custom-categories",
"features/custom-instructions",
"features/direct-import",
"features/async-client",
"features/memory-export",
"features/webhooks",
"features/graph-memory",
"features/feedback-mechanism",
"features/expiration-date"
"platform/features/platform-overview",
"platform/features/advanced-retrieval",
"platform/features/criteria-retrieval",
"platform/features/contextual-add",
"platform/features/multimodal-support",
"platform/features/timestamp",
"platform/features/selective-memory",
"platform/features/custom-categories",
"platform/features/custom-instructions",
"platform/features/direct-import",
"platform/features/async-client",
"platform/features/memory-export",
"platform/features/webhooks",
"platform/features/graph-memory",
"platform/features/feedback-mechanism",
"platform/features/expiration-date"
]
}
]
@@ -76,10 +77,10 @@
"icon": "wrench",
"pages": [
"open-source/features/async-memory",
"features/openai_compatibility",
"features/custom-fact-extraction-prompt",
"features/custom-update-memory-prompt",
"open-source/multimodal-support",
"open-source/features/openai_compatibility",
"open-source/features/custom-fact-extraction-prompt",
"open-source/features/custom-update-memory-prompt",
"open-source/features/multimodal-support",
"open-source/features/rest-api"
]
},
@@ -114,6 +115,7 @@
"components/llms/models/gemini",
"components/llms/models/deepseek",
"components/llms/models/xAI",
"components/llms/models/sarvam",
"components/llms/models/lmstudio",
"components/llms/models/langchain"
]
@@ -203,6 +205,7 @@
"examples/aws_example",
"examples/mem0-demo",
"examples/ai_companion_js",
"examples/collaborative-task-agent",
"examples/eliza_os",
"examples/mem0-mastra",
"examples/mem0-with-ollama",
@@ -217,6 +220,7 @@
"examples/mem0-agentic-tool",
"examples/openai-inbuilt-tools",
"examples/mem0-openai-voice-demo",
"examples/mem0-google-adk-healthcare-assistant",
"examples/email_processing",
"examples/youtube-assistant"
]
@@ -245,7 +249,9 @@
"integrations/elevenlabs",
"integrations/pipecat",
"integrations/agno",
"integrations/keywords"
"integrations/keywords",
"integrations/raycast",
"integrations/mastra"
]
}
]
+5 -1
View File
@@ -75,7 +75,11 @@ Explore how **Mem0** can power real-world applications and bring personalized, i
<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>
<Card title="Healthcare Assistant Google ADK" icon="microphone" href="/examples/mem0-google-adk-healthcare-assistant">
Build a personalized healthcare assistant with persistent memory using Google's ADK and Mem0.
</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.
+125
View File
@@ -0,0 +1,125 @@
---
title: Multi-User Collaboration with Mem0
---
<Snippet file="paper-release.mdx" />
## Overview
Build a multi-user collaborative chat or task management system with Mem0. Each message is attributed to its author, and all messages are stored in a shared project space. Mem0 makes it easy to track contributions, sort and group messages, and collaborate in real time.
## Setup
Install the required packages:
```bash
pip install openai mem0ai
```
## Full Code Example
```python
from openai import OpenAI
from mem0 import Memory
import os
from datetime import datetime
from collections import defaultdict
# Set your OpenAI API key
os.environ["OPENAI_API_KEY"] = "sk-your-key"
# Shared project context
RUN_ID = "project-demo"
# Initialize Mem0
mem = Memory()
class CollaborativeAgent:
def __init__(self, run_id):
self.run_id = run_id
self.mem = mem
def add_message(self, role, name, content):
msg = {"role": role, "name": name, "content": content}
self.mem.add([msg], run_id=self.run_id, infer=False)
def brainstorm(self, prompt):
# Get recent messages for context
memories = self.mem.search(prompt, run_id=self.run_id, limit=5)["results"]
context = "\n".join(f"- {m['memory']} (by {m.get('actor_id', 'Unknown')})" for m in memories)
client = OpenAI()
messages = [
{"role": "system", "content": "You are a helpful project assistant."},
{"role": "user", "content": f"Prompt: {prompt}\nContext:\n{context}"}
]
reply = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
).choices[0].message.content.strip()
self.add_message("assistant", "assistant", reply)
return reply
def get_all_messages(self):
return self.mem.get_all(run_id=self.run_id)["results"]
def print_sorted_by_time(self):
messages = self.get_all_messages()
messages.sort(key=lambda m: m.get('created_at', ''))
print("\n--- Messages (sorted by time) ---")
for m in messages:
who = m.get("actor_id") or "Unknown"
ts = m.get('created_at', 'Timestamp N/A')
try:
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
ts_fmt = dt.strftime('%Y-%m-%d %H:%M:%S')
except Exception:
ts_fmt = ts
print(f"[{ts_fmt}] [{who}] {m['memory']}")
def print_grouped_by_actor(self):
messages = self.get_all_messages()
grouped = defaultdict(list)
for m in messages:
grouped[m.get("actor_id") or "Unknown"].append(m)
print("\n--- Messages (grouped by actor) ---")
for actor, mems in grouped.items():
print(f"\n=== {actor} ===")
for m in mems:
ts = m.get('created_at', 'Timestamp N/A')
try:
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
ts_fmt = dt.strftime('%Y-%m-%d %H:%M:%S')
except Exception:
ts_fmt = ts
print(f"[{ts_fmt}] {m['memory']}")
```
## Usage
```python
# Example usage
agent = CollaborativeAgent(RUN_ID)
agent.add_message("user", "alice", "Let's list tasks for the new landing page.")
agent.add_message("user", "bob", "I'll own the hero section copy.")
agent.add_message("user", "carol", "I'll choose product screenshots.")
# Brainstorm with context
print("\nAssistant reply:\n", agent.brainstorm("What are the current open tasks?"))
# Print all messages sorted by time
agent.print_sorted_by_time()
# Print all messages grouped by actor
agent.print_grouped_by_actor()
```
## Key Points
- Each message is attributed to a user or agent (actor)
- All messages are stored in a shared project space (`run_id`)
- You can sort messages by time, group by actor, and format timestamps for clarity
- Mem0 makes it easy to build collaborative, attributed chat/task systems
## Conclusion
Mem0 enables fast, transparent collaboration for teams and agents, with full attribution, flexible memory search, and easy message organization.
@@ -0,0 +1,291 @@
---
title: 'Healthcare Assistant with Mem0 and Google ADK'
description: 'Build a personalized healthcare agent that remembers patient information across conversations using Mem0 and Google ADK'
---
<Snippet file="paper-release.mdx" />
# Healthcare Assistant with Memory
This example demonstrates how to build a healthcare assistant that remembers patient information across conversations using Google ADK and Mem0.
## Overview
The Healthcare Assistant helps patients by:
- Remembering their medical history and symptoms
- Providing general health information
- Scheduling appointment reminders
- Maintaining a personalized experience across conversations
By integrating Mem0's memory layer with Google ADK, the assistant maintains context about the patient without requiring them to repeat information.
## Setup
Before you begin, make sure you have:
Installed Google ADK and Mem0 SDK:
```bash
pip install google-adk
pip install mem0ai
```
## Code Breakdown
Let's get started and understand the different components required in building a healthcare assistant powered by memory
```python
# Import dependencies
import os
from google.adk.agents import Agent
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.genai import types
from mem0 import MemoryClient
# Set up API keys (replace with your actual keys)
os.environ["GOOGLE_API_KEY"] = "your-google-api-key"
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
# Define a global user ID for simplicity
USER_ID = "Alex"
# Initialize Mem0 client
mem0_client = MemoryClient()
```
## Define Memory Tools
First, we'll create tools that allow our agent to store and retrieve information using Mem0:
```python
def save_patient_info(information: str) -> dict:
"""Saves important patient information to memory."""
# Store in Mem0
response = mem0_client.add(
[{"role": "user", "content": information}],
user_id=USER_ID,
run_id="healthcare_session",
metadata={"type": "patient_information"}
)
def retrieve_patient_info(query: str) -> dict:
"""Retrieves relevant patient information from memory."""
# Search Mem0
results = 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 results and len(results) > 0:
memories = [memory["memory"] for memory in results.get('results', [])]
return {
"status": "success",
"memories": memories,
"count": len(memories)
}
else:
return {
"status": "no_results",
"memories": [],
"count": 0
}
```
## Define Healthcare Tools
Next, we'll add tools specific to healthcare assistance:
```python
def schedule_appointment(date: str, time: str, reason: str) -> dict:
"""Schedules a doctor's appointment."""
# In a real app, this would connect to a scheduling system
appointment_id = f"APT-{hash(date + time) % 10000}"
return {
"status": "success",
"appointment_id": appointment_id,
"confirmation": f"Appointment scheduled for {date} at {time} for {reason}",
"message": "Please arrive 15 minutes early to complete paperwork."
}
```
## Create the Healthcare Assistant Agent
Now we'll create our main agent with all the tools:
```python
# Create the agent
healthcare_agent = Agent(
name="healthcare_assistant",
model="gemini-1.5-flash", # Using Gemini for healthcare assistant
description="Healthcare assistant that helps patients with health information and appointment scheduling.",
instruction="""You are a helpful Healthcare Assistant with memory capabilities.
Your primary responsibilities are to:
1. Remember patient information using the 'save_patient_info' tool when they share symptoms, conditions, or preferences.
2. Retrieve past patient information using the 'retrieve_patient_info' tool when relevant to the current conversation.
3. Help schedule appointments using the 'schedule_appointment' tool.
IMPORTANT GUIDELINES:
- Always be empathetic, professional, and helpful.
- Save important patient information like symptoms, conditions, allergies, and preferences.
- Check if you have relevant patient information before asking for details they may have shared previously.
- Make it clear you are not a doctor and cannot provide medical diagnosis or treatment.
- For serious symptoms, always recommend consulting a healthcare professional.
- Keep all patient information confidential.
""",
tools=[save_patient_info, retrieve_patient_info, schedule_appointment]
)
```
## Set Up Session and Runner
```python
# Set up Session Service and Runner
session_service = InMemorySessionService()
# Define constants for the conversation
APP_NAME = "healthcare_assistant_app"
USER_ID = "Alex"
SESSION_ID = "session_001"
# Create a session
session = session_service.create_session(
app_name=APP_NAME,
user_id=USER_ID,
session_id=SESSION_ID
)
# Create the runner
runner = Runner(
agent=healthcare_agent,
app_name=APP_NAME,
session_service=session_service
)
```
## Interact with the Healthcare Assistant
```python
# Function to interact with the agent
async def call_agent_async(query, runner, user_id, session_id):
"""Sends a query to the agent and returns the final response."""
print(f"\n>>> Patient: {query}")
# Format the user's message
content = types.Content(
role='user',
parts=[types.Part(text=query)]
)
# Set user_id for tools to access
save_patient_info.user_id = user_id
retrieve_patient_info.user_id = user_id
# Run the agent
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=content
):
if event.is_final_response():
if event.content and event.content.parts:
response = event.content.parts[0].text
print(f"<<< Assistant: {response}")
return response
return "No response received."
# Example conversation flow
async def run_conversation():
# First interaction - patient introduces themselves with key information
await call_agent_async(
"Hi, I'm Alex. I've been having headaches for the past week, and I have a penicillin allergy.",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID
)
# Request for health information
await call_agent_async(
"Can you tell me more about what might be causing my headaches?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID
)
# Schedule an appointment
await call_agent_async(
"I think I should see a doctor. Can you help me schedule an appointment for next Monday at 2pm?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID
)
# Test memory - should remember patient name, symptoms, and allergy
await call_agent_async(
"What medications should I avoid for my headaches?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID
)
# Run the conversation example
if __name__ == "__main__":
asyncio.run(run_conversation())
```
## How It Works
This healthcare assistant demonstrates several key capabilities:
1. **Memory Storage**: When Alex mentions her headaches and penicillin allergy, the agent stores this information in Mem0 using the `save_patient_info` tool.
2. **Contextual Retrieval**: When Alex asks about headache causes, the agent uses the `retrieve_patient_info` tool to recall her specific situation.
3. **Memory Application**: When discussing medications, the agent remembers Alex's penicillin allergy without her needing to repeat it, providing safer and more personalized advice.
4. **Conversation Continuity**: The agent maintains context across the entire conversation session, creating a more natural and efficient interaction.
## Key Implementation Details
### User ID Management
Instead of passing the user ID as a parameter to the memory tools (which would require modifying the ADK's tool calling system), we attach it directly to the function object:
```python
# Set user_id for tools to access
save_patient_info.user_id = user_id
retrieve_patient_info.user_id = user_id
```
Inside the tool functions, we retrieve this attribute:
```python
# Get user_id from session state or use default
user_id = getattr(save_patient_info, 'user_id', 'default_user')
```
This approach allows our tools to maintain user context without complicating their parameter signatures.
### Mem0 Integration
The integration with Mem0 happens through two primary functions:
1. `mem0_client.add()` - Stores new information with appropriate metadata
2. `mem0_client.search()` - Retrieves relevant memories using semantic search
The `threshold` parameter in the search function ensures that only highly relevant memories are returned.
## Conclusion
This example demonstrates how to build a healthcare assistant with persistent memory using Google ADK and Mem0. The integration allows for a more personalized patient experience by maintaining context across conversation turns, which is particularly valuable in healthcare scenarios where continuity of information is crucial.
By storing and retrieving patient information intelligently, the assistant provides more relevant responses without requiring the patient to repeat their medical history, symptoms, or preferences.
+56
View File
@@ -322,4 +322,60 @@ Here are the available integrations for Mem0:
>
Build AI applications with persistent memory and comprehensive LLM observability.
</Card>
<Card
title="Raycast"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M3 12L21 12M12 3L12 21M7.5 7.5L16.5 16.5M16.5 7.5L7.5 16.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
}
href="/integrations/raycast"
>
Mem0 Raycast extension for intelligent memory management and retrieval.
</Card>
<Card
title="Mastra"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M12 2L22 7L12 12L2 7L12 2Z"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
<path
d="M2 17L12 22L22 17"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
<path
d="M2 12L12 17L22 12"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
</svg>
}
href="/integrations/mastra"
>
Build AI agents with persistent memory using Mastra's framework and tools.
</Card>
</CardGroup>
+147 -117
View File
@@ -12,8 +12,7 @@ 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 \
pip install livekit-agents[voice] \
livekit-plugins-silero \
livekit-plugins-deepgram \
livekit-plugins-openai
@@ -38,7 +37,7 @@ OPENAI_API_KEY=your_openai_api_key
## Code Breakdown
Let's break down the key components of this implementation:
Let's break down the key components of this implementation using LiveKit Agents:
### 1. Setting Up Dependencies and Environment
@@ -51,17 +50,19 @@ from typing import List, Dict, Any, Annotated
import aiohttp
from dotenv import load_dotenv
from livekit.agents import (
Agent,
AgentSession,
AutoSubscribe,
JobContext,
JobProcess,
WorkerOptions,
cli,
llm,
metrics,
function_tool,
RunContext,
cli,
WorkerOptions,
ModelSettings,
)
from livekit import rtc, api
from livekit.agents.pipeline import VoicePipelineAgent
from livekit.plugins import deepgram, openai, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel
from mem0 import AsyncMemoryClient
# Load environment variables
@@ -88,36 +89,45 @@ This section handles:
### 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"""
async def _enrich_with_memory(chat_ctx: llm.ChatContext):
"""Add memories and augment chat context with relevant memories"""
if not chat_ctx.messages:
return
# Store user message in Mem0
# Get the latest user message
user_msg = chat_ctx.messages[-1]
if user_msg.role != "user":
return
user_content = user_msg.text_content()
if not user_content:
return
# Store user message in Mem0
await mem0.add(
[{"role": "user", "content": user_msg.content}],
[{"role": "user", "content": user_content}],
user_id=USER_ID
)
# Search for relevant memories
results = await mem0.search(
user_msg.content,
user_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(
# Add memory context as a assistant message
memory_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[-1] = memory_msg
chat_ctx.messages.append(user_msg)
```
@@ -130,62 +140,45 @@ This function:
### 3. Prewarm and Entrypoint Functions
```python
def prewarm_process(proc: JobProcess):
# Preload silero VAD in memory to speed up session start
def prewarm_process(proc):
"""Preload components to speed up session start"""
proc.userdata["vad"] = silero.VAD.load()
async def entrypoint(ctx: JobContext):
"""Main entrypoint for the memory-enabled voice agent"""
# 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(),
# Create agent session with modern 1.0 architecture
session = AgentSession(
stt=deepgram.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
before_llm_cb=_enrich_with_memory,
vad=silero.VAD.load(),
turn_detection=MultilingualModel(),
)
# 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
# Create memory-enabled agent
agent = MemoryEnabledAgent()
# Start the session
await session.start(
room=ctx.room,
agent=agent,
)
# Run the application
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm_process))
# Initial greeting
await session.generate_reply(
instructions="Greet the user warmly as George the travel guide and ask how you can help them plan their next adventure."
)
```
The entrypoint function:
- Connects to LiveKit room
- Initializes Mem0 memory client
- Sets up initial system context
- Creates a VoicePipelineAgent with memory enrichment
- Create agent session using `AgentSession` orchestrator with memory enrichment
- Uses modern turn detection with `MultilingualModel()`
- Starts the agent with an initial greeting
## Create a Memory-Enabled Voice Agent
@@ -196,22 +189,22 @@ Now that we've explained each component, here's the complete implementation that
import asyncio
import logging
import os
from typing import List, Dict, Any, Annotated
from typing import AsyncIterable, Any
import aiohttp
from dotenv import load_dotenv
from livekit.agents import (
AutoSubscribe,
Agent,
AgentSession,
JobContext,
JobProcess,
WorkerOptions,
cli,
llm,
metrics,
function_tool,
RunContext,
cli,
WorkerOptions,
ModelSettings,
)
from livekit import rtc, api
from livekit.agents.pipeline import VoicePipelineAgent
from livekit.plugins import deepgram, openai, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel
from mem0 import AsyncMemoryClient
# Load environment variables
@@ -227,92 +220,129 @@ 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()
class MemoryEnabledAgent(Agent):
"""Travel guide agent with Mem0 memory integration"""
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"""
def __init__(self):
super().__init__(
instructions="""
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.
"""
)
async def llm_node(
self,
chat_ctx: llm.ChatContext,
tools: list[llm.FunctionTool],
model_settings: ModelSettings,
) -> AsyncIterable[llm.ChatChunk]:
"""Override LLM node to add memory enrichment before inference"""
# Enrich context with memory before LLM inference
await self._enrich_with_memory(chat_ctx)
# Call default LLM node with enriched context
async for chunk in Agent.default.llm_node(self, chat_ctx, tools, model_settings):
yield chunk
async def _enrich_with_memory(self, chat_ctx: llm.ChatContext):
"""Add memories and augment chat context with relevant memories"""
if not chat_ctx.messages:
return
# Store user message in Mem0
# Get the latest user message
user_msg = chat_ctx.messages[-1]
if user_msg.role != "user":
return
user_content = user_msg.text_content()
if not user_content:
return
# Store user message in Mem0
await mem0.add(
[{"role": "user", "content": user_msg.content}],
[{"role": "user", "content": user_content}],
user_id=USER_ID
)
# Search for relevant memories
results = await mem0.search(
user_msg.content,
user_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(
# Add memory context as a assistant message
memory_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[-1] = memory_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.
"""
),
)
def prewarm_process(proc):
"""Preload components to speed up session start"""
proc.userdata["vad"] = silero.VAD.load()
# Create VoicePipelineAgent with memory capabilities
agent = VoicePipelineAgent(
chat_ctx=initial_ctx,
vad=silero.VAD.load(),
async def entrypoint(ctx: JobContext):
"""Main entrypoint for the memory-enabled voice agent"""
# Connect to LiveKit room
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
# Initialize Mem0 client
mem0 = AsyncMemoryClient()
# Create agent session with modern 1.0 architecture
session = AgentSession(
stt=deepgram.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
before_llm_cb=_enrich_with_memory,
vad=silero.VAD.load(),
turn_detection=MultilingualModel(),
)
# 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? ",
# Create memory-enabled agent
agent = MemoryEnabledAgent()
# Start the session
await session.start(
room=ctx.room,
agent=agent,
)
# Initial greeting
await session.generate_reply(
instructions="Greet the user warmly as George the travel guide and ask how you can help them plan their next adventure.",
allow_interruptions=True
)
# Run the application
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm_process))
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
2. **Voice Interaction**: Leverages LiveKit for voice communication with proper turn detection
3. **Intelligent Context Management**: Augments conversations with past interactions
4. **Travel Planning Specialization**: Focused on creating a helpful travel guide assistant
5. **Function Tools**: Modern tool definition for enhanced capabilities
## Running the Example
@@ -325,13 +355,13 @@ To run this example:
```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.
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
3. **Relevant Memory Filtering**: Use semantic search to retrieve only the most relevant memories
4. **Error Handling**: Implement robust error handling for memory operations
## Debugging Function Tools
+136
View File
@@ -0,0 +1,136 @@
---
title: Mastra
---
<Snippet file="paper-release.mdx" />
The [**Mastra**](https://mastra.ai/) integration demonstrates how to use Mastra's agent system with Mem0 as the memory backend through custom tools. This enables agents to remember and recall information across conversations.
## Overview
In this guide, we'll create a Mastra agent that:
1. Uses Mem0 to store information using a memory tool
2. Retrieves relevant memories using a search tool
3. Provides personalized responses based on past interactions
4. Maintains context across conversations and sessions
## Setup and Configuration
Install the required libraries:
```bash
npm install @mastra/core @mastra/mem0 @ai-sdk/openai zod
```
Set up your environment variables:
<Note>Remember to get the Mem0 API key from [Mem0 Platform](https://app.mem0.ai).</Note>
```bash
MEM0_API_KEY=your-mem0-api-key
OPENAI_API_KEY=your-openai-api-key
```
## Initialize Mem0 Integration
Import required modules and set up the Mem0 integration:
```typescript
import { Mem0Integration } from '@mastra/mem0';
import { createTool } from '@mastra/core/tools';
import { Agent } from '@mastra/core/agent';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Initialize Mem0 integration
const mem0 = new Mem0Integration({
config: {
apiKey: process.env.MEM0_API_KEY || '',
user_id: 'alice', // Unique user identifier
},
});
```
## Create Memory Tools
Set up tools for memorizing and remembering information:
```typescript
// Tool for remembering saved memories
const mem0RememberTool = createTool({
id: 'Mem0-remember',
description: "Remember your agent memories that you've previously saved using the Mem0-memorize tool.",
inputSchema: z.object({
question: z.string().describe('Question used to look up the answer in saved memories.'),
}),
outputSchema: z.object({
answer: z.string().describe('Remembered answer'),
}),
execute: async ({ context }) => {
console.log(`Searching memory "${context.question}"`);
const memory = await mem0.searchMemory(context.question);
console.log(`\nFound memory "${memory}"\n`);
return {
answer: memory,
};
},
});
// Tool for saving new memories
const mem0MemorizeTool = createTool({
id: 'Mem0-memorize',
description: 'Save information to mem0 so you can remember it later using the Mem0-remember tool.',
inputSchema: z.object({
statement: z.string().describe('A statement to save into memory'),
}),
execute: async ({ context }) => {
console.log(`\nCreating memory "${context.statement}"\n`);
// To reduce latency, memories can be saved async without blocking tool execution
void mem0.createMemory(context.statement).then(() => {
console.log(`\nMemory "${context.statement}" saved.\n`);
});
return { success: true };
},
});
```
## Create Mastra Agent
Initialize an agent with memory tools and clear instructions:
```typescript
// Create an agent with memory tools
const mem0Agent = new Agent({
name: 'Mem0 Agent',
instructions: `
You are a helpful assistant that has the ability to memorize and remember facts using Mem0.
Use the Mem0-memorize tool to save important information that might be useful later.
Use the Mem0-remember tool to recall previously saved information when answering questions.
`,
model: openai('gpt-4o'),
tools: { mem0RememberTool, mem0MemorizeTool },
});
```
## Key Features
1. **Tool-based Memory Control**: The agent decides when to save and retrieve information using specific tools
2. **Semantic Search**: Mem0 finds relevant memories based on semantic similarity, not just exact matches
3. **User-specific Memory Spaces**: Each user_id maintains separate memory contexts
4. **Asynchronous Saving**: Memories are saved in the background to reduce response latency
5. **Cross-conversation Persistence**: Memories persist across different conversation threads
6. **Transparent Operations**: Memory operations are visible through tool usage
## Conclusion
By integrating Mastra with Mem0, you can build intelligent agents that learn and remember information across conversations. The tool-based approach provides transparency and control over memory operations, making it easy to create personalized and context-aware AI experiences.
## Help
- For more details on Mastra, visit the [Mastra documentation](https://docs.mastra.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" />
+47
View File
@@ -0,0 +1,47 @@
---
title: "Raycast Extension"
description: "Mem0 Raycast extension for intelligent memory management"
---
# Mem0
Mem0 is a self-improving memory layer for LLM applications, enabling personalized AI experiences that save costs and delight users. This extension lets you store and retrieve text snippets using Mem0's intelligent memory system. Find Mem0 in [Raycast Store](https://www.raycast.com/dev_khant/mem0) for using it.
## 🚀 Getting Started
**Get your API Key**: You'll need a Mem0 API key to use this extension:
a. Sign up at [app.mem0.ai](https://app.mem0.ai)
b. Navigate to your API Keys page
c. Copy your API key
d. Enter this key in the extension preferences
**Basic Usage**:
- Store memories and text snippets
- Retrieve context-aware information
- Manage persistent user preferences
- Search through stored memories
## ✨ Features
**Remember Everything**: Never lose important information - store notes, preferences, and conversations that your AI can recall later
**Smart Connections**: Automatically links related topics, just like your brain does - helping you discover useful connections
**Cost Saver**: Spend less on AI usage by efficiently retrieving relevant information instead of regenerating responses
## 🔑 How This Helps You
**More Personal Experience**: Your AI remembers your preferences and past conversations, making interactions feel more natural
**Learn Your Style**: Adapts to how you work and what you like, becoming more helpful over time
**No More Repetition**: Stop explaining the same things over and over - your AI remembers your context and preferences
---
<Snippet file="get-help.mdx" />
+10 -10
View File
@@ -37,13 +37,13 @@
## Features
[Graph Memory](https://docs.mem0.ai/features/graph-memory): Mem0's graph memory system builds relationships between entities in your data, enabling contextually relevant retrieval by analyzing connections between information points - activate it with `enable_graph=True` to enhance search results beyond direct semantic matches, ideal for applications tracking evolving relationships.
[Advanced Retrieval](https://docs.mem0.ai/features/advanced-retrieval): Mem0 offers enhanced search capabilities through three advanced retrieval modes: keyword search (improves recall by matching specific terms), reranking (ensures most relevant results appear first using neural networks), and filtering (narrows results by specific criteria) - each can be enabled independently or in combination to optimize search precision and relevance.
[Multimodal Support](https://docs.mem0.ai/features/multimodal-support): Mem0 extends beyond text by supporting images and documents (JPG, PNG, MDX, TXT, PDF), allowing users to integrate visual and document content through direct URLs or Base64 encoding, enhancing the memory system's ability to understand and recall information from various media types.
[Memory Customization](https://docs.mem0.ai/features/selective-memory): Mem0 enables selective memory storage through inclusion and exclusion rules, allowing users to focus on relevant information (like specific topics) while omitting irrelevant data (such as food preferences), resulting in more efficient, accurate, and privacy-conscious AI interactions.
[Custom Categories](https://docs.mem0.ai/features/custom-categories): Mem0 allows setting custom categories at both project level and during individual API calls, overriding default categories (like personal_details, family, sports) with more specific ones to improve memory categorization accuracy - simply provide a list of category dictionaries with descriptive definitions when adding memories.
[Async Client](https://docs.mem0.ai/features/async-client): Mem0 provides an AsyncMemoryClient for non-blocking operations, offering the same functionality as the synchronous client (add, search, get_all, delete, etc.) but with async/await support, making it ideal for high-concurrency applications that need to perform memory operations without blocking execution.
[Memory Export](https://docs.mem0.ai/features/memory-export): Mem0 enables exporting memories in structured formats using customizable Pydantic schemas, allowing you to transform stored memories into specific data structures by defining schemas, submitting export jobs with optional processing instructions, and retrieving the formatted data with various filtering options.
[Graph Memory](https://docs.mem0.ai/platform/features/graph-memory): Mem0's graph memory system builds relationships between entities in your data, enabling contextually relevant retrieval by analyzing connections between information points - activate it with `enable_graph=True` to enhance search results beyond direct semantic matches, ideal for applications tracking evolving relationships.
[Advanced Retrieval](https://docs.mem0.ai/platform/features/advanced-retrieval): Mem0 offers enhanced search capabilities through three advanced retrieval modes: keyword search (improves recall by matching specific terms), reranking (ensures most relevant results appear first using neural networks), and filtering (narrows results by specific criteria) - each can be enabled independently or in combination to optimize search precision and relevance.
[Multimodal Support](https://docs.mem0.ai/platform/features/multimodal-support): Mem0 extends beyond text by supporting images and documents (JPG, PNG, MDX, TXT, PDF), allowing users to integrate visual and document content through direct URLs or Base64 encoding, enhancing the memory system's ability to understand and recall information from various media types.
[Memory Customization](https://docs.mem0.ai/platform/features/selective-memory): Mem0 enables selective memory storage through inclusion and exclusion rules, allowing users to focus on relevant information (like specific topics) while omitting irrelevant data (such as food preferences), resulting in more efficient, accurate, and privacy-conscious AI interactions.
[Custom Categories](https://docs.mem0.ai/platform/features/custom-categories): Mem0 allows setting custom categories at both project level and during individual API calls, overriding default categories (like personal_details, family, sports) with more specific ones to improve memory categorization accuracy - simply provide a list of category dictionaries with descriptive definitions when adding memories.
[Async Client](https://docs.mem0.ai/platform/features/async-client): Mem0 provides an AsyncMemoryClient for non-blocking operations, offering the same functionality as the synchronous client (add, search, get_all, delete, etc.) but with async/await support, making it ideal for high-concurrency applications that need to perform memory operations without blocking execution.
[Memory Export](https://docs.mem0.ai/platform/features/memory-export): Mem0 enables exporting memories in structured formats using customizable Pydantic schemas, allowing you to transform stored memories into specific data structures by defining schemas, submitting export jobs with optional processing instructions, and retrieving the formatted data with various filtering options.
## OSS
@@ -72,9 +72,9 @@
### Features
[OpenAI Compatibility](https://docs.mem0.ai/features/openai_compatibility): Mem0 offers seamless integration with OpenAI-compatible APIs, allowing developers to enhance conversational agents with structured memory by initializing with a Mem0 API key (or locally without one), supporting various LLM providers, and enabling personalized responses through user context persistence across interactions with parameters like user_id, agent_id, and custom filters.
[Custom Fact Extraction Prompt](https://docs.mem0.ai/features/custom-fact-extraction-prompt): Mem0 enables custom fact extraction prompts to tailor information extraction for specific use cases by defining domain-specific examples and formats, allowing precise control over what information is extracted from messages - simply provide a custom prompt with few-shot examples in the config when initializing the Memory client.
[Custom Update Memory Prompt](https://docs.mem0.ai/features/custom-update-memory-prompt): Mem0 enables customizing the update memory prompt to control how memories are modified by comparing newly retrieved facts with existing memories and determining appropriate actions (add, update, delete, or no change) based on custom logic and examples provided in the prompt configuration.
[OpenAI Compatibility](https://docs.mem0.ai/open-source/features/openai_compatibility): Mem0 offers seamless integration with OpenAI-compatible APIs, allowing developers to enhance conversational agents with structured memory by initializing with a Mem0 API key (or locally without one), supporting various LLM providers, and enabling personalized responses through user context persistence across interactions with parameters like user_id, agent_id, and custom filters.
[Custom Fact Extraction Prompt](https://docs.mem0.ai/open-source/features/custom-fact-extraction-prompt): Mem0 enables custom fact extraction prompts to tailor information extraction for specific use cases by defining domain-specific examples and formats, allowing precise control over what information is extracted from messages - simply provide a custom prompt with few-shot examples in the config when initializing the Memory client.
[Custom Update Memory Prompt](https://docs.mem0.ai/open-source/features/custom-update-memory-prompt): Mem0 enables customizing the update memory prompt to control how memories are modified by comparing newly retrieved facts with existing memories and determining appropriate actions (add, update, delete, or no change) based on custom logic and examples provided in the prompt configuration.
[REST API Server](https://docs.mem0.ai/open-source/features/rest-api): Mem0 provides a FastAPI-based REST API server that supports core operations (create/retrieve/search/update/delete memories) with OpenAPI documentation at /docs, easily deployable via Docker Compose with pre-configured databases (postgres pgvector, neo4j) - just set OPENAI_API_KEY to get started.
[Graph Memory](https://docs.mem0.ai/open-source/graph_memory/overview): Mem0's open-source graph memory system enables building and querying relationships between entities by installing with `pip install "mem0ai[graph]"` and configuring a graph store provider (like Neo4j) - this allows for more contextual memory retrieval by combining vector and graph-based approaches to track evolving relationships between information points.
@@ -0,0 +1,69 @@
---
title: Multimodal Support
description: Integrate images into your interactions with Mem0
icon: "image"
iconType: "solid"
---
<Snippet file="paper-release.mdx" />
Mem0 extends its capabilities beyond text by supporting multimodal data. With this feature, users can seamlessly integrate images into their interactions—allowing Mem0 to extract relevant information.
## 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 multimodal inputs.
<CodeGroup>
```python Python
import os
from mem0 import Memory
client = Memory()
messages = [
{
"role": "user",
"content": "Hi, my name is Alice."
},
{
"role": "assistant",
"content": "Nice to meet you, Alice! What do you like to eat?"
},
{
"role": "user",
"content": {
"type": "image_url",
"image_url": {
"url": "https://www.superhealthykids.com/wp-content/uploads/2021/10/best-veggie-pizza-featured-image-square-2.jpg"
}
}
},
]
# Calling the add method to ingest messages into the memory system
client.add(messages, user_id="alice")
```
```json Output
{
"results": [
{
"memory": "Name is Alice",
"event": "ADD",
"id": "7ae113a3-3cb5-46e9-b6f7-486c36391847"
},
{
"memory": "Likes large pizza with toppings including cherry tomatoes, black olives, green spinach, yellow bell peppers, diced ham, and sliced mushrooms",
"event": "ADD",
"id": "56545065-7dee-4acf-8bf2-a5b2535aabb3"
}
]
}
```
</CodeGroup>
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:
<Snippet file="get-help.mdx" />
+4 -5
View File
@@ -10,13 +10,12 @@ iconType: "solid"
Graph Memory is a powerful feature that allows users to create and utilize complex relationships between pieces of information.
## Graph Memory supports the following features:
A list of features provided by Graph Memory.
### Add Customize Prompt
### Using Custom Prompts
Users can add a customized prompt that will be used to extract specific entities from the given input text.
This allows for more targeted and relevant information extraction based on the user's needs.
Here's an example of how to add a customized prompt:
Users can specify a custom prompt that will be used to extract specific entities from the given input text.
This allows for more targeted and relevant information extraction based on the user's needs.
Here's an example of how to specify a custom prompt:
<CodeGroup>
```python Python
+57 -47
View File
@@ -2316,6 +2316,11 @@
"message": {
"type": "string",
"example": "Organization created successfully."
},
"org_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the organization"
}
}
}
@@ -2722,13 +2727,13 @@
"schema": {
"type": "object",
"required": [
"username",
"email",
"role"
],
"properties": {
"username": {
"email": {
"type": "string",
"description": "Username of the member whose role is to be updated"
"description": "Email of the member whose role is to be updated"
},
"role": {
"type": "string",
@@ -2798,27 +2803,27 @@
"x-code-samples": [
{
"lang": "Python",
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
},
{
"lang": "JavaScript",
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
},
{
"lang": "cURL",
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\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/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
},
{
"lang": "PHP",
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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}"
},
{
"lang": "Java",
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
}
]
},
@@ -2847,13 +2852,13 @@
"schema": {
"type": "object",
"required": [
"username",
"email",
"role"
],
"properties": {
"username": {
"email": {
"type": "string",
"description": "Username of the member to be added"
"description": "Email of the member to be added"
},
"role": {
"type": "string",
@@ -2923,23 +2928,23 @@
"x-code-samples": [
{
"lang": "Python",
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
},
{
"lang": "JavaScript",
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
},
{
"lang": "cURL",
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\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/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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}"
},
{
"lang": "PHP",
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\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 \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/members/\",\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 \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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}"
},
{
"lang": "Java",
@@ -2971,12 +2976,12 @@
"schema": {
"type": "object",
"required": [
"username"
"email"
],
"properties": {
"username": {
"email": {
"type": "string",
"description": "Username of the member to be removed"
"description": "Email of the member to be removed"
}
}
}
@@ -3020,27 +3025,27 @@
"x-code-samples": [
{
"lang": "Python",
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\"username\": \"<string>\"}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"DELETE\", url, json=payload, headers=headers)\n\nprint(response.text)"
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\"email\": \"<string>\"}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"DELETE\", url, json=payload, headers=headers)\n\nprint(response.text)"
},
{
"lang": "JavaScript",
"source": "const options = {\n method: 'DELETE',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
"source": "const options = {\n method: 'DELETE',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
},
{
"lang": "cURL",
"source": "curl --request DELETE \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\"\n}'"
"source": "curl --request DELETE \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\"\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/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"DELETE\", 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/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"DELETE\", 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/api/v1/orgs/organizations/{org_id}/members/\",\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 => \"DELETE\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/members/\",\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 => \"DELETE\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<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}"
},
{
"lang": "Java",
"source": "HttpResponse<String> response = Unirest.delete(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\"\n}\")\n .asString();"
"source": "HttpResponse<String> response = Unirest.delete(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\"\n}\")\n .asString();"
}
]
}
@@ -3199,6 +3204,11 @@
"message": {
"type": "string",
"example": "Project created successfully."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the project"
}
}
}
@@ -3753,13 +3763,13 @@
"schema": {
"type": "object",
"required": [
"username",
"email",
"role"
],
"properties": {
"username": {
"email": {
"type": "string",
"description": "Username of the member to be added"
"description": "Email of the member to be added"
},
"role": {
"type": "string",
@@ -3823,27 +3833,27 @@
"x-code-samples": [
{
"lang": "Python",
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
},
{
"lang": "JavaScript",
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
},
{
"lang": "cURL",
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\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/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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}"
},
{
"lang": "PHP",
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\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 \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\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 \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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}"
},
{
"lang": "Java",
"source": "HttpResponse<String> response = Unirest.post(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
"source": "HttpResponse<String> response = Unirest.post(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
}
]
},
@@ -3881,13 +3891,13 @@
"schema": {
"type": "object",
"required": [
"username",
"email",
"role"
],
"properties": {
"username": {
"email": {
"type": "string",
"description": "Username of the member to be updated"
"description": "Email of the member to be updated"
},
"role": {
"type": "string",
@@ -3951,27 +3961,27 @@
"x-code-samples": [
{
"lang": "Python",
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
},
{
"lang": "JavaScript",
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
},
{
"lang": "cURL",
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\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/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
},
{
"lang": "PHP",
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<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}"
},
{
"lang": "Java",
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
}
]
},
@@ -3999,10 +4009,10 @@
}
},
{
"name": "username",
"name": "email",
"in": "query",
"required": true,
"description": "Username of the member to be removed",
"description": "Email of the member to be removed",
"schema": {
"type": "string"
}
@@ -4809,7 +4819,7 @@
"type": "object",
"properties": {
"messages": {
"description": "An array of message objects representing the content of the memory. Each message object typically contains 'role' and 'content' fields, where 'role' indicates the sender (e.g., 'user', 'assistant', 'system') and 'content' contains the actual message text. This structure allows for the representation of conversations or multi-part memories.",
"description": "An array of message objects representing the content of the memory. Each message object typically contains 'role' and 'content' fields, where 'role' indicates the sender either 'user' or 'assistant' and 'content' contains the actual message text. This structure allows for the representation of conversations or multi-part memories.",
"type": "array",
"items": {
"type": "object",
+47 -3
View File
@@ -6,6 +6,24 @@ iconType: "solid"
<Snippet file="paper-release.mdx" />
## 🚀 Hosted OpenMemory MCP Now Available!
#### Sign Up Now - [app.openmemory.dev](https://app.openmemory.dev)
Everything you love about OpenMemory MCP but with zero setup.
✅ Works with all MCP-compatible tools (Claude Desktop, Cursor...)
✅ Same standard memory ops: `add_memories`, `search_memory`, etc
✅ One-click provisioning, no Docker required
✅ Powered by Mem0
Add shared, persistent, low-friction memory to your MCP-compatible clients in seconds.
### 🌟 Get Started Now
**Sign up and get your access key at [app.openmemory.dev](https://app.openmemory.dev)**
Example installation: `npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key`
OpenMemory is a local memory infrastructure powered by Mem0 that lets you carry your memory accross any AI app. It provides a unified memory layer that stays with you, enabling agents and assistants to remember what matters across applications.
<img src="https://github.com/user-attachments/assets/3c701757-ad82-4afa-bfbe-e049c2b4320b" alt="OpenMemory UI" />
@@ -16,6 +34,33 @@ The OpenMemory MCP Server is a private, local-first memory server that creates a
The OpenMemory MCP Server ensures all memory stays local, structured, and under your control with no cloud sync or external storage.
## OpenMemory Easy Setup
### Prerequisites
- Docker
- OpenAI API Key
You can quickly run OpenMemory by running the following command:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | bash
```
You should set the `OPENAI_API_KEY` as a global environment variable:
```bash
export OPENAI_API_KEY=your_api_key
```
You can also set the `OPENAI_API_KEY` as a parameter to the script:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | OPENAI_API_KEY=your_api_key bash
```
This will start the OpenMemory server and the OpenMemory UI. Deleting the container will lead to the deletion of the memory store.
We suggest you follow the instructions [here](/openmemory/quickstart#setting-up-openmemory) to set up OpenMemory on your local machine, with more persistant memory store.
## How the OpenMemory MCP Server Works
Built around the Model Context Protocol (MCP), the OpenMemory MCP Server exposes a standardized set of memory tools:
@@ -68,9 +113,8 @@ This is just the beginning. The MCP server is the first core layer in the OpenMe
## Getting Started Today
- Github Repository: https://github.com/mem0ai/mem0
- Read the documentation: [Docs Link]
- Join our community: [Discord link]
- Repository: [GitHub](https://github.com/mem0ai/mem0/tree/main/openmemory)
- Join our community: [Discord](https://discord.gg/6PzXDgEjG5)
With OpenMemory, your AI memories stay private, portable, and under your control, exactly where they belong.
+130 -11
View File
@@ -6,28 +6,147 @@ iconType: "solid"
<Snippet file="paper-release.mdx" />
## 🚀 Hosted OpenMemory MCP Now Available!
#### Sign Up Now - [app.openmemory.dev](https://app.openmemory.dev)
Everything you love about OpenMemory MCP but with zero setup.
✅ Works with all MCP-compatible tools (Claude Desktop, Cursor...)
✅ Same standard memory ops: `add_memories`, `search_memory`, etc
✅ One-click provisioning, no Docker required
✅ Powered by Mem0
Add shared, persistent, low-friction memory to your MCP-compatible clients in seconds.
### 🌟 Get Started Now
**Sign up and get your access key at [app.openmemory.dev](https://app.openmemory.dev)**
Example installation: `npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key`
## Getting Started with Hosted OpenMemory
The fastest way to get started is with our hosted version - no setup required:
### 1. Get your API key
Visit [app.openmemory.dev](https://app.openmemory.dev) to sign up and get your `OPENMEMORY_API_KEY`.
### 2. Install and connect to your preferred client
Example commands (replace `your-key` with your actual API key):
For Claude Desktop: `npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key`
For Cursor: `npx @openmemory/install --client cursor --env OPENMEMORY_API_KEY=your-key`
For Windsurf: `npx @openmemory/install --client windsurf --env OPENMEMORY_API_KEY=your-key`
That's it! Your AI client now has persistent memory across sessions.
## Local Setup (Self-Hosted)
Prefer to run OpenMemory locally? Follow the instructions below for a self-hosted setup.
## OpenMemory Easy Setup
### Prerequisites
- Docker
- OpenAI API Key
You can quickly run OpenMemory by running the following command:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | bash
```
You should set the `OPENAI_API_KEY` as a global environment variable:
```bash
export OPENAI_API_KEY=your_api_key
```
You can also set the `OPENAI_API_KEY` as a parameter to the script:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | OPENAI_API_KEY=your_api_key bash
```
This will start the OpenMemory server and the OpenMemory UI. Deleting the container will lead to the deletion of the memory store.
We suggest you follow the instructions below to set up OpenMemory on your local machine, with more persistant memory store.
## Setting Up OpenMemory
Getting started with OpenMemory is straight forward and takes just a few minutes to set up on your local machine. Follow these steps:
### Getting started
First clone the repository and then follow the instructions:
### 1. First clone the repository and then follow the instructions:
```bash
# Clone the repository
git clone https://github.com/mem0ai/mem0.git
cd mem0/openmemory
# Create the backend .env file with your OpenAI key
make env
# Build the Docker images
make build
# Start all services (API server, vector database, and MCP server components)
make up
```
### 2. Set Up Environment Variables
Before running the project, you need to configure environment variables for both the API and the UI.
You can do this in one of the following ways:
- **Manually**:
Create a `.env` file in each of the following directories:
- `/api/.env`
- `/ui/.env`
- **Using `.env.example` files**:
Copy and rename the example files:
```bash
cp api/.env.example api/.env
cp ui/.env.example ui/.env
```
- **Using Makefile** (if supported):
Run:
```bash
make env
```
- #### Example `/api/.env`
``` bash
OPENAI_API_KEY=sk-xxx
USER=<user-id> # The User Id you want to associate the memories with
```
- #### Example `/ui/.env`
```bash
NEXT_PUBLIC_API_URL=http://localhost:8765
NEXT_PUBLIC_USER_ID=<user-id> # Same as the user id for environment variable in api
```
### 3. Build and Run the Project
You can run the project using the following two commands:
```bash
make build # builds the mcp server and ui
make up # runs openmemory mcp server and ui
```
After running these commands, you will have:
- OpenMemory MCP server running at: http://localhost:8765 (API documentation available at http://localhost:8765/docs)
- OpenMemory UI running at: http://localhost:3000
#### UI not working on http://localhost:3000?
If the UI does not start properly on http://localhost:3000, try running it manually:
```bash
cd ui
pnpm install
pnpm dev
```
You can configure the MCP client using the following command (replace username with your username):
```bash
@@ -40,4 +159,4 @@ Once set up, OpenMemory runs locally on your machine, ensuring all your AI memor
### Getting Started Today
- Github Repository: https://github.com/mem0ai/mem0/openmemory
- Github Repository: https://github.com/mem0ai/mem0/tree/main/openmemory
+4
View File
@@ -5,6 +5,10 @@ iconType: "solid"
---
<Snippet file="paper-release.mdx" />
<Note type="info">
🎉 We're excited to announce that Claude 4 is now available with Mem0! Check it out [here](components/llms/models/anthropic).
</Note>
# Introduction
@@ -0,0 +1,184 @@
---
title: Criteria Retrieval
icon: "magnifying-glass-plus"
iconType: "solid"
---
<Snippet file="paper-release.mdx" />
Mem0's **Criteria Retrieval** feature allows you to retrieve memories based on specific criteria. This is useful when you need to find memories that match certain conditions or criteria, such as emotional content, sentiment, or other custom attributes.
## Setting Up Custom Criteria
You can define custom criteria at the project level, assigning weights to each criterion. These weights will be normalized during memory retrieval.
```python
from mem0 import MemoryClient
client = MemoryClient(
api_key="mem0_api_key",
org_id="mem0_organization_id",
project_id="mem0_project_id"
)
# Define custom criteria with weights
retrieval_criteria = [
{
"name": "joy",
"description": "Measure the intensity of positive emotions such as happiness, excitement, or amusement expressed in the sentence. A higher score reflects greater joy.",
"weight": 3
},
{
"name": "curiosity",
"description": "Assess the extent to which the sentence reflects inquisitiveness, interest in exploring new information, or asking questions. A higher score reflects stronger curiosity.",
"weight": 2
},
{
"name": "emotion",
"description": "Evaluate the presence and depth of sadness or negative emotional tone, including expressions of disappointment, frustration, or sorrow. A higher score reflects greater sadness.",
"weight": 1
}
]
# Update project with custom criteria
client.update_project(
retrieval_criteria=retrieval_criteria
)
```
## Using Criteria Retrieval
After setting up your criteria, you can use them to filter and retrieve memories. Here's an example:
```python
# Add some example memories
messages = [
{"role": "user", "content": "What a beautiful sunny day! I feel so refreshed and ready to take on anything!"},
{"role": "user", "content": "I've always wondered how storms form—what triggers them in the atmosphere?"},
{"role": "user", "content": "It's been raining for days, and it just makes everything feel heavier."},
{"role": "user", "content": "Finally I get time to draw something today, after a long time!! I am super happy today."}
]
client.add(messages, user_id="alice")
# Search with criteria-based filtering
filters = {
"AND": [
{"user_id": "alice"}
]
}
results_with_criteria = client.search(
query="Why I am feeling happy today?",
filters=filters,
version="v2"
)
# Standard search without criteria filtering
results_without_criteria = client.search(
query="Why I am feeling happy today?",
user_id="alice"
)
```
## Search Results Comparison
Let's compare the results from criteria-based retrieval versus standard retrieval to see how the emotional criteria affects ranking:
### Search Results (with Criteria)
```python
[
{
"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day",
"score": 0.666,
...
},
{
"memory": "User finally has time to draw something after a long time",
"score": 0.616,
...
},
{
"memory": "User is happy today",
"score": 0.500,
...
},
{
"memory": "User is curious about how storms form and what triggers them in the atmosphere.",
"score": 0.400,
...
},
{
"memory": "It has been raining for days, making everything feel heavier.",
"score": 0.116,
...
}
]
```
### Search Results (without Criteria)
```python
[
{
"memory": "User is happy today",
"score": 0.607,
...
},
{
"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day",
"score": 0.512,
...
},
{
"memory": "It has been raining for days, making everything feel heavier.",
"score": 0.4617,
...
},
{
"memory": "User is curious about how storms form and what triggers them in the atmosphere.",
"score": 0.340,
...
},
{
"memory": "User finally has time to draw something after a long time",
"score": 0.336,
...
}
]
```
Looking at the example results above, we can see how criteria-based filtering affects the output:
1. **Memory Ordering**: With criteria, memories with high joy scores (like feeling refreshed and drawing) are ranked higher, while without criteria, the most relevant memory ("User is happy today") comes first.
2. **Score Distribution**: With criteria, scores are more spread out (0.116 to 0.666) and reflect the criteria weights, while without criteria, scores are more clustered (0.336 to 0.607) and based purely on relevance.
3. **Negative Content**: With criteria, the negative memory about rain has a much lower score (0.116) due to the emotion criteria, while without criteria it maintains a relatively high score (0.4617) due to its relevance.
4. **Curiosity Content**: The storm-related memory gets a moderate score (0.400) with criteria due to the curiosity weighting, while without criteria it's ranked lower (0.340) as it's less relevant to the happiness query.
## Key Differences
1. **Scoring**: With criteria, normalized scores (0-1) are used based on custom criteria weights, while without criteria, standard relevance scoring is used
2. **Ordering**: With criteria, memories are first retrieved by relevance, then criteria-based filtering and prioritization is applied, while without criteria, ordering is solely by relevance
3. **Filtering**: With criteria, post-retrieval filtering based on custom criteria (joy, curiosity, etc.) is available, which isn't available without criteria
<Note>
When no custom criteria are specified, the search will default to standard relevance-based retrieval. In this case, results are returned based solely on their relevance to the query, without any additional filtering or prioritization that would normally be applied through criteria.
</Note>
## How It Works
1. **Criteria Definition**: Define custom criteria with names, descriptions, and weights
2. **Project Configuration**: Apply these criteria at the project level
3. **Memory Retrieval**: Use v2 search with filters to retrieve memories based on your criteria
4. **Weighted Scoring**: Memories are scored based on the defined criteria weights
<Note>
Criteria retrieval is currently supported only in search v2. Make sure to use `version="v2"` when performing searches with custom criteria.
</Note>
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx" />
@@ -281,6 +281,67 @@ console.log(memories);
</CodeGroup>
### Setting Graph Memory at Project Level
Instead of passing `enable_graph=True` to every add call, you can enable it once at the project level:
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(
api_key="your-api-key",
org_id="your-org-id",
project_id="your-project-id"
)
# Enable graph memory for all operations in this project
client.update_project(enable_graph=True, version="v1")
# Now all add operations will use graph memory by default
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"}
]
client.add(
messages,
user_id="joseph",
output_format="v1.1"
)
```
```javascript JavaScript
import { MemoryClient } from "mem0";
const client = new MemoryClient({
apiKey: "your-api-key",
org_id: "your-org-id",
project_id: "your-project-id"
});
# Enable graph memory for all operations in this project
await client.updateProject({ enable_graph: true, version: "v1" });
# Now all add operations will use graph memory by default
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" }
];
await client.add({
messages,
user_id: "joseph",
output_format: "v1.1"
});
```
</CodeGroup>
## Best Practices
- Enable Graph Memory for applications where understanding context and relationships between memories is important
@@ -131,12 +131,14 @@ curl -X POST "https://api.mem0.ai/v1/memories/export/" \
### Retrieve Export
Once the export job is complete, you can retrieve the structured data:
Once the export job is complete, you can retrieve the structured data in two ways:
#### Using Filters
<CodeGroup>
```python Python
# Corrected date range (assuming you meant July 10 to July 20)
# Retrieve using filters
filters = {
"AND": [
{"created_at": {"gte": "2024-07-10", "lte": "2024-07-20"}},
@@ -148,9 +150,27 @@ response = client.get_memory_export(filters=filters)
print(response)
```
```bash cURL
curl -X GET "https://api.mem0.ai/v1/memories/export/?user_id=alice" \
-H "Authorization: Token your-api-key"
```json Output
{
"full_name": "John Doe",
"current_role": "Senior Software Engineer",
"years_experience": 8,
"employment_status": "full_time",
"education_level": "masters",
"skills": ["Python", "AWS", "Machine Learning"]
}
```
</CodeGroup>
#### Using Export ID
<CodeGroup>
```python Python
# Retrieve using export ID
response = client.get_memory_export(memory_export_id="550e8400-e29b-41d4-a716-446655440000")
print(response)
```
```json Output
@@ -59,7 +59,10 @@ five_days_ago = current_time - timedelta(days=5)
unix_timestamp = int(five_days_ago.timestamp())
# Add memory with custom timestamp
client.add("I'm travelling to SF", user_id="user1", timestamp=unix_timestamp)
messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
client.add(messages, user_id="user1", timestamp=unix_timestamp)
```
```javascript JavaScript
@@ -77,7 +80,10 @@ fiveDaysAgo.setDate(currentTime.getDate() - 5);
const unixTimestamp = Math.floor(fiveDaysAgo.getTime() / 1000);
// Add memory with custom timestamp
client.add("I'm travelling to SF", { user_id: "user1", timestamp: unixTimestamp })
const messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
client.add(messages, { user_id: "user1", timestamp: unixTimestamp })
.then(response => console.log(response))
.catch(error => console.error(error));
```
@@ -119,14 +125,20 @@ For example, to create a memory with a timestamp of January 1, 2023:
# January 1, 2023 timestamp
january_2023_timestamp = 1672531200 # Unix timestamp for 2023-01-01 00:00:00 UTC
client.add("Important historical information", user_id="user1", timestamp=january_2023_timestamp)
messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
client.add(messages, user_id="user1", timestamp=january_2023_timestamp)
```
```javascript JavaScript
// January 1, 2023 timestamp
const january2023Timestamp = 1672531200; // Unix timestamp for 2023-01-01 00:00:00 UTC
client.add("Important historical information", { user_id: "user1", timestamp: january2023Timestamp })
const messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
client.add(messages, { user_id: "user1", timestamp: january2023Timestamp })
.then(response => console.log(response))
.catch(error => console.error(error));
```
+402 -6
View File
@@ -64,7 +64,10 @@ client = AsyncMemoryClient()
async def main():
response = await client.add("I'm travelling to SF", user_id="john")
messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
response = await client.add(messages, user_id="john")
print(response)
await main()
@@ -139,7 +142,20 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
</CodeGroup>
<Note>
Messages passed along with `user_id`, `run_id`, or `app_id` are stored as user memories, while messages from the assistant are excluded from memory. To store messages for the assistant, use `agent_id` exclusively and avoid including other IDs, such as user_id, alongside it. This ensures the memory is properly attributed to the assistant.
When passing `user_id`, memories are primarily created based on user messages, but may be influenced by assistant messages for contextual understanding. For example, in a conversation about food preferences, both the user's stated preferences and their responses to the assistant's questions would form user memories. Similarly, when using `agent_id`, assistant messages are prioritized, but user messages might influence the agent's memories based on context. This approach ensures comprehensive memory creation while maintaining appropriate attribution to either users or agents.
**Example:**
```
User: My favorite cuisine is Italian
Assistant: Nice! What about Indian cuisine?
User: Don't like it much since I cannot eat spicy food
Resulting user memories:
memory1 - Likes Italian food
memory2 - Doesn't like Indian food since cannot eat spicy
(memory2 comes from user's response about Indian cuisine)
```
</Note>
<Note>Metadata allows you to store structured information (location, timestamp, user state) with memories. Add it during creation to enable precise filtering and retrieval during searches.</Note>
@@ -442,7 +458,7 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
#### Search using custom filters
Our advanced search allows you to set custom search filters. You can filter by user_id, agent_id, app_id, run_id, created_at, updated_at, categories, and text. The filters support logical operators (AND, OR) and comparison operators (in, gte, lte, gt, lt, ne, contains, icontains). For more details, see [V2 Search Memories](/api-reference/memory/v2-search-memories).
Our advanced search allows you to set custom search filters. You can filter by user_id, agent_id, app_id, run_id, created_at, updated_at, categories, and text. The filters support logical operators (AND, OR) and comparison operators (in, gte, lte, gt, lt, ne, contains, icontains, *). The wildcard character (*) matches everything for a specific field. For more details, see [V2 Search Memories](/api-reference/memory/v2-search-memories).
Here you need to define `version` as `v2` in the search method.
@@ -678,6 +694,152 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
</CodeGroup>
Example 4: Search using NOT filters
<CodeGroup>
```python Python
query = "What do you know about me?"
filters = {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
}
client.search(query, version="v2", filters=filters)
```
```javascript JavaScript
const query = "What do you know about me?";
const filters = {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
};
client.search(query, { version: "v2", filters })
.then(results => console.log(results))
.catch(error => console.error(error));
```
```bash cURL
curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"query": "What do you know about me?",
"filters": {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
}
}'
```
```json Output
{
"results": [
{
"id": "123abc-d456-7890-efgh-ijklmnopqrst",
"memory": "Lives in San Francisco",
"user_id": "alex",
"metadata": null,
"categories": ["location"],
"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"
}
]
}
```
</CodeGroup>
Example 5: Search using wildcard filters
<CodeGroup>
```python Python
query = "What do you know about me?"
filters = {
"AND": [
{
"user_id": "alex"
},
{
"run_id": "*" # Matches all run_ids
}
]
}
client.search(query, version="v2", filters=filters)
```
```javascript JavaScript
const query = "What do you know about me?";
const filters = {
"AND": [
{
"user_id": "alex"
},
{
"run_id": "*" // Matches all run_ids
}
]
};
client.search(query, { version: "v2", filters })
.then(results => console.log(results))
.catch(error => console.error(error));
```
```bash cURL
curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"query": "What do you know about me?",
"filters": {
"AND": [
{
"user_id": "alex"
},
{
"run_id": "*"
}
]
}
}'
```
```json Output
{
"results": [
{
"id": "7f165f7e-b411-4afe-b7e5-35789b72c4a5",
"memory": "Name: Alex. Vegetarian. Allergic to nuts.",
"user_id": "alex",
"run_id": "session-1",
"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"
}
]
}
```
</CodeGroup>
### 4.3 Get All Users
@@ -1051,7 +1213,7 @@ curl -X GET "https://api.mem0.ai/v1/memories/?user_id=alex&keywords=to play&page
#### Get all memories using custom filters
Our advanced retrieval allows you to set custom filters when fetching memories. You can filter by user_id, agent_id, app_id, run_id, created_at, updated_at, categories, and keywords. The filters support logical operators (AND, OR) and comparison operators (in, gte, lte, gt, lt, ne, contains, icontains). For more details, see [v2 Get Memories](/api-reference/memory/v2-get-memories).
Our advanced retrieval allows you to set custom filters when fetching memories. You can filter by user_id, agent_id, app_id, run_id, created_at, updated_at, categories, and keywords. The filters support logical operators (AND, OR) and comparison operators (in, gte, lte, gt, lt, ne, contains, icontains, *). The wildcard character (*) matches everything for a specific field. For more details, see [v2 Get Memories](/api-reference/memory/v2-get-memories).
Here you need to define `version` as `v2` in the get_all method.
@@ -1295,6 +1457,234 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
</CodeGroup>
Example 3: Get all memories using NOT filters
<CodeGroup>
```python Python
filters = {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
}
# Default (No Pagination)
client.get_all(version="v2", filters=filters)
# Pagination (You can also use the page and page_size parameters)
client.get_all(version="v2", filters=filters, page=1, page_size=50)
```
```javascript JavaScript
const filters = {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
};
// Default (No Pagination)
client.getAll({ version: "v2", filters })
.then(memories => console.log(memories))
.catch(error => console.error(error));
// Pagination (You can also use the page and page_size parameters)
client.getAll({ version: "v2", filters, page: 1, page_size: 50 })
.then(memories => console.log(memories))
.catch(error => console.error(error));
```
```bash cURL
# Default (No Pagination)
curl -X GET "https://api.mem0.ai/v1/memories/?version=v2" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
}
}'
# Pagination (You can also use the page and page_size parameters)
curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"NOT": [
{
"categories": {
"contains": "food_preferences"
}
}
]
}
}'
```
```json Output
[
{
"id": "789xyz-e012-3456-fghi-jklmnopqrstu",
"memory": "Works as a software engineer",
"user_id": "alex",
"metadata": {"job": "tech"},
"categories": ["work"],
"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": "789xyz-e012-3456-fghi-jklmnopqrstu",
"memory": "Works as a software engineer",
"user_id": "alex",
"metadata": {"job": "tech"},
"categories": ["work"],
"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"
}
]
}
```
</CodeGroup>
Example 4: Get all memories using wildcard filters
<CodeGroup>
```python Python
filters = {
"AND": [
{
"user_id": "alex"
},
{
"run_id": "*" # Matches all run_ids
}
]
}
# Default (No Pagination)
client.get_all(version="v2", filters=filters)
# Pagination (You can also use the page and page_size parameters)
client.get_all(version="v2", filters=filters, page=1, page_size=50)
```
```javascript JavaScript
const filters = {
"AND": [
{
"user_id": "alex"
},
{
"run_id": "*" // Matches all run_ids
}
]
};
// Default (No Pagination)
client.getAll({ version: "v2", filters })
.then(memories => console.log(memories))
.catch(error => console.error(error));
// Pagination (You can also use the page and page_size parameters)
client.getAll({ version: "v2", filters, page: 1, page_size: 50 })
.then(memories => console.log(memories))
.catch(error => console.error(error));
```
```bash cURL
# Default (No Pagination)
curl -X GET "https://api.mem0.ai/v1/memories/?version=v2" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"AND": [
{"user_id":"alex"},
{"run_id": "*"}
]
}
}'
# Pagination (You can also use the page and page_size parameters)
curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"AND": [
{"user_id":"alex"},
{"run_id": "*"}
]
}
}'
```
```json Output (Default)
[
{
"id": "f38b689d-6b24-45b7-bced-17fbb4d8bac7",
"memory": "Name: Alex. Vegetarian. Allergic to nuts.",
"user_id": "alex",
"run_id": "session-1",
"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"
}
]
```
```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",
"run_id": "session-1",
"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"
}
]
}
```
</CodeGroup>
### 4.5 Memory History
@@ -1547,11 +1937,17 @@ Fun fact: You can also delete the memory using the `add()` method by passing a n
<CodeGroup>
```python Python
client.add("Delete all of my food preferences", user_id="alex")
messages = [
{"role": "user", "content": "Delete all of my food preferences"}
]
client.add(messages, user_id="alex")
```
```javascript JavaScript
client.add("Delete all of my food preferences", { user_id: "alex" })
const messages = [
{"role": "user", "content": "Delete all of my food preferences"}
]
client.add(messages, { user_id: "alex" })
.then(result => console.log(result))
.catch(error => console.error(error));
```
+3
View File
@@ -5,6 +5,9 @@ iconType: "solid"
---
<Snippet file="paper-release.mdx" />
<Note type="info">
🎉 We're excited to announce that Claude 4 is now available with Mem0! Check it out [here](components/llms/models/anthropic).
</Note>
Mem0 offers two powerful ways to leverage our technology: [our managed platform](#mem0-platform-managed-solution) and [our open source solution](#mem0-open-source).
+28 -29
View File
@@ -14,46 +14,47 @@ def process_item(item_data):
local_results = defaultdict(list)
for item in v:
gt_answer = str(item['answer'])
pred_answer = str(item['response'])
category = str(item['category'])
question = str(item['question'])
gt_answer = str(item["answer"])
pred_answer = str(item["response"])
category = str(item["category"])
question = str(item["question"])
# Skip category 5
if category == '5':
if category == "5":
continue
metrics = calculate_metrics(pred_answer, gt_answer)
bleu_scores = calculate_bleu_scores(pred_answer, gt_answer)
llm_score = evaluate_llm_judge(question, gt_answer, pred_answer)
local_results[k].append({
"question": question,
"answer": gt_answer,
"response": pred_answer,
"category": category,
"bleu_score": bleu_scores["bleu1"],
"f1_score": metrics["f1"],
"llm_score": llm_score
})
local_results[k].append(
{
"question": question,
"answer": gt_answer,
"response": pred_answer,
"category": category,
"bleu_score": bleu_scores["bleu1"],
"f1_score": metrics["f1"],
"llm_score": llm_score,
}
)
return local_results
def main():
parser = argparse.ArgumentParser(description='Evaluate RAG results')
parser.add_argument('--input_file', type=str,
default="results/rag_results_500_k1.json",
help='Path to the input dataset file')
parser.add_argument('--output_file', type=str,
default="evaluation_metrics.json",
help='Path to save the evaluation results')
parser.add_argument('--max_workers', type=int, default=10,
help='Maximum number of worker threads')
parser = argparse.ArgumentParser(description="Evaluate RAG results")
parser.add_argument(
"--input_file", type=str, default="results/rag_results_500_k1.json", help="Path to the input dataset file"
)
parser.add_argument(
"--output_file", type=str, default="evaluation_metrics.json", help="Path to save the evaluation results"
)
parser.add_argument("--max_workers", type=int, default=10, help="Maximum number of worker threads")
args = parser.parse_args()
with open(args.input_file, 'r') as f:
with open(args.input_file, "r") as f:
data = json.load(f)
results = defaultdict(list)
@@ -61,18 +62,16 @@ def main():
# Use ThreadPoolExecutor with specified workers
with concurrent.futures.ThreadPoolExecutor(max_workers=args.max_workers) as executor:
futures = [executor.submit(process_item, item_data)
for item_data in data.items()]
futures = [executor.submit(process_item, item_data) for item_data in data.items()]
for future in tqdm(concurrent.futures.as_completed(futures),
total=len(futures)):
for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures)):
local_results = future.result()
with results_lock:
for k, items in local_results.items():
results[k].extend(items)
# Save results to JSON file
with open(args.output_file, 'w') as f:
with open(args.output_file, "w") as f:
json.dump(results, f, indent=4)
print(f"Results saved to {args.output_file}")
+6 -14
View File
@@ -3,7 +3,7 @@ import json
import pandas as pd
# Load the evaluation metrics data
with open('evaluation_metrics.json', 'r') as f:
with open("evaluation_metrics.json", "r") as f:
data = json.load(f)
# Flatten the data into a list of question items
@@ -15,28 +15,20 @@ for key in data:
df = pd.DataFrame(all_items)
# Convert category to numeric type
df['category'] = pd.to_numeric(df['category'])
df["category"] = pd.to_numeric(df["category"])
# Calculate mean scores by category
result = df.groupby('category').agg({
'bleu_score': 'mean',
'f1_score': 'mean',
'llm_score': 'mean'
}).round(4)
result = df.groupby("category").agg({"bleu_score": "mean", "f1_score": "mean", "llm_score": "mean"}).round(4)
# Add count of questions per category
result['count'] = df.groupby('category').size()
result["count"] = df.groupby("category").size()
# Print the results
print("Mean Scores Per Category:")
print(result)
# Calculate overall means
overall_means = df.agg({
'bleu_score': 'mean',
'f1_score': 'mean',
'llm_score': 'mean'
}).round(4)
overall_means = df.agg({"bleu_score": "mean", "f1_score": "mean", "llm_score": "mean"}).round(4)
print("\nOverall Mean Scores:")
print(overall_means)
print(overall_means)
+28 -28
View File
@@ -33,35 +33,34 @@ Do NOT include both CORRECT and WRONG in your response, or it will break the eva
Just return the label CORRECT or WRONG in a json format with the key as "label".
"""
def evaluate_llm_judge(question, gold_answer, generated_answer):
"""Evaluate the generated answer against the gold answer using an LLM judge."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": ACCURACY_PROMPT.format(
question=question,
gold_answer=gold_answer,
generated_answer=generated_answer
)
}],
messages=[
{
"role": "user",
"content": ACCURACY_PROMPT.format(
question=question, gold_answer=gold_answer, generated_answer=generated_answer
),
}
],
response_format={"type": "json_object"},
temperature=0.0
temperature=0.0,
)
label = json.loads(response.choices[0].message.content)['label']
label = json.loads(response.choices[0].message.content)["label"]
return 1 if label == "CORRECT" else 0
def main():
"""Main function to evaluate RAG results using LLM judge."""
parser = argparse.ArgumentParser(
description='Evaluate RAG results using LLM judge'
)
parser = argparse.ArgumentParser(description="Evaluate RAG results using LLM judge")
parser.add_argument(
'--input_file',
"--input_file",
type=str,
default="results/default_run_v4_k30_new_graph.json",
help='Path to the input dataset file'
help="Path to the input dataset file",
)
args = parser.parse_args()
@@ -78,10 +77,10 @@ def main():
index = 0
for k, v in data.items():
for x in v:
question = x['question']
gold_answer = x['answer']
generated_answer = x['response']
category = x['category']
question = x["question"]
gold_answer = x["answer"]
generated_answer = x["response"]
category = x["category"]
# Skip category 5
if int(category) == 5:
@@ -92,13 +91,15 @@ def main():
LLM_JUDGE[category].append(label)
# Store the results
RESULTS[index].append({
"question": question,
"gt_answer": gold_answer,
"response": generated_answer,
"category": category,
"llm_label": label
})
RESULTS[index].append(
{
"question": question,
"gt_answer": gold_answer,
"response": generated_answer,
"category": category,
"llm_label": label,
}
)
# Save intermediate results
with open(output_path, "w") as f:
@@ -108,8 +109,7 @@ def main():
print("All categories accuracy:")
for cat, results in LLM_JUDGE.items():
if results: # Only print if there are results for this category
print(f" Category {cat}: {np.mean(results):.4f} "
f"({sum(results)}/{len(results)})")
print(f" Category {cat}: {np.mean(results):.4f} " f"({sum(results)}/{len(results)})")
print("------------------------------------------")
index += 1
+54 -54
View File
@@ -3,7 +3,7 @@ Borrowed from https://github.com/WujiangXu/AgenticMemory/blob/main/utils.py
@article{xu2025mem,
title={A-mem: Agentic memory for llm agents},
author={Xu, Wujiang and Liang, Zujie and Mei, Kai and Gao, Hang and Tan, Juntao
author={Xu, Wujiang and Liang, Zujie and Mei, Kai and Gao, Hang and Tan, Juntao
and Zhang, Yongfeng},
journal={arXiv preprint arXiv:2502.12110},
year={2025}
@@ -26,42 +26,45 @@ from sentence_transformers.util import pytorch_cos_sim
# Download required NLTK data
try:
nltk.download('punkt', quiet=True)
nltk.download('wordnet', quiet=True)
nltk.download("punkt", quiet=True)
nltk.download("wordnet", quiet=True)
except Exception as e:
print(f"Error downloading NLTK data: {e}")
# Initialize SentenceTransformer model (this will be reused)
try:
sentence_model = SentenceTransformer('all-MiniLM-L6-v2')
sentence_model = SentenceTransformer("all-MiniLM-L6-v2")
except Exception as e:
print(f"Warning: Could not load SentenceTransformer model: {e}")
sentence_model = None
def simple_tokenize(text):
"""Simple tokenization function."""
# Convert to string if not already
text = str(text)
return text.lower().replace('.', ' ').replace(',', ' ').replace('!', ' ').replace('?', ' ').split()
return text.lower().replace(".", " ").replace(",", " ").replace("!", " ").replace("?", " ").split()
def calculate_rouge_scores(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate ROUGE scores for prediction against reference."""
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scorer = rouge_scorer.RougeScorer(["rouge1", "rouge2", "rougeL"], use_stemmer=True)
scores = scorer.score(reference, prediction)
return {
'rouge1_f': scores['rouge1'].fmeasure,
'rouge2_f': scores['rouge2'].fmeasure,
'rougeL_f': scores['rougeL'].fmeasure
"rouge1_f": scores["rouge1"].fmeasure,
"rouge2_f": scores["rouge2"].fmeasure,
"rougeL_f": scores["rougeL"].fmeasure,
}
def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate BLEU scores with different n-gram settings."""
pred_tokens = nltk.word_tokenize(prediction.lower())
ref_tokens = [nltk.word_tokenize(reference.lower())]
weights_list = [(1, 0, 0, 0), (0.5, 0.5, 0, 0), (0.33, 0.33, 0.33, 0), (0.25, 0.25, 0.25, 0.25)]
smooth = SmoothingFunction().method1
scores = {}
for n, weights in enumerate(weights_list, start=1):
try:
@@ -69,26 +72,20 @@ def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]:
except Exception as e:
print(f"Error calculating BLEU score: {e}")
score = 0.0
scores[f'bleu{n}'] = score
scores[f"bleu{n}"] = score
return scores
def calculate_bert_scores(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate BERTScore for semantic similarity."""
try:
P, R, F1 = bert_score([prediction], [reference], lang='en', verbose=False)
return {
'bert_precision': P.item(),
'bert_recall': R.item(),
'bert_f1': F1.item()
}
P, R, F1 = bert_score([prediction], [reference], lang="en", verbose=False)
return {"bert_precision": P.item(), "bert_recall": R.item(), "bert_f1": F1.item()}
except Exception as e:
print(f"Error calculating BERTScore: {e}")
return {
'bert_precision': 0.0,
'bert_recall': 0.0,
'bert_f1': 0.0
}
return {"bert_precision": 0.0, "bert_recall": 0.0, "bert_f1": 0.0}
def calculate_meteor_score(prediction: str, reference: str) -> float:
"""Calculate METEOR score for the prediction."""
@@ -98,6 +95,7 @@ def calculate_meteor_score(prediction: str, reference: str) -> float:
print(f"Error calculating METEOR score: {e}")
return 0.0
def calculate_sentence_similarity(prediction: str, reference: str) -> float:
"""Calculate sentence embedding similarity using SentenceBERT."""
if sentence_model is None:
@@ -106,7 +104,7 @@ def calculate_sentence_similarity(prediction: str, reference: str) -> float:
# Encode sentences
embedding1 = sentence_model.encode([prediction], convert_to_tensor=True)
embedding2 = sentence_model.encode([reference], convert_to_tensor=True)
# Calculate cosine similarity
similarity = pytorch_cos_sim(embedding1, embedding2).item()
return float(similarity)
@@ -114,6 +112,7 @@ def calculate_sentence_similarity(prediction: str, reference: str) -> float:
print(f"Error calculating sentence similarity: {e}")
return 0.0
def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate comprehensive evaluation metrics for a prediction."""
# Handle empty or None values
@@ -130,31 +129,31 @@ def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
"bleu4": 0.0,
"bert_f1": 0.0,
"meteor": 0.0,
"sbert_similarity": 0.0
"sbert_similarity": 0.0,
}
# Convert to strings if they're not already
prediction = str(prediction).strip()
reference = str(reference).strip()
# Calculate exact match
exact_match = int(prediction.lower() == reference.lower())
# Calculate token-based F1 score
pred_tokens = set(simple_tokenize(prediction))
ref_tokens = set(simple_tokenize(reference))
common_tokens = pred_tokens & ref_tokens
if not pred_tokens or not ref_tokens:
f1 = 0.0
else:
precision = len(common_tokens) / len(pred_tokens)
recall = len(common_tokens) / len(ref_tokens)
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
# Calculate all scores
bleu_scores = calculate_bleu_scores(prediction, reference)
# Combine all metrics
metrics = {
"exact_match": exact_match,
@@ -164,48 +163,49 @@ def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
return metrics
def aggregate_metrics(all_metrics: List[Dict[str, float]], all_categories: List[int]) -> Dict[str, Dict[str, Union[float, Dict[str, float]]]]:
def aggregate_metrics(
all_metrics: List[Dict[str, float]], all_categories: List[int]
) -> Dict[str, Dict[str, Union[float, Dict[str, float]]]]:
"""Calculate aggregate statistics for all metrics, split by category."""
if not all_metrics:
return {}
# Initialize aggregates for overall and per-category metrics
aggregates = defaultdict(list)
category_aggregates = defaultdict(lambda: defaultdict(list))
# Collect all values for each metric, both overall and per category
for metrics, category in zip(all_metrics, all_categories):
for metric_name, value in metrics.items():
aggregates[metric_name].append(value)
category_aggregates[category][metric_name].append(value)
# Calculate statistics for overall metrics
results = {
"overall": {}
}
results = {"overall": {}}
for metric_name, values in aggregates.items():
results["overall"][metric_name] = {
'mean': statistics.mean(values),
'std': statistics.stdev(values) if len(values) > 1 else 0.0,
'median': statistics.median(values),
'min': min(values),
'max': max(values),
'count': len(values)
"mean": statistics.mean(values),
"std": statistics.stdev(values) if len(values) > 1 else 0.0,
"median": statistics.median(values),
"min": min(values),
"max": max(values),
"count": len(values),
}
# Calculate statistics for each category
for category in sorted(category_aggregates.keys()):
results[f"category_{category}"] = {}
for metric_name, values in category_aggregates[category].items():
if values: # Only calculate if we have values for this category
results[f"category_{category}"][metric_name] = {
'mean': statistics.mean(values),
'std': statistics.stdev(values) if len(values) > 1 else 0.0,
'median': statistics.median(values),
'min': min(values),
'max': max(values),
'count': len(values)
"mean": statistics.mean(values),
"std": statistics.stdev(values) if len(values) > 1 else 0.0,
"median": statistics.median(values),
"min": min(values),
"max": max(values),
"count": len(values),
}
return results
+1 -1
View File
@@ -144,4 +144,4 @@ ANSWER_PROMPT_ZEP = """
Question: {{question}}
Answer:
"""
"""
+16 -43
View File
@@ -21,23 +21,15 @@ class Experiment:
def main():
parser = argparse.ArgumentParser(description='Run memory experiments')
parser.add_argument('--technique_type', choices=TECHNIQUES, default='mem0',
help='Memory technique to use')
parser.add_argument('--method', choices=METHODS, default='add',
help='Method to use')
parser.add_argument('--chunk_size', type=int, default=1000,
help='Chunk size for processing')
parser.add_argument('--output_folder', type=str, default='results/',
help='Output path for results')
parser.add_argument('--top_k', type=int, default=30,
help='Number of top memories to retrieve')
parser.add_argument('--filter_memories', action='store_true', default=False,
help='Whether to filter memories')
parser.add_argument('--is_graph', action='store_true', default=False,
help='Whether to use graph-based search')
parser.add_argument('--num_chunks', type=int, default=1,
help='Number of chunks to process')
parser = argparse.ArgumentParser(description="Run memory experiments")
parser.add_argument("--technique_type", choices=TECHNIQUES, default="mem0", help="Memory technique to use")
parser.add_argument("--method", choices=METHODS, default="add", help="Method to use")
parser.add_argument("--chunk_size", type=int, default=1000, help="Chunk size for processing")
parser.add_argument("--output_folder", type=str, default="results/", help="Output path for results")
parser.add_argument("--top_k", type=int, default=30, help="Number of top memories to retrieve")
parser.add_argument("--filter_memories", action="store_true", default=False, help="Whether to filter memories")
parser.add_argument("--is_graph", action="store_true", default=False, help="Whether to use graph-based search")
parser.add_argument("--num_chunks", type=int, default=1, help="Number of chunks to process")
args = parser.parse_args()
@@ -46,33 +38,18 @@ def main():
if args.technique_type == "mem0":
if args.method == "add":
memory_manager = MemoryADD(
data_path='dataset/locomo10.json',
is_graph=args.is_graph
)
memory_manager = MemoryADD(data_path="dataset/locomo10.json", is_graph=args.is_graph)
memory_manager.process_all_conversations()
elif args.method == "search":
output_file_path = os.path.join(
args.output_folder,
f"mem0_results_top_{args.top_k}_filter_{args.filter_memories}_graph_{args.is_graph}.json"
f"mem0_results_top_{args.top_k}_filter_{args.filter_memories}_graph_{args.is_graph}.json",
)
memory_searcher = MemorySearch(
output_file_path,
args.top_k,
args.filter_memories,
args.is_graph
)
memory_searcher.process_data_file('dataset/locomo10.json')
memory_searcher = MemorySearch(output_file_path, args.top_k, args.filter_memories, args.is_graph)
memory_searcher.process_data_file("dataset/locomo10.json")
elif args.technique_type == "rag":
output_file_path = os.path.join(
args.output_folder,
f"rag_results_{args.chunk_size}_k{args.num_chunks}.json"
)
rag_manager = RAGManager(
data_path="dataset/locomo10_rag.json",
chunk_size=args.chunk_size,
k=args.num_chunks
)
output_file_path = os.path.join(args.output_folder, f"rag_results_{args.chunk_size}_k{args.num_chunks}.json")
rag_manager = RAGManager(data_path="dataset/locomo10_rag.json", chunk_size=args.chunk_size, k=args.num_chunks)
rag_manager.process_all_conversations(output_file_path)
elif args.technique_type == "langmem":
output_file_path = os.path.join(args.output_folder, "langmem_results.json")
@@ -85,11 +62,7 @@ def main():
elif args.method == "search":
output_file_path = os.path.join(args.output_folder, "zep_search_results.json")
zep_manager = ZepSearch()
zep_manager.process_data_file(
"dataset/locomo10.json",
"1",
output_file_path
)
zep_manager.process_data_file("dataset/locomo10.json", "1", output_file_path)
elif args.technique_type == "openai":
output_file_path = os.path.join(args.output_folder, "openai_results.json")
openai_manager = OpenAIPredict()
+36 -40
View File
@@ -28,14 +28,12 @@ def get_answer(question, speaker_1_user_id, speaker_1_memories, speaker_2_user_i
speaker_1_user_id=speaker_1_user_id,
speaker_1_memories=speaker_1_memories,
speaker_2_user_id=speaker_2_user_id,
speaker_2_memories=speaker_2_memories
speaker_2_memories=speaker_2_memories,
)
t1 = time.time()
response = client.chat.completions.create(
model=os.getenv("MODEL"),
messages=[{"role": "system", "content": prompt}],
temperature=0.0
model=os.getenv("MODEL"), messages=[{"role": "system", "content": prompt}], temperature=0.0
)
t2 = time.time()
return response.choices[0].message.content, t2 - t1
@@ -59,7 +57,9 @@ def prompt(state):
class LangMem:
def __init__(self,):
def __init__(
self,
):
self.store = InMemoryStore(
index={
"dims": 1536,
@@ -80,18 +80,12 @@ class LangMem:
)
def add_memory(self, message, config):
return self.agent.invoke(
{"messages": [{"role": "user", "content": message}]},
config=config
)
return self.agent.invoke({"messages": [{"role": "user", "content": message}]}, config=config)
def search_memory(self, query, config):
try:
t1 = time.time()
response = self.agent.invoke(
{"messages": [{"role": "user", "content": query}]},
config=config
)
response = self.agent.invoke({"messages": [{"role": "user", "content": query}]}, config=config)
t2 = time.time()
return response["messages"][-1].content, t2 - t1
except Exception as e:
@@ -102,7 +96,7 @@ class LangMem:
class LangMemManager:
def __init__(self, dataset_path):
self.dataset_path = dataset_path
with open(self.dataset_path, 'r') as f:
with open(self.dataset_path, "r") as f:
self.data = json.load(f)
def process_all_conversations(self, output_file_path):
@@ -123,7 +117,7 @@ class LangMemManager:
# Identify speakers
for conv in chat_history:
speakers.add(conv['speaker'])
speakers.add(conv["speaker"])
if len(speakers) != 2:
raise ValueError(f"Expected 2 speakers, got {len(speakers)}")
@@ -134,50 +128,52 @@ class LangMemManager:
# Add memories for each message
for conv in tqdm(chat_history, desc=f"Processing messages {key}", leave=False):
message = f"{conv['timestamp']} | {conv['speaker']}: {conv['text']}"
if conv['speaker'] == speaker1:
if conv["speaker"] == speaker1:
agent1.add_memory(message, config)
elif conv['speaker'] == speaker2:
elif conv["speaker"] == speaker2:
agent2.add_memory(message, config)
else:
raise ValueError(f"Expected speaker1 or speaker2, got {conv['speaker']}")
# Process questions
for q in tqdm(questions, desc=f"Processing questions {key}", leave=False):
category = q['category']
category = q["category"]
if int(category) == 5:
continue
answer = q['answer']
question = q['question']
answer = q["answer"]
question = q["question"]
response1, speaker1_memory_time = agent1.search_memory(question, config)
response2, speaker2_memory_time = agent2.search_memory(question, config)
generated_answer, response_time = get_answer(
question, speaker1, response1, speaker2, response2
)
generated_answer, response_time = get_answer(question, speaker1, response1, speaker2, response2)
result[key].append({
"question": question,
"answer": answer,
"response1": response1,
"response2": response2,
"category": category,
"speaker1_memory_time": speaker1_memory_time,
"speaker2_memory_time": speaker2_memory_time,
"response_time": response_time,
'response': generated_answer
})
result[key].append(
{
"question": question,
"answer": answer,
"response1": response1,
"response2": response2,
"category": category,
"speaker1_memory_time": speaker1_memory_time,
"speaker2_memory_time": speaker2_memory_time,
"response_time": response_time,
"response": generated_answer,
}
)
return result
# Use multiprocessing to process conversations in parallel
with mp.Pool(processes=10) as pool:
results = list(tqdm(
pool.imap(process_conversation, list(self.data.items())),
total=len(self.data),
desc="Processing conversations"
))
results = list(
tqdm(
pool.imap(process_conversation, list(self.data.items())),
total=len(self.data),
desc="Processing conversations",
)
)
# Combine results from all workers
for result in results:
@@ -185,5 +181,5 @@ class LangMemManager:
OUTPUT[key].extend(items)
# Save final results
with open(output_file_path, 'w') as f:
with open(output_file_path, "w") as f:
json.dump(OUTPUT, f, indent=4)
+17 -19
View File
@@ -13,7 +13,7 @@ load_dotenv()
# Update custom instructions
custom_instructions ="""
custom_instructions = """
Generate personal memories that follow these guidelines:
1. Each memory should be self-contained with complete context, including:
@@ -47,7 +47,7 @@ class MemoryADD:
self.mem0_client = MemoryClient(
api_key=os.getenv("MEM0_API_KEY"),
org_id=os.getenv("MEM0_ORGANIZATION_ID"),
project_id=os.getenv("MEM0_PROJECT_ID")
project_id=os.getenv("MEM0_PROJECT_ID"),
)
self.mem0_client.update_project(custom_instructions=custom_instructions)
@@ -59,15 +59,16 @@ class MemoryADD:
self.load_data()
def load_data(self):
with open(self.data_path, 'r') as f:
with open(self.data_path, "r") as f:
self.data = json.load(f)
return self.data
def add_memory(self, user_id, message, metadata, retries=3):
for attempt in range(retries):
try:
_ = self.mem0_client.add(message, user_id=user_id, version="v2",
metadata=metadata, enable_graph=self.is_graph)
_ = self.mem0_client.add(
message, user_id=user_id, version="v2", metadata=metadata, enable_graph=self.is_graph
)
return
except Exception as e:
if attempt < retries - 1:
@@ -78,13 +79,13 @@ class MemoryADD:
def add_memories_for_speaker(self, speaker, messages, timestamp, desc):
for i in tqdm(range(0, len(messages), self.batch_size), desc=desc):
batch_messages = messages[i:i+self.batch_size]
batch_messages = messages[i : i + self.batch_size]
self.add_memory(speaker, batch_messages, metadata={"timestamp": timestamp})
def process_conversation(self, item, idx):
conversation = item['conversation']
speaker_a = conversation['speaker_a']
speaker_b = conversation['speaker_b']
conversation = item["conversation"]
speaker_a = conversation["speaker_a"]
speaker_b = conversation["speaker_b"]
speaker_a_user_id = f"{speaker_a}_{idx}"
speaker_b_user_id = f"{speaker_b}_{idx}"
@@ -94,7 +95,7 @@ class MemoryADD:
self.mem0_client.delete_all(user_id=speaker_b_user_id)
for key in conversation.keys():
if key in ['speaker_a', 'speaker_b'] or "date" in key or "timestamp" in key:
if key in ["speaker_a", "speaker_b"] or "date" in key or "timestamp" in key:
continue
date_time_key = key + "_date_time"
@@ -104,10 +105,10 @@ class MemoryADD:
messages = []
messages_reverse = []
for chat in chats:
if chat['speaker'] == speaker_a:
if chat["speaker"] == speaker_a:
messages.append({"role": "user", "content": f"{speaker_a}: {chat['text']}"})
messages_reverse.append({"role": "assistant", "content": f"{speaker_a}: {chat['text']}"})
elif chat['speaker'] == speaker_b:
elif chat["speaker"] == speaker_b:
messages.append({"role": "assistant", "content": f"{speaker_b}: {chat['text']}"})
messages_reverse.append({"role": "user", "content": f"{speaker_b}: {chat['text']}"})
else:
@@ -116,11 +117,11 @@ class MemoryADD:
# add memories for the two users on different threads
thread_a = threading.Thread(
target=self.add_memories_for_speaker,
args=(speaker_a_user_id, messages, timestamp, "Adding Memories for Speaker A")
args=(speaker_a_user_id, messages, timestamp, "Adding Memories for Speaker A"),
)
thread_b = threading.Thread(
target=self.add_memories_for_speaker,
args=(speaker_b_user_id, messages_reverse, timestamp, "Adding Memories for Speaker B")
args=(speaker_b_user_id, messages_reverse, timestamp, "Adding Memories for Speaker B"),
)
thread_a.start()
@@ -134,10 +135,7 @@ class MemoryADD:
if not self.data:
raise ValueError("No data loaded. Please set data_path and call load_data() first.")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [
executor.submit(self.process_conversation, item, idx)
for idx, item in enumerate(self.data)
]
futures = [executor.submit(self.process_conversation, item, idx) for idx, item in enumerate(self.data)]
for future in futures:
future.result()
future.result()
+92 -68
View File
@@ -16,12 +16,11 @@ load_dotenv()
class MemorySearch:
def __init__(self, output_path='results.json', top_k=10, filter_memories=False, is_graph=False):
def __init__(self, output_path="results.json", top_k=10, filter_memories=False, is_graph=False):
self.mem0_client = MemoryClient(
api_key=os.getenv("MEM0_API_KEY"),
org_id=os.getenv("MEM0_ORGANIZATION_ID"),
project_id=os.getenv("MEM0_PROJECT_ID")
project_id=os.getenv("MEM0_PROJECT_ID"),
)
self.top_k = top_k
self.openai_client = OpenAI()
@@ -42,11 +41,18 @@ class MemorySearch:
try:
if self.is_graph:
print("Searching with graph")
memories = self.mem0_client.search(query, user_id=user_id, top_k=self.top_k,
filter_memories=self.filter_memories, enable_graph=True, output_format='v1.1')
memories = self.mem0_client.search(
query,
user_id=user_id,
top_k=self.top_k,
filter_memories=self.filter_memories,
enable_graph=True,
output_format="v1.1",
)
else:
memories = self.mem0_client.search(query, user_id=user_id, top_k=self.top_k,
filter_memories=self.filter_memories)
memories = self.mem0_client.search(
query, user_id=user_id, top_k=self.top_k, filter_memories=self.filter_memories
)
break
except Exception as e:
print("Retrying...")
@@ -57,64 +63,86 @@ class MemorySearch:
end_time = time.time()
if not self.is_graph:
semantic_memories = [{'memory': memory['memory'],
'timestamp': memory['metadata']['timestamp'],
'score': round(memory['score'], 2)}
for memory in memories]
semantic_memories = [
{
"memory": memory["memory"],
"timestamp": memory["metadata"]["timestamp"],
"score": round(memory["score"], 2),
}
for memory in memories
]
graph_memories = None
else:
semantic_memories = [{'memory': memory['memory'],
'timestamp': memory['metadata']['timestamp'],
'score': round(memory['score'], 2)} for memory in memories['results']]
graph_memories = [{"source": relation['source'], "relationship": relation['relationship'], "target": relation['target']} for relation in memories['relations']]
semantic_memories = [
{
"memory": memory["memory"],
"timestamp": memory["metadata"]["timestamp"],
"score": round(memory["score"], 2),
}
for memory in memories["results"]
]
graph_memories = [
{"source": relation["source"], "relationship": relation["relationship"], "target": relation["target"]}
for relation in memories["relations"]
]
return semantic_memories, graph_memories, end_time - start_time
def answer_question(self, speaker_1_user_id, speaker_2_user_id, question, answer, category):
speaker_1_memories, speaker_1_graph_memories, speaker_1_memory_time = self.search_memory(speaker_1_user_id, question)
speaker_2_memories, speaker_2_graph_memories, speaker_2_memory_time = self.search_memory(speaker_2_user_id, question)
speaker_1_memories, speaker_1_graph_memories, speaker_1_memory_time = self.search_memory(
speaker_1_user_id, question
)
speaker_2_memories, speaker_2_graph_memories, speaker_2_memory_time = self.search_memory(
speaker_2_user_id, question
)
search_1_memory = [f"{item['timestamp']}: {item['memory']}"
for item in speaker_1_memories]
search_2_memory = [f"{item['timestamp']}: {item['memory']}"
for item in speaker_2_memories]
search_1_memory = [f"{item['timestamp']}: {item['memory']}" for item in speaker_1_memories]
search_2_memory = [f"{item['timestamp']}: {item['memory']}" for item in speaker_2_memories]
template = Template(self.ANSWER_PROMPT)
answer_prompt = template.render(
speaker_1_user_id=speaker_1_user_id.split('_')[0],
speaker_2_user_id=speaker_2_user_id.split('_')[0],
speaker_1_user_id=speaker_1_user_id.split("_")[0],
speaker_2_user_id=speaker_2_user_id.split("_")[0],
speaker_1_memories=json.dumps(search_1_memory, indent=4),
speaker_2_memories=json.dumps(search_2_memory, indent=4),
speaker_1_graph_memories=json.dumps(speaker_1_graph_memories, indent=4),
speaker_2_graph_memories=json.dumps(speaker_2_graph_memories, indent=4),
question=question
question=question,
)
t1 = time.time()
response = self.openai_client.chat.completions.create(
model=os.getenv("MODEL"),
messages=[
{"role": "system", "content": answer_prompt}
],
temperature=0.0
model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
)
t2 = time.time()
response_time = t2 - t1
return response.choices[0].message.content, speaker_1_memories, speaker_2_memories, speaker_1_memory_time, speaker_2_memory_time, speaker_1_graph_memories, speaker_2_graph_memories, response_time
return (
response.choices[0].message.content,
speaker_1_memories,
speaker_2_memories,
speaker_1_memory_time,
speaker_2_memory_time,
speaker_1_graph_memories,
speaker_2_graph_memories,
response_time,
)
def process_question(self, val, speaker_a_user_id, speaker_b_user_id):
question = val.get('question', '')
answer = val.get('answer', '')
category = val.get('category', -1)
evidence = val.get('evidence', [])
adversarial_answer = val.get('adversarial_answer', '')
question = val.get("question", "")
answer = val.get("answer", "")
category = val.get("category", -1)
evidence = val.get("evidence", [])
adversarial_answer = val.get("adversarial_answer", "")
response, speaker_1_memories, speaker_2_memories, speaker_1_memory_time, speaker_2_memory_time, speaker_1_graph_memories, speaker_2_graph_memories, response_time = self.answer_question(
speaker_a_user_id,
speaker_b_user_id,
question,
answer,
category
)
(
response,
speaker_1_memories,
speaker_2_memories,
speaker_1_memory_time,
speaker_2_memory_time,
speaker_1_graph_memories,
speaker_2_graph_memories,
response_time,
) = self.answer_question(speaker_a_user_id, speaker_b_user_id, question, answer, category)
result = {
"question": question,
@@ -125,67 +153,63 @@ class MemorySearch:
"adversarial_answer": adversarial_answer,
"speaker_1_memories": speaker_1_memories,
"speaker_2_memories": speaker_2_memories,
'num_speaker_1_memories': len(speaker_1_memories),
'num_speaker_2_memories': len(speaker_2_memories),
'speaker_1_memory_time': speaker_1_memory_time,
'speaker_2_memory_time': speaker_2_memory_time,
"num_speaker_1_memories": len(speaker_1_memories),
"num_speaker_2_memories": len(speaker_2_memories),
"speaker_1_memory_time": speaker_1_memory_time,
"speaker_2_memory_time": speaker_2_memory_time,
"speaker_1_graph_memories": speaker_1_graph_memories,
"speaker_2_graph_memories": speaker_2_graph_memories,
"response_time": response_time
"response_time": response_time,
}
# Save results after each question is processed
with open(self.output_path, 'w') as f:
with open(self.output_path, "w") as f:
json.dump(self.results, f, indent=4)
return result
def process_data_file(self, file_path):
with open(file_path, 'r') as f:
with open(file_path, "r") as f:
data = json.load(f)
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
qa = item['qa']
conversation = item['conversation']
speaker_a = conversation['speaker_a']
speaker_b = conversation['speaker_b']
qa = item["qa"]
conversation = item["conversation"]
speaker_a = conversation["speaker_a"]
speaker_b = conversation["speaker_b"]
speaker_a_user_id = f"{speaker_a}_{idx}"
speaker_b_user_id = f"{speaker_b}_{idx}"
for question_item in tqdm(qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False):
result = self.process_question(
question_item,
speaker_a_user_id,
speaker_b_user_id
)
for question_item in tqdm(
qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
):
result = self.process_question(question_item, speaker_a_user_id, speaker_b_user_id)
self.results[idx].append(result)
# Save results after each question is processed
with open(self.output_path, 'w') as f:
with open(self.output_path, "w") as f:
json.dump(self.results, f, indent=4)
# Final save at the end
with open(self.output_path, 'w') as f:
with open(self.output_path, "w") as f:
json.dump(self.results, f, indent=4)
def process_questions_parallel(self, qa_list, speaker_a_user_id, speaker_b_user_id, max_workers=1):
def process_single_question(val):
result = self.process_question(val, speaker_a_user_id, speaker_b_user_id)
# Save results after each question is processed
with open(self.output_path, 'w') as f:
with open(self.output_path, "w") as f:
json.dump(self.results, f, indent=4)
return result
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(tqdm(
executor.map(process_single_question, qa_list),
total=len(qa_list),
desc="Answering Questions"
))
results = list(
tqdm(executor.map(process_single_question, qa_list), total=len(qa_list), desc="Answering Questions")
)
# Final save at the end
with open(self.output_path, 'w') as f:
with open(self.output_path, "w") as f:
json.dump(self.results, f, indent=4)
return results
+18 -31
View File
@@ -59,23 +59,19 @@ class OpenAIPredict:
self.results = defaultdict(list)
def search_memory(self, idx):
with open(f'memories/{idx}.txt', 'r') as file:
with open(f"memories/{idx}.txt", "r") as file:
memories = file.read()
return memories, 0
def process_question(self, val, idx):
question = val.get('question', '')
answer = val.get('answer', '')
category = val.get('category', -1)
evidence = val.get('evidence', [])
adversarial_answer = val.get('adversarial_answer', '')
question = val.get("question", "")
answer = val.get("answer", "")
category = val.get("category", -1)
evidence = val.get("evidence", [])
adversarial_answer = val.get("adversarial_answer", "")
response, search_memory_time, response_time, context = self.answer_question(
idx,
question
)
response, search_memory_time, response_time, context = self.answer_question(idx, question)
result = {
"question": question,
@@ -86,7 +82,7 @@ class OpenAIPredict:
"adversarial_answer": adversarial_answer,
"search_memory_time": search_memory_time,
"response_time": response_time,
"context": context
"context": context,
}
return result
@@ -95,43 +91,35 @@ class OpenAIPredict:
memories, search_memory_time = self.search_memory(idx)
template = Template(ANSWER_PROMPT)
answer_prompt = template.render(
memories=memories,
question=question
)
answer_prompt = template.render(memories=memories, question=question)
t1 = time.time()
response = self.openai_client.chat.completions.create(
model=os.getenv("MODEL"),
messages=[
{"role": "system", "content": answer_prompt}
],
temperature=0.0
model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
)
t2 = time.time()
response_time = t2 - t1
return response.choices[0].message.content, search_memory_time, response_time, memories
def process_data_file(self, file_path, output_file_path):
with open(file_path, 'r') as f:
with open(file_path, "r") as f:
data = json.load(f)
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
qa = item['qa']
qa = item["qa"]
for question_item in tqdm(qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False):
result = self.process_question(
question_item,
idx
)
for question_item in tqdm(
qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
):
result = self.process_question(question_item, idx)
self.results[idx].append(result)
# Save results after each question is processed
with open(output_file_path, 'w') as f:
with open(output_file_path, "w") as f:
json.dump(self.results, f, indent=4)
# Final save at the end
with open(output_file_path, 'w') as f:
with open(output_file_path, "w") as f:
json.dump(self.results, f, indent=4)
@@ -141,4 +129,3 @@ if __name__ == "__main__":
args = parser.parse_args()
openai_predict = OpenAIPredict()
openai_predict.process_data_file("../../dataset/locomo10.json", args.output_file_path)
+34 -49
View File
@@ -33,10 +33,7 @@ class RAGManager:
def generate_response(self, question, context):
template = Template(PROMPT)
prompt = template.render(
CONTEXT=context,
QUESTION=question
)
prompt = template.render(CONTEXT=context, QUESTION=question)
max_retries = 3
retries = 0
@@ -47,19 +44,21 @@ class RAGManager:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system",
"content": "You are a helpful assistant that can answer "
"questions based on the provided context."
"If the question involves timing, use the conversation date for reference."
"Provide the shortest possible answer."
"Use words directly from the conversation when possible."
"Avoid using subjects in your answer."},
{"role": "user", "content": prompt}
{
"role": "system",
"content": "You are a helpful assistant that can answer "
"questions based on the provided context."
"If the question involves timing, use the conversation date for reference."
"Provide the shortest possible answer."
"Use words directly from the conversation when possible."
"Avoid using subjects in your answer.",
},
{"role": "user", "content": prompt},
],
temperature=0
temperature=0,
)
t2 = time.time()
return response.choices[0].message.content.strip(), t2-t1
return response.choices[0].message.content.strip(), t2 - t1
except Exception as e:
retries += 1
if retries > max_retries:
@@ -69,21 +68,16 @@ class RAGManager:
def clean_chat_history(self, chat_history):
cleaned_chat_history = ""
for c in chat_history:
cleaned_chat_history += (f"{c['timestamp']} | {c['speaker']}: "
f"{c['text']}\n")
cleaned_chat_history += f"{c['timestamp']} | {c['speaker']}: " f"{c['text']}\n"
return cleaned_chat_history
def calculate_embedding(self, document):
response = self.client.embeddings.create(
model=os.getenv("EMBEDDING_MODEL"),
input=document
)
response = self.client.embeddings.create(model=os.getenv("EMBEDDING_MODEL"), input=document)
return response.data[0].embedding
def calculate_similarity(self, embedding1, embedding2):
return np.dot(embedding1, embedding2) / (
np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
return np.dot(embedding1, embedding2) / (np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
def search(self, query, chunks, embeddings, k=1):
"""
@@ -101,10 +95,7 @@ class RAGManager:
"""
t1 = time.time()
query_embedding = self.calculate_embedding(query)
similarities = [
self.calculate_similarity(query_embedding, embedding)
for embedding in embeddings
]
similarities = [self.calculate_similarity(query_embedding, embedding) for embedding in embeddings]
# Get indices of top-k most similar chunks
if k == 1:
@@ -118,7 +109,7 @@ class RAGManager:
combined_chunks = "\n<->\n".join([chunks[i] for i in top_indices])
t2 = time.time()
return combined_chunks, t2-t1
return combined_chunks, t2 - t1
def create_chunks(self, chat_history, chunk_size=500):
"""
@@ -139,7 +130,7 @@ class RAGManager:
# Split into chunks based on token count
for i in range(0, len(tokens), chunk_size):
chunk_tokens = tokens[i:i+chunk_size]
chunk_tokens = tokens[i : i + chunk_size]
chunk = encoding.decode(chunk_tokens)
chunks.append(chunk)
@@ -159,13 +150,9 @@ class RAGManager:
chat_history = value["conversation"]
questions = value["question"]
chunks, embeddings = self.create_chunks(
chat_history, self.chunk_size
)
chunks, embeddings = self.create_chunks(chat_history, self.chunk_size)
for item in tqdm(
questions, desc="Answering questions", leave=False
):
for item in tqdm(questions, desc="Answering questions", leave=False):
question = item["question"]
answer = item.get("answer", "")
category = item["category"]
@@ -174,22 +161,20 @@ class RAGManager:
context = chunks[0]
search_time = 0
else:
context, search_time = self.search(
question, chunks, embeddings, k=self.k
)
response, response_time = self.generate_response(
question, context
)
context, search_time = self.search(question, chunks, embeddings, k=self.k)
response, response_time = self.generate_response(question, context)
FINAL_RESULTS[key].append({
"question": question,
"answer": answer,
"category": category,
"context": context,
"response": response,
"search_time": search_time,
"response_time": response_time,
})
FINAL_RESULTS[key].append(
{
"question": question,
"answer": answer,
"category": category,
"context": context,
"response": response,
"search_time": search_time,
"response_time": response_time,
}
)
with open(output_file_path, "w+") as f:
json.dump(FINAL_RESULTS, f, indent=4)
+2 -11
View File
@@ -1,12 +1,3 @@
TECHNIQUES = [
"mem0",
"rag",
"langmem",
"zep",
"openai"
]
TECHNIQUES = ["mem0", "rag", "langmem", "zep", "openai"]
METHODS = [
"add",
"search"
]
METHODS = ["add", "search"]
+11 -9
View File
@@ -19,12 +19,12 @@ class ZepAdd:
self.load_data()
def load_data(self):
with open(self.data_path, 'r') as f:
with open(self.data_path, "r") as f:
self.data = json.load(f)
return self.data
def process_conversation(self, run_id, item, idx):
conversation = item['conversation']
conversation = item["conversation"]
user_id = f"run_id_{run_id}_experiment_user_{idx}"
session_id = f"run_id_{run_id}_experiment_session_{idx}"
@@ -41,7 +41,7 @@ class ZepAdd:
print("Starting to add memories... for user", user_id)
for key in tqdm(conversation.keys(), desc=f"Processing user {user_id}"):
if key in ['speaker_a', 'speaker_b'] or "date" in key:
if key in ["speaker_a", "speaker_b"] or "date" in key:
continue
date_time_key = key + "_date_time"
@@ -51,11 +51,13 @@ class ZepAdd:
for chat in tqdm(chats, desc=f"Adding chats for {key}", leave=False):
self.zep_client.memory.add(
session_id=session_id,
messages=[Message(
role=chat['speaker'],
role_type="user",
content=f"{timestamp}: {chat['text']}",
)]
messages=[
Message(
role=chat["speaker"],
role_type="user",
content=f"{timestamp}: {chat['text']}",
)
],
)
def process_all_conversations(self, run_id):
@@ -71,4 +73,4 @@ if __name__ == "__main__":
parser.add_argument("--run_id", type=str, required=True)
args = parser.parse_args()
zep_add = ZepAdd(data_path="../../dataset/locomo10.json")
zep_add.process_all_conversations(args.run_id)
zep_add.process_all_conversations(args.run_id)
+28 -35
View File
@@ -42,9 +42,9 @@ class ZepSearch:
return f"{edge.valid_at if edge.valid_at else 'date unknown'} - {(edge.invalid_at if edge.invalid_at else 'present')}"
def compose_search_context(self, edges: list[EntityEdge], nodes: list[EntityNode]) -> str:
facts = [f' - {edge.fact} ({self.format_edge_date_range(edge)})' for edge in edges]
entities = [f' - {node.name}: {node.summary}' for node in nodes]
return TEMPLATE.format(facts='\n'.join(facts), entities='\n'.join(entities))
facts = [f" - {edge.fact} ({self.format_edge_date_range(edge)})" for edge in edges]
entities = [f" - {node.name}: {node.summary}" for node in nodes]
return TEMPLATE.format(facts="\n".join(facts), entities="\n".join(entities))
def search_memory(self, run_id, idx, query, max_retries=3, retry_delay=1):
start_time = time.time()
@@ -52,8 +52,14 @@ class ZepSearch:
while retries < max_retries:
try:
user_id = f"run_id_{run_id}_experiment_user_{idx}"
edges_results = (self.zep_client.graph.search(user_id=user_id, reranker='cross_encoder', query=query, scope='edges', limit=20)).edges
node_results = (self.zep_client.graph.search(user_id=user_id, reranker='rrf', query=query, scope='nodes', limit=20)).nodes
edges_results = (
self.zep_client.graph.search(
user_id=user_id, reranker="cross_encoder", query=query, scope="edges", limit=20
)
).edges
node_results = (
self.zep_client.graph.search(user_id=user_id, reranker="rrf", query=query, scope="nodes", limit=20)
).nodes
context = self.compose_search_context(edges_results, node_results)
break
except Exception as e:
@@ -68,17 +74,13 @@ class ZepSearch:
return context, end_time - start_time
def process_question(self, run_id, val, idx):
question = val.get('question', '')
answer = val.get('answer', '')
category = val.get('category', -1)
evidence = val.get('evidence', [])
adversarial_answer = val.get('adversarial_answer', '')
question = val.get("question", "")
answer = val.get("answer", "")
category = val.get("category", -1)
evidence = val.get("evidence", [])
adversarial_answer = val.get("adversarial_answer", "")
response, search_memory_time, response_time, context = self.answer_question(
run_id,
idx,
question
)
response, search_memory_time, response_time, context = self.answer_question(run_id, idx, question)
result = {
"question": question,
@@ -89,7 +91,7 @@ class ZepSearch:
"adversarial_answer": adversarial_answer,
"search_memory_time": search_memory_time,
"response_time": response_time,
"context": context
"context": context,
}
return result
@@ -98,44 +100,35 @@ class ZepSearch:
context, search_memory_time = self.search_memory(run_id, idx, question)
template = Template(ANSWER_PROMPT_ZEP)
answer_prompt = template.render(
memories=context,
question=question
)
answer_prompt = template.render(memories=context, question=question)
t1 = time.time()
response = self.openai_client.chat.completions.create(
model=os.getenv("MODEL"),
messages=[
{"role": "system", "content": answer_prompt}
],
temperature=0.0
model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
)
t2 = time.time()
response_time = t2 - t1
return response.choices[0].message.content, search_memory_time, response_time, context
def process_data_file(self, file_path, run_id, output_file_path):
with open(file_path, 'r') as f:
with open(file_path, "r") as f:
data = json.load(f)
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
qa = item['qa']
qa = item["qa"]
for question_item in tqdm(qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False):
result = self.process_question(
run_id,
question_item,
idx
)
for question_item in tqdm(
qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
):
result = self.process_question(run_id, question_item, idx)
self.results[idx].append(result)
# Save results after each question is processed
with open(output_file_path, 'w') as f:
with open(output_file_path, "w") as f:
json.dump(self.results, f, indent=4)
# Final save at the end
with open(output_file_path, 'w') as f:
with open(output_file_path, "w") as f:
json.dump(self.results, f, indent=4)
@@ -56,9 +56,7 @@
"\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = (\n",
" \"\"\n",
")"
"os.environ[\"OPENAI_API_KEY\"] = \"\""
]
},
{
@@ -149,7 +147,7 @@
" \"role\": \"assistant\",\n",
" \"content\": \"Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future.\",\n",
" },\n",
"]\n"
"]"
]
},
{
@@ -166,9 +164,7 @@
"outputs": [],
"source": [
"# Store inferred memories (default behavior)\n",
"result = m.add(\n",
" messages, user_id=\"alice\", metadata={\"category\": \"movie_recommendations\"}\n",
")"
"result = m.add(messages, user_id=\"alice\", metadata={\"category\": \"movie_recommendations\"})"
]
},
{
+271
View File
@@ -0,0 +1,271 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "ApdaLD4Qi30H"
},
"source": [
"# Neo4j as Graph Memory"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "l7bi3i21i30I"
},
"source": [
"## Prerequisites\n",
"\n",
"### 1. Install Mem0 with Graph Memory support\n",
"\n",
"To use Mem0 with Graph Memory support, install it using pip:\n",
"\n",
"```bash\n",
"pip install \"mem0ai[graph]\"\n",
"```\n",
"\n",
"This command installs Mem0 along with the necessary dependencies for graph functionality.\n",
"\n",
"### 2. Install Neo4j\n",
"\n",
"To utilize Neo4j as Graph Memory, run it with Docker:\n",
"\n",
"```bash\n",
"docker run \\\n",
" -p 7474:7474 -p 7687:7687 \\\n",
" -e NEO4J_AUTH=neo4j/password \\\n",
" neo4j:5\n",
"```\n",
"\n",
"This command starts Neo4j with default credentials (`neo4j` / `password`) and exposes both the HTTP (7474) and Bolt (7687) ports.\n",
"\n",
"You can access the Neo4j browser at [http://localhost:7474](http://localhost:7474).\n",
"\n",
"Additional information can be found in the [Neo4j documentation](https://neo4j.com/docs/).\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "DkeBdFEpi30I"
},
"source": [
"## Configuration\n",
"\n",
"Do all the imports and configure OpenAI (enter your OpenAI API key):"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "d99EfBpii30I"
},
"outputs": [],
"source": [
"from mem0 import Memory\n",
"\n",
"import os\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = (\n",
" \"\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "QTucZJjIi30J"
},
"source": [
"Set up configuration to use the embedder model and Neo4j as a graph store:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"id": "QSE0RFoSi30J"
},
"outputs": [],
"source": [
"config = {\n",
" \"embedder\": {\n",
" \"provider\": \"openai\",\n",
" \"config\": {\"model\": \"text-embedding-3-large\", \"embedding_dims\": 1536},\n",
" },\n",
" \"graph_store\": {\n",
" \"provider\": \"neo4j\",\n",
" \"config\": {\n",
" \"url\": \"bolt://54.87.227.131:7687\",\n",
" \"username\": \"neo4j\",\n",
" \"password\": \"causes-bins-vines\",\n",
" },\n",
" },\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "OioTnv6xi30J"
},
"source": [
"## Graph Memory initializiation\n",
"\n",
"Initialize Neo4j as a Graph Memory store:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"id": "fX-H9vgNi30J"
},
"outputs": [],
"source": [
"m = Memory.from_config(config_dict=config)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "kr1fVMwEi30J"
},
"source": [
"## Store memories\n",
"\n",
"Create memories:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"id": "sEfogqp_i30J"
},
"outputs": [],
"source": [
"messages = [\n",
" {\n",
" \"role\": \"user\",\n",
" \"content\": \"I'm planning to watch a movie tonight. Any recommendations?\",\n",
" },\n",
" {\n",
" \"role\": \"assistant\",\n",
" \"content\": \"How about a thriller movies? They can be quite engaging.\",\n",
" },\n",
" {\n",
" \"role\": \"user\",\n",
" \"content\": \"I'm not a big fan of thriller movies but I love sci-fi movies.\",\n",
" },\n",
" {\n",
" \"role\": \"assistant\",\n",
" \"content\": \"Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future.\",\n",
" },\n",
"]\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "gtBHCyIgi30J"
},
"source": [
"Store memories in Neo4j:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"id": "BMVGgZMFi30K"
},
"outputs": [],
"source": [
"# Store inferred memories (default behavior)\n",
"result = m.add(\n",
" messages, user_id=\"alice\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "lQRptOywi30K"
},
"source": [
"![](https://github.com/tomasonjo/mem0/blob/neo4jexample/examples/graph-db-demo/alice-memories.png?raw=1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "LBXW7Gv-i30K"
},
"source": [
"## Search memories"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "UHFDeQBEi30K",
"outputId": "2c69de7d-a79a-48f6-e3c4-bd743067857c"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loves sci-fi movies 0.3153664287340898\n",
"Planning to watch a movie tonight 0.09683349296551162\n",
"Not a big fan of thriller movies 0.09468540071789466\n"
]
}
],
"source": [
"for result in m.search(\"what does alice love?\", user_id=\"alice\")[\"results\"]:\n",
" print(result[\"memory\"], result[\"score\"])"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"id": "2jXEIma9kK_Q"
},
"outputs": [],
"source": []
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.2"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
+30 -77
View File
@@ -20,19 +20,19 @@ agent = Agent(
name="Fitness Agent",
model=OpenAIChat(id="gpt-4o"),
description="You are a helpful fitness assistant who remembers past logs and gives personalized suggestions for Anish's training and diet.",
markdown=True
markdown=True,
)
# Store user preferences as memory
def store_user_preferences(conversation: list, user_id: str = USER_ID):
"""Store user preferences from conversation history"""
memory_client.add(conversation, user_id=user_id, output_format='v1.1')
memory_client.add(conversation, user_id=user_id, output_format="v1.1")
# Memory-aware assistant function
def fitness_coach(user_input: str, user_id: str = USER_ID):
memories = memory_client.search(user_input, user_id=user_id) # Search relevant memories bases on user query
memories = memory_client.search(user_input, user_id=user_id) # Search relevant memories bases on user query
memory_context = "\n".join(f"- {m['memory']}" for m in memories)
prompt = f"""You are a fitness assistant who helps Anish with his training, recovery, and diet. You have long-term memory of his health, routines, preferences, and past conversations.
@@ -48,113 +48,66 @@ User query:
memory_client.add(f"User: {user_input}\nAssistant: {response.content}", user_id=user_id)
return response.content
# --------------------------------------------------
# Store user preferences and memories
messages = [
{
"role": "user",
"content": "Hi, I’m Anish. I'm 26 years old, 5'10\", and weigh 72kg. I started working out 6 months ago with the goal of building lean muscle."
"content": "Hi, I’m Anish. I'm 26 years old, 5'10\", and weigh 72kg. I started working out 6 months ago with the goal of building lean muscle.",
},
{
"role": "assistant",
"content": "Got it — you're 26, 5'10\", 72kg, and on a lean muscle journey. Started gym 6 months ago."
"content": "Got it — you're 26, 5'10\", 72kg, and on a lean muscle journey. Started gym 6 months ago.",
},
{
"role": "user",
"content": "I follow a push-pull-legs routine and train 5 times a week. My rest days are Wednesday and Sunday."
"content": "I follow a push-pull-legs routine and train 5 times a week. My rest days are Wednesday and Sunday.",
},
{
"role": "assistant",
"content": "Understood — push-pull-legs split, training 5x/week with rest on Wednesdays and Sundays."
"content": "Understood — push-pull-legs split, training 5x/week with rest on Wednesdays and Sundays.",
},
{"role": "user", "content": "After push days, I usually eat high-protein and moderate-carb meals to recover."},
{"role": "assistant", "content": "Noted — high-protein, moderate-carb meals after push workouts."},
{"role": "user", "content": "For pull days, I take whey protein and eat a banana after training."},
{"role": "assistant", "content": "Logged — whey protein and banana post pull workouts."},
{"role": "user", "content": "On leg days, I make sure to have complex carbs like rice or oats."},
{"role": "assistant", "content": "Noted — complex carbs like rice and oats are part of your leg day meals."},
{
"role": "user",
"content": "After push days, I usually eat high-protein and moderate-carb meals to recover."
},
{
"role": "assistant",
"content": "Noted — high-protein, moderate-carb meals after push workouts."
"content": "I often feel sore after leg days, so I use turmeric milk and magnesium to help with recovery.",
},
{"role": "assistant", "content": "I'll remember turmeric milk and magnesium as part of your leg day recovery."},
{
"role": "user",
"content": "For pull days, I take whey protein and eat a banana after training."
"content": "Last push day, I did 3x8 bench press at 60kg, 4x12 overhead press, and dips. Felt fatigued after.",
},
{
"role": "assistant",
"content": "Logged — whey protein and banana post pull workouts."
"content": "Push day logged — 60kg bench, overhead press, dips. You felt fatigued afterward.",
},
{"role": "user", "content": "I prefer light dinners post-workout like tofu, soup, and vegetables."},
{"role": "assistant", "content": "Got it — light dinners post-workout: tofu, soup, and veggies."},
{
"role": "user",
"content": "On leg days, I make sure to have complex carbs like rice or oats."
},
{
"role": "assistant",
"content": "Noted — complex carbs like rice and oats are part of your leg day meals."
"content": "I have mild lactose intolerance, so I avoid dairy. I use almond milk or lactose-free whey.",
},
{"role": "assistant", "content": "Understood — avoiding regular dairy, using almond milk and lactose-free whey."},
{
"role": "user",
"content": "I often feel sore after leg days, so I use turmeric milk and magnesium to help with recovery."
"content": "I get occasional knee pain, so I avoid deep squats and do more hamstring curls and glute bridges on leg days.",
},
{
"role": "assistant",
"content": "I'll remember turmeric milk and magnesium as part of your leg day recovery."
},
{
"role": "user",
"content": "Last push day, I did 3x8 bench press at 60kg, 4x12 overhead press, and dips. Felt fatigued after."
},
{
"role": "assistant",
"content": "Push day logged — 60kg bench, overhead press, dips. You felt fatigued afterward."
},
{
"role": "user",
"content": "I prefer light dinners post-workout like tofu, soup, and vegetables."
},
{
"role": "assistant",
"content": "Got it — light dinners post-workout: tofu, soup, and veggies."
},
{
"role": "user",
"content": "I have mild lactose intolerance, so I avoid dairy. I use almond milk or lactose-free whey."
},
{
"role": "assistant",
"content": "Understood — avoiding regular dairy, using almond milk and lactose-free whey."
},
{
"role": "user",
"content": "I get occasional knee pain, so I avoid deep squats and do more hamstring curls and glute bridges on leg days."
},
{
"role": "assistant",
"content": "Noted — due to knee discomfort, you substitute deep squats with curls and glute bridges."
},
{
"role": "user",
"content": "I track sleep and notice poor performance when I sleep less than 6 hours."
},
{
"role": "assistant",
"content": "Logged — performance drops when you get under 6 hours of sleep."
},
{
"role": "user",
"content": "I take magnesium supplements to help with muscle recovery and sleep quality."
},
{
"role": "assistant",
"content": "Remembered — magnesium helps you with recovery and sleep."
},
{
"role": "user",
"content": "I avoid caffeine after 4 PM because it affects my sleep."
},
{
"role": "assistant",
"content": "Got it — you avoid caffeine post-4 PM to protect your sleep."
"content": "Noted — due to knee discomfort, you substitute deep squats with curls and glute bridges.",
},
{"role": "user", "content": "I track sleep and notice poor performance when I sleep less than 6 hours."},
{"role": "assistant", "content": "Logged — performance drops when you get under 6 hours of sleep."},
{"role": "user", "content": "I take magnesium supplements to help with muscle recovery and sleep quality."},
{"role": "assistant", "content": "Remembered — magnesium helps you with recovery and sleep."},
{"role": "user", "content": "I avoid caffeine after 4 PM because it affects my sleep."},
{"role": "assistant", "content": "Got it — you avoid caffeine post-4 PM to protect your sleep."},
]
store_user_preferences(messages)
@@ -0,0 +1,208 @@
import asyncio
import warnings
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from mem0 import MemoryClient
warnings.filterwarnings("ignore", category=DeprecationWarning)
# Initialize Mem0 client
mem0_client = MemoryClient()
# Define Memory Tools
def save_patient_info(information: str) -> dict:
"""Saves important patient information to memory."""
print(f"Storing patient information: {information[:30]}...")
# Get user_id from session state or use default
user_id = getattr(save_patient_info, "user_id", "default_user")
# Store in Mem0
mem0_client.add(
[{"role": "user", "content": information}],
user_id=user_id,
run_id="healthcare_session",
metadata={"type": "patient_information"},
)
return {"status": "success", "message": "Information saved"}
def retrieve_patient_info(query: str) -> str:
"""Retrieves relevant patient information from memory."""
print(f"Searching for patient information: {query}")
# Get user_id from session state or use default
user_id = getattr(retrieve_patient_info, "user_id", "default_user")
# Search Mem0
results = mem0_client.search(
query,
user_id=user_id,
run_id="healthcare_session",
limit=5,
threshold=0.7, # Higher threshold for more relevant results
)
if not results:
return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
# Define Healthcare Tools
def schedule_appointment(date: str, time: str, reason: str) -> dict:
"""Schedules a doctor's appointment."""
# In a real app, this would connect to a scheduling system
appointment_id = f"APT-{hash(date + time) % 10000}"
return {
"status": "success",
"appointment_id": appointment_id,
"confirmation": f"Appointment scheduled for {date} at {time} for {reason}",
"message": "Please arrive 15 minutes early to complete paperwork.",
}
# Create the Healthcare Assistant Agent
healthcare_agent = Agent(
name="healthcare_assistant",
model="gemini-1.5-flash", # Using Gemini for healthcare assistant
description="Healthcare assistant that helps patients with health information and appointment scheduling.",
instruction="""You are a helpful Healthcare Assistant with memory capabilities.
Your primary responsibilities are to:
1. Remember patient information using the 'save_patient_info' tool when they share symptoms, conditions, or preferences.
2. Retrieve past patient information using the 'retrieve_patient_info' tool when relevant to the current conversation.
3. Help schedule appointments using the 'schedule_appointment' tool.
IMPORTANT GUIDELINES:
- Always be empathetic, professional, and helpful.
- Save important patient information like symptoms, conditions, allergies, and preferences.
- Check if you have relevant patient information before asking for details they may have shared previously.
- Make it clear you are not a doctor and cannot provide medical diagnosis or treatment.
- For serious symptoms, always recommend consulting a healthcare professional.
- Keep all patient information confidential.
""",
tools=[save_patient_info, retrieve_patient_info, schedule_appointment],
)
# Set Up Session and Runner
session_service = InMemorySessionService()
# Define constants for the conversation
APP_NAME = "healthcare_assistant_app"
USER_ID = "Alex"
SESSION_ID = "session_001"
# Create a session
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
# Create the runner
runner = Runner(agent=healthcare_agent, app_name=APP_NAME, session_service=session_service)
# Interact with the Healthcare Assistant
async def call_agent_async(query, runner, user_id, session_id):
"""Sends a query to the agent and returns the final response."""
print(f"\n>>> Patient: {query}")
# Format the user's message
content = types.Content(role="user", parts=[types.Part(text=query)])
# Set user_id for tools to access
save_patient_info.user_id = user_id
retrieve_patient_info.user_id = user_id
# Run the agent
async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=content):
if event.is_final_response():
if event.content and event.content.parts:
response = event.content.parts[0].text
print(f"<<< Assistant: {response}")
return response
return "No response received."
# Example conversation flow
async def run_conversation():
# First interaction - patient introduces themselves with key information
await call_agent_async(
"Hi, I'm Alex. I've been having headaches for the past week, and I have a penicillin allergy.",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID,
)
# Request for health information
await call_agent_async(
"Can you tell me more about what might be causing my headaches?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID,
)
# Schedule an appointment
await call_agent_async(
"I think I should see a doctor. Can you help me schedule an appointment for next Monday at 2pm?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID,
)
# Test memory - should remember patient name, symptoms, and allergy
await call_agent_async(
"What medications should I avoid for my headaches?", runner=runner, user_id=USER_ID, session_id=SESSION_ID
)
# Interactive mode
async def interactive_mode():
"""Run an interactive chat session with the healthcare assistant."""
print("=== Healthcare Assistant Interactive Mode ===")
print("Enter 'exit' to quit at any time.")
# Get user information
patient_id = input("Enter patient ID (or press Enter for default): ").strip() or USER_ID
session_id = f"session_{hash(patient_id) % 1000:03d}"
# Create session for this user
session_service.create_session(app_name=APP_NAME, user_id=patient_id, session_id=session_id)
print(f"\nStarting conversation with patient ID: {patient_id}")
print("Type your message and press Enter.")
while True:
user_input = input("\n>>> Patient: ").strip()
if user_input.lower() in ["exit", "quit", "bye"]:
print("Ending conversation. Thank you!")
break
await call_agent_async(user_input, runner=runner, user_id=patient_id, session_id=session_id)
# Main execution
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Healthcare Assistant with Memory")
parser.add_argument("--demo", action="store_true", help="Run the demo conversation")
parser.add_argument("--interactive", action="store_true", help="Run in interactive mode")
parser.add_argument("--patient-id", type=str, default=USER_ID, help="Patient ID for the conversation")
args = parser.parse_args()
if args.demo:
asyncio.run(run_conversation())
elif args.interactive:
asyncio.run(interactive_mode())
else:
# Default to demo mode if no arguments provided
asyncio.run(run_conversation())
+10 -20
View File
@@ -16,26 +16,21 @@ from mem0 import Memory
# Configure Mem0 with Grok 3 and Qdrant
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"embedding_model_dims": 384
}
},
"vector_store": {"provider": "qdrant", "config": {"embedding_model_dims": 384}},
"llm": {
"provider": "xai",
"config": {
"model": "grok-3-beta",
"temperature": 0.1,
"max_tokens": 2000,
}
},
},
"embedder": {
"provider": "huggingface",
"config": {
"model": "all-MiniLM-L6-v2" # open embedding model
}
}
},
},
}
# Instantiate memory layer
@@ -57,20 +52,14 @@ def recommend_movie_with_memory(user_id: str, user_query: str):
prompt += f"\nPreviously, the user mentioned: {past_memories}"
# Generate movie recommendation using Grok 3
response = grok_client.chat.completions.create(
model="grok-3-beta",
messages=[
{"role": "user", "content": prompt}
]
)
response = grok_client.chat.completions.create(model="grok-3-beta", messages=[{"role": "user", "content": prompt}])
recommendation = response.choices[0].message.content
# Store conversation in memory
memory.add(
[{"role": "user", "content": user_query},
{"role": "assistant", "content": recommendation}],
[{"role": "user", "content": user_query}, {"role": "assistant", "content": recommendation}],
user_id=user_id,
metadata={"category": "movie"}
metadata={"category": "movie"},
)
return recommendation
@@ -81,10 +70,11 @@ if __name__ == "__main__":
user_id = "arshi"
recommend_movie_with_memory(user_id, "I'm looking for a movie to watch tonight. Any suggestions?")
# OUTPUT: You have watched Intersteller last weekend and you don't like horror movies, maybe you can watch "Purple Hearts" today.
recommend_movie_with_memory(user_id, "Can we skip the tearjerkers? I really enjoyed Notting Hill and Crazy Rich Asians.")
recommend_movie_with_memory(
user_id, "Can we skip the tearjerkers? I really enjoyed Notting Hill and Crazy Rich Asians."
)
# OUTPUT: Got it — no sad endings! You might enjoy "The Proposal" or "Love, Rosie". They’re both light-hearted romcoms with happy vibes.
recommend_movie_with_memory(user_id, "Any light-hearted movie I can watch after work today?")
# OUTPUT: Since you liked Crazy Rich Asians and The Proposal, how about "The Intern" or "Isn’t It Romantic"? Both are upbeat, funny, and perfect for relaxing.
recommend_movie_with_memory(user_id, "I’ve already watched The Intern. Something new maybe?")
# OUTPUT: No problem! Try "Your Place or Mine" - romcoms that match your taste and are tear-free!
+13 -18
View File
@@ -23,8 +23,8 @@ agent = Agent(
name="Personal Agent",
model=OpenAIChat(id="gpt-4o"),
description="You are a helpful personal agent that helps me with day to day activities."
"You can process both text and images.",
markdown=True
"You can process both text and images.",
markdown=True,
)
@@ -35,24 +35,16 @@ def chat_user(user_input: str = None, user_id: str = "user_123", image_path: str
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
# First: the text message
text_msg = {
"role": "user",
"content": user_input
}
text_msg = {"role": "user", "content": user_input}
# Second: the image message
image_msg = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
"content": {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}},
}
# Send both as separate message objects
client.add([text_msg, image_msg], user_id=user_id, output_format='v1.1')
client.add([text_msg, image_msg], user_id=user_id, output_format="v1.1")
print("✅ Image uploaded and stored in memory.")
if user_input:
@@ -92,10 +84,13 @@ print(chat_user("When is my test?", user_id=user_id))
# OUTPUT: Your pilot's test is on your birthday, which is in five days. You're turning 25!
# Good luck with your preparations, and remember to take some time to relax amidst the studying.
print(chat_user("This is the picture of what I brought with me in the trip to Bahamas",
image_path="travel_items.jpeg", # this will be added to Mem0 memory
user_id=user_id))
print(chat_user("hey can you quickly tell me if brought my sunglasses to my trip, not able to find",
user_id=user_id))
print(
chat_user(
"This is the picture of what I brought with me in the trip to Bahamas",
image_path="travel_items.jpeg", # this will be added to Mem0 memory
user_id=user_id,
)
)
print(chat_user("hey can you quickly tell me if brought my sunglasses to my trip, not able to find", user_id=user_id))
# OUTPUT: Yes, you did bring your sunglasses on your trip to the Bahamas along with your laptop, face masks and other items..
# Since you can't find them now, perhaps check the pockets of jackets you wore or in your luggage compartments.
+15 -13
View File
@@ -7,6 +7,7 @@ In order to run this file, you need to set up your Mem0 API at Mem0 platform and
export OPENAI_API_KEY="your_openai_api_key"
export MEM0_API_KEY="your_mem0_api_key"
"""
import asyncio
from agents import Agent, Runner
@@ -23,25 +24,19 @@ study_agent = Agent(
- Identify topics the user has struggled with (e.g., "I'm confused", "this is hard")
- Help with spaced repetition by suggesting topics to revisit based on last review time
- Personalize answers using stored memories
- Summarize PDFs or notes the user uploads""")
- Summarize PDFs or notes the user uploads""",
)
# Upload and store PDF to Mem0
def upload_pdf(pdf_url: str, user_id: str):
pdf_message = {
"role": "user",
"content": {
"type": "pdf_url",
"pdf_url": {"url": pdf_url}
}
}
pdf_message = {"role": "user", "content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}}}
client.add([pdf_message], user_id=user_id)
print("✅ PDF uploaded and processed into memory.")
# Main interaction loop with your personal study buddy
async def study_buddy(user_id: str, topic: str, user_input: str):
memories = client.search(f"{topic}", user_id=user_id)
memory_context = "n".join(f"- {m['memory']}" for m in memories)
@@ -56,9 +51,11 @@ Now respond to the user's new question or comment:
result = await Runner.run(study_agent, prompt)
response = result.final_output
client.add([
{"role": "user", "content": f'''Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}'''}
], user_id=user_id, metadata={"topic": topic})
client.add(
[{"role": "user", "content": f"""Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}"""}],
user_id=user_id,
metadata={"topic": topic},
)
return response
@@ -78,7 +75,12 @@ async def main():
# Demonstrate spaced repetition prompting
topic = "Momentum Conservation"
print(await study_buddy(user_id, topic, "I think we covered this last week. Is it time to review momentum conservation again?"))
print(
await study_buddy(
user_id, topic, "I think we covered this last week. Is it time to review momentum conservation again?"
)
)
if __name__ == "__main__":
asyncio.run(main())
+18 -29
View File
@@ -57,7 +57,7 @@ def initialize_memory():
},
{
"role": "user",
"content": "I prefer brief and concise responses without unnecessary explanations. I get frustrated when assistants are too wordy or repeat information I already know."
"content": "I prefer brief and concise responses without unnecessary explanations. I get frustrated when assistants are too wordy or repeat information I already know.",
},
{
"role": "assistant",
@@ -65,7 +65,7 @@ def initialize_memory():
},
{
"role": "user",
"content": "I like to listen to jazz music when I'm working, especially artists like Miles Davis and John Coltrane. I find it helps me focus and be more productive."
"content": "I like to listen to jazz music when I'm working, especially artists like Miles Davis and John Coltrane. I find it helps me focus and be more productive.",
},
{
"role": "assistant",
@@ -73,7 +73,7 @@ def initialize_memory():
},
{
"role": "user",
"content": "I usually wake up at 7 AM and prefer reminders for meetings 30 minutes in advance. My most productive hours are between 9 AM and noon, so I try to schedule important tasks during that time."
"content": "I usually wake up at 7 AM and prefer reminders for meetings 30 minutes in advance. My most productive hours are between 9 AM and noon, so I try to schedule important tasks during that time.",
},
{
"role": "assistant",
@@ -81,7 +81,7 @@ def initialize_memory():
},
{
"role": "user",
"content": "My favorite color is navy blue, and I prefer dark mode in all my apps. I'm allergic to peanuts, so please remind me to check ingredients when I ask about recipes or restaurants."
"content": "My favorite color is navy blue, and I prefer dark mode in all my apps. I'm allergic to peanuts, so please remind me to check ingredients when I ask about recipes or restaurants.",
},
{
"role": "assistant",
@@ -89,7 +89,7 @@ def initialize_memory():
},
{
"role": "user",
"content": "My partner's name is Jamie, and we have a golden retriever named Max who is 3 years old. My parents live in Chicago, and I try to visit them once every two months."
"content": "My partner's name is Jamie, and we have a golden retriever named Max who is 3 years old. My parents live in Chicago, and I try to visit them once every two months.",
},
{
"role": "assistant",
@@ -135,11 +135,11 @@ def record_audio(filename="input.wav", record_seconds=5):
stream.close()
p.terminate()
with wave.open(filename, 'wb') as wf:
with wave.open(filename, "wb") as wf:
wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(fmt))
wf.setframerate(rate)
wf.writeframes(b''.join(frames))
wf.writeframes(b"".join(frames))
# ------------------ STT USING WHISPER ------------------
@@ -147,10 +147,7 @@ def transcribe_whisper(audio_path):
print("🔎 Transcribing with Whisper...")
try:
with open(audio_path, "rb") as audio_file:
transcript = openai_client.audio.transcriptions.create(
model="whisper-1",
file=audio_file
)
transcript = openai_client.audio.transcriptions.create(model="whisper-1", file=audio_file)
print(f"🗣️ You said: {transcript.text}")
return transcript.text
except Exception as e:
@@ -165,9 +162,7 @@ def get_agent_response(user_input):
try:
task = Task(
description=f"Respond to: {user_input}",
expected_output="A short and relevant reply.",
agent=voice_agent
description=f"Respond to: {user_input}", expected_output="A short and relevant reply.", agent=voice_agent
)
crew = Crew(
agents=[voice_agent],
@@ -175,22 +170,19 @@ def get_agent_response(user_input):
process=Process.sequential,
verbose=True,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": USER_ID}
}
memory_config={"provider": "mem0", "config": {"user_id": USER_ID}},
)
result = crew.kickoff()
# Extract the text response from the complex result object
if hasattr(result, 'raw'):
if hasattr(result, "raw"):
return result.raw
elif isinstance(result, dict) and 'raw' in result:
return result['raw']
elif isinstance(result, dict) and 'tasks_output' in result:
outputs = result['tasks_output']
elif isinstance(result, dict) and "raw" in result:
return result["raw"]
elif isinstance(result, dict) and "tasks_output" in result:
outputs = result["tasks_output"]
if outputs and isinstance(outputs, list) and len(outputs) > 0:
return outputs[0].get('raw', str(result))
return outputs[0].get("raw", str(result))
# Fallback to string representation if we can't extract the raw response
return str(result)
@@ -204,10 +196,7 @@ def get_agent_response(user_input):
def speak_response(text):
print(f"🤖 Agent: {text}")
audio = tts_client.text_to_speech.convert(
text=text,
voice_id="JBFqnCBsd6RMkjVDRZzb",
model_id="eleven_multilingual_v2",
output_format="mp3_44100_128"
text=text, voice_id="JBFqnCBsd6RMkjVDRZzb", model_id="eleven_multilingual_v2", output_format="mp3_44100_128"
)
play(audio)
@@ -220,7 +209,7 @@ def run_voice_agent():
record_audio(tmp_audio.name)
try:
user_text = transcribe_whisper(tmp_audio.name)
if user_text.lower() in ['exit', 'quit', 'stop']:
if user_text.lower() in ["exit", "quit", "stop"]:
print("👋 Exiting.")
break
response = get_agent_response(user_text)
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "2.1.25",
"version": "2.1.30",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -98,7 +98,7 @@
},
"peerDependencies": {
"@anthropic-ai/sdk": "^0.40.1",
"@google/genai": "^0.7.0",
"@google/genai": "^1.2.0",
"@mistralai/mistralai": "^1.5.2",
"@qdrant/js-client-rest": "1.13.0",
"@supabase/supabase-js": "^2.49.1",
@@ -107,6 +107,8 @@
"@types/sqlite3": "3.1.11",
"groq-sdk": "0.3.0",
"@langchain/core": "^0.3.44",
"cloudflare": "^4.2.0",
"@cloudflare/workers-types": "^4.20250504.0",
"neo4j-driver": "^5.28.1",
"ollama": "^0.5.14",
"pg": "8.11.3",
+704 -10
View File
File diff suppressed because it is too large Load Diff
+3 -10
View File
@@ -159,16 +159,9 @@ export default class MemoryClient {
return jsonResponse;
}
_preparePayload(
messages: string | Array<Message>,
options: MemoryOptions,
): object {
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
const payload: any = {};
if (typeof messages === "string") {
payload.messages = [{ role: "user", content: messages }];
} else if (Array.isArray(messages)) {
payload.messages = messages;
}
payload.messages = messages;
return { ...payload, ...options };
}
@@ -217,7 +210,7 @@ export default class MemoryClient {
}
async add(
messages: string | Array<Message>,
messages: Array<Message>,
options: MemoryOptions = {},
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
+2 -1
View File
@@ -23,6 +23,7 @@ export interface MemoryOptions {
custom_instructions?: string;
timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
}
export interface ProjectOptions {
@@ -53,7 +54,7 @@ export interface MultiModalMessages {
}
export interface Messages {
role: string;
role: "user" | "assistant";
content: string | MultiModalMessages;
}
+1 -1
View File
@@ -1,7 +1,7 @@
// @ts-nocheck
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
let version = "2.1.16";
let version = "2.1.26";
// Safely check for process.env in different environments
let MEM0_TELEMETRY = true;
+2
View File
@@ -21,6 +21,7 @@
"@types/redis": "^4.0.10",
"@types/sqlite3": "^3.1.11",
"@types/uuid": "^9.0.8",
"cloudflare": "^4.2.0",
"dotenv": "^16.4.4",
"groq-sdk": "^0.3.0",
"openai": "^4.28.0",
@@ -31,6 +32,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250504.0",
"@types/jest": "^29.5.12",
"jest": "^29.7.0",
"rimraf": "^5.0.5",
+1
View File
@@ -20,6 +20,7 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
llm: {
provider: "openai",
config: {
baseURL: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
modelProperties: undefined,
+1
View File
@@ -80,6 +80,7 @@ export class ConfigManager {
}
return {
baseURL: userConf?.baseURL || defaultConf.baseURL,
apiKey:
userConf?.apiKey !== undefined
? userConf.apiKey
+1
View File
@@ -22,4 +22,5 @@ export * from "./vector_stores/qdrant";
export * from "./vector_stores/redis";
export * from "./vector_stores/supabase";
export * from "./vector_stores/langchain";
export * from "./vector_stores/vectorize";
export * from "./utils/factory";
+4 -1
View File
@@ -7,7 +7,10 @@ export class OpenAILLM implements LLM {
private model: string;
constructor(config: LLMConfig) {
this.openai = new OpenAI({ apiKey: config.apiKey });
this.openai = new OpenAI({
apiKey: config.apiKey,
baseURL: config.baseURL,
});
this.model = config.model || "gpt-4o-mini";
}
+3
View File
@@ -39,6 +39,7 @@ export interface HistoryStoreConfig {
export interface LLMConfig {
provider?: string;
baseURL?: string;
config?: Record<string, any>;
apiKey?: string;
model?: string | any;
@@ -116,6 +117,7 @@ export const MemoryConfigSchema = z.object({
modelProperties: z.record(z.string(), z.any()).optional(),
apiKey: z.string().optional(),
model: z.union([z.string(), z.any()]).optional(),
baseURL: z.string().optional(),
}),
}),
vectorStore: z.object({
@@ -134,6 +136,7 @@ export const MemoryConfigSchema = z.object({
apiKey: z.string().optional(),
model: z.union([z.string(), z.any()]).optional(),
modelProperties: z.record(z.string(), z.any()).optional(),
baseURL: z.string().optional(),
}),
}),
historyDbPath: z.string().optional(),
+3
View File
@@ -16,6 +16,7 @@ import { Embedder } from "../embeddings/base";
import { LLM } from "../llms/base";
import { VectorStore } from "../vector_stores/base";
import { Qdrant } from "../vector_stores/qdrant";
import { VectorizeDB } from "../vector_stores/vectorize";
import { RedisDB } from "../vector_stores/redis";
import { OllamaLLM } from "../llms/ollama";
import { SupabaseDB } from "../vector_stores/supabase";
@@ -90,6 +91,8 @@ export class VectorStoreFactory {
return new SupabaseDB(config as any);
case "langchain":
return new LangchainVectorStore(config as any);
case "vectorize":
return new VectorizeDB(config as any);
default:
throw new Error(`Unsupported vector store provider: ${provider}`);
}
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
TelemetryEventData,
} from "./telemetry.types";
let version = "2.1.16";
let version = "2.1.26";
// Safely check for process.env in different environments
let MEM0_TELEMETRY = true;
@@ -0,0 +1,436 @@
import Cloudflare from "cloudflare";
import type { Vectorize, VectorizeVector } from "@cloudflare/workers-types";
import { VectorStore } from "./base";
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
interface VectorizeConfig extends VectorStoreConfig {
apiKey?: string;
indexName: string;
accountId: string;
}
interface CloudflareVector {
id: string;
values: number[];
metadata?: Record<string, any>;
}
export class VectorizeDB implements VectorStore {
private client: Cloudflare | null = null;
private dimensions: number;
private indexName: string;
private accountId: string;
constructor(config: VectorizeConfig) {
this.client = new Cloudflare({ apiToken: config.apiKey });
this.dimensions = config.dimension || 1536;
this.indexName = config.indexName;
this.accountId = config.accountId;
this.initialize().catch(console.error);
}
async insert(
vectors: number[][],
ids: string[],
payloads: Record<string, any>[],
): Promise<void> {
try {
const vectorObjects: CloudflareVector[] = vectors.map(
(vector, index) => ({
id: ids[index],
values: vector,
metadata: payloads[index] || {},
}),
);
const ndjsonPayload = vectorObjects
.map((v) => JSON.stringify(v))
.join("\n");
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/${this.indexName}/insert`,
{
method: "POST",
headers: {
"Content-Type": "application/x-ndjson",
Authorization: `Bearer ${this.client?.apiToken}`,
},
body: ndjsonPayload,
},
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Failed to insert vectors: ${response.status} ${errorText}`,
);
}
} catch (error) {
console.error("Error inserting vectors:", error);
throw new Error(
`Failed to insert vectors: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async search(
query: number[],
limit: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
try {
const result = await this.client?.vectorize.indexes.query(
this.indexName,
{
account_id: this.accountId,
vector: query,
filter: filters,
returnMetadata: "all",
topK: limit,
},
);
return (
(result?.matches?.map((match) => ({
id: match.id,
payload: match.metadata,
score: match.score,
})) as VectorStoreResult[]) || []
); // Return empty array if result or matches is null/undefined
} catch (error) {
console.error("Error searching vectors:", error);
throw new Error(
`Failed to search vectors: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async get(vectorId: string): Promise<VectorStoreResult | null> {
try {
const result = (await this.client?.vectorize.indexes.getByIds(
this.indexName,
{
account_id: this.accountId,
ids: [vectorId],
},
)) as any;
if (!result?.length) return null;
return {
id: vectorId,
payload: result[0].metadata,
};
} catch (error) {
console.error("Error getting vector:", error);
throw new Error(
`Failed to get vector: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async update(
vectorId: string,
vector: number[],
payload: Record<string, any>,
): Promise<void> {
try {
const data: VectorizeVector = {
id: vectorId,
values: vector,
metadata: payload,
};
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/${this.indexName}/upsert`,
{
method: "POST",
headers: {
"Content-Type": "application/x-ndjson",
Authorization: `Bearer ${this.client?.apiToken}`,
},
body: JSON.stringify(data) + "\n", // ndjson format
},
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Failed to update vector: ${response.status} ${errorText}`,
);
}
} catch (error) {
console.error("Error updating vector:", error);
throw new Error(
`Failed to update vector: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async delete(vectorId: string): Promise<void> {
try {
await this.client?.vectorize.indexes.deleteByIds(this.indexName, {
account_id: this.accountId,
ids: [vectorId],
});
} catch (error) {
console.error("Error deleting vector:", error);
throw new Error(
`Failed to delete vector: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async deleteCol(): Promise<void> {
try {
await this.client?.vectorize.indexes.delete(this.indexName, {
account_id: this.accountId,
});
} catch (error) {
console.error("Error deleting collection:", error);
throw new Error(
`Failed to delete collection: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async list(
filters?: SearchFilters,
limit: number = 20,
): Promise<[VectorStoreResult[], number]> {
try {
const result = await this.client?.vectorize.indexes.query(
this.indexName,
{
account_id: this.accountId,
vector: Array(this.dimensions).fill(0), // Dummy vector for listing
filter: filters,
topK: limit,
returnMetadata: "all",
},
);
const matches =
(result?.matches?.map((match) => ({
id: match.id,
payload: match.metadata,
score: match.score,
})) as VectorStoreResult[]) || [];
return [matches, matches.length];
} catch (error) {
console.error("Error listing vectors:", error);
throw new Error(
`Failed to list vectors: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
private generateUUID(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
},
);
}
async getUserId(): Promise<string> {
try {
let found = false;
for await (const index of this.client!.vectorize.indexes.list({
account_id: this.accountId,
})) {
if (index.name === "memory_migrations") {
found = true;
}
}
if (!found) {
await this.client?.vectorize.indexes.create({
account_id: this.accountId,
name: "memory_migrations",
config: {
dimensions: 1,
metric: "cosine",
},
});
}
// Now try to get the userId
const result: any = await this.client?.vectorize.indexes.query(
"memory_migrations",
{
account_id: this.accountId,
vector: [0],
topK: 1,
returnMetadata: "all",
},
);
if (result.matches.length > 0) {
return result.matches[0].metadata.userId as string;
}
// Generate a random userId if none exists
const randomUserId =
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
const data: VectorizeVector = {
id: this.generateUUID(),
values: [0],
metadata: { userId: randomUserId },
};
await fetch(
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/memory_migrations/upsert`,
{
method: "POST",
headers: {
"Content-Type": "application/x-ndjson",
Authorization: `Bearer ${this.client?.apiToken}`,
},
body: JSON.stringify(data) + "\n", // ndjson format
},
);
return randomUserId;
} catch (error) {
console.error("Error getting user ID:", error);
throw new Error(
`Failed to get user ID: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async setUserId(userId: string): Promise<void> {
try {
// Get existing point ID
const result: any = await this.client?.vectorize.indexes.query(
"memory_migrations",
{
account_id: this.accountId,
vector: [0],
topK: 1,
returnMetadata: "all",
},
);
const pointId =
result.matches.length > 0 ? result.matches[0].id : this.generateUUID();
const data: VectorizeVector = {
id: pointId,
values: [0],
metadata: { userId },
};
await fetch(
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/memory_migrations/upsert`,
{
method: "POST",
headers: {
"Content-Type": "application/x-ndjson",
Authorization: `Bearer ${this.client?.apiToken}`,
},
body: JSON.stringify(data) + "\n", // ndjson format
},
);
} catch (error) {
console.error("Error setting user ID:", error);
throw new Error(
`Failed to set user ID: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async initialize(): Promise<void> {
try {
// Check if the index already exists
let indexFound = false;
for await (const idx of this.client!.vectorize.indexes.list({
account_id: this.accountId,
})) {
if (idx.name === this.indexName) {
indexFound = true;
break;
}
}
// If the index doesn't exist, create it
if (!indexFound) {
try {
await this.client?.vectorize.indexes.create({
account_id: this.accountId,
name: this.indexName,
config: {
dimensions: this.dimensions,
metric: "cosine",
},
});
const properties = ["userId", "agentId", "runId"];
for (const propertyName of properties) {
await this.client?.vectorize.indexes.metadataIndex.create(
this.indexName,
{
account_id: this.accountId,
indexType: "string",
propertyName,
},
);
}
} catch (err: any) {
throw new Error(err);
}
}
// check for metadata index
const metadataIndexes =
await this.client?.vectorize.indexes.metadataIndex.list(
this.indexName,
{
account_id: this.accountId,
},
);
const existingMetadataIndexes = new Set<string>();
for (const metadataIndex of metadataIndexes?.metadataIndexes || []) {
existingMetadataIndexes.add(metadataIndex.propertyName!);
}
const properties = ["userId", "agentId", "runId"];
for (const propertyName of properties) {
if (!existingMetadataIndexes.has(propertyName)) {
await this.client?.vectorize.indexes.metadataIndex.create(
this.indexName,
{
account_id: this.accountId,
indexType: "string",
propertyName,
},
);
}
}
// Create memory_migrations collection if it doesn't exist
let found = false;
for await (const index of this.client!.vectorize.indexes.list({
account_id: this.accountId,
})) {
if (index.name === "memory_migrations") {
found = true;
break;
}
}
if (!found) {
await this.client?.vectorize.indexes.create({
account_id: this.accountId,
name: "memory_migrations",
config: {
dimensions: 1,
metric: "cosine",
},
});
}
} catch (err: any) {
throw new Error(err);
}
}
}
+103 -49
View File
@@ -3,7 +3,7 @@ import logging
import os
import warnings
from functools import wraps
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional
import httpx
import requests
@@ -95,10 +95,7 @@ class MemoryClient:
self.client = client
# Ensure the client has the correct base_url and headers
self.client.base_url = httpx.URL(self.host)
self.client.headers.update({
"Authorization": f"Token {self.api_key}",
"Mem0-User-ID": self.user_id
})
self.client.headers.update({"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id})
else:
self.client = httpx.Client(
base_url=self.host,
@@ -132,11 +129,11 @@ class MemoryClient:
raise ValueError(f"Error: {error_message}")
@api_error_handler
def add(self, messages: Union[str, List[Dict[str, str]]], **kwargs) -> Dict[str, Any]:
def add(self, messages: List[Dict[str, str]], **kwargs) -> Dict[str, Any]:
"""Add a new memory.
Args:
messages: Either a string message or a list of message dictionaries.
messages: A list of message dictionaries.
**kwargs: Additional parameters such as user_id, agent_id, app_id, metadata, filters.
Returns:
@@ -237,7 +234,9 @@ class MemoryClient:
response.raise_for_status()
if "metadata" in kwargs:
del kwargs["metadata"]
capture_client_event("client.search", self, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"})
capture_client_event(
"client.search", self, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"}
)
return response.json()
@api_error_handler
@@ -357,10 +356,7 @@ class MemoryClient:
else:
entities = self.users()
# Filter entities based on provided IDs using list comprehension
to_delete = [
{"type": entity["type"], "name": entity["name"]}
for entity in entities["results"]
]
to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
params = self._prepare_params()
@@ -373,7 +369,9 @@ class MemoryClient:
response.raise_for_status()
capture_client_event(
"client.delete_users", self, {"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "sync"}
"client.delete_users",
self,
{"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "sync"},
)
return {
"message": "Entity deleted successfully."
@@ -454,7 +452,9 @@ class MemoryClient:
"""
response = self.client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
response.raise_for_status()
capture_client_event("client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "sync"})
capture_client_event(
"client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "sync"}
)
return response.json()
@api_error_handler
@@ -472,6 +472,22 @@ class MemoryClient:
capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
return response.json()
@api_error_handler
def get_summary(self, filters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Get the summary of a memory export.
Args:
filters: Optional filters to apply to the summary request
Returns:
Dict containing the export status and summary data
"""
response = self.client.post("/v1/summary/", json=self._prepare_params({"filters": filters}))
response.raise_for_status()
capture_client_event("client.get_summary", self, {"sync_type": "sync"})
return response.json()
@api_error_handler
def get_project(self, fields: Optional[List[str]] = None) -> Dict[str, Any]:
"""Get instructions or categories for the current project.
@@ -504,12 +520,17 @@ class MemoryClient:
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
enable_graph: Optional[bool] = None,
version: Optional[str] = None,
) -> Dict[str, Any]:
"""Update the project settings.
Args:
custom_instructions: New instructions for the project
custom_categories: New categories for the project
retrieval_criteria: New retrieval criteria for the project
enable_graph: Enable or disable the graph for the project
version: Version of the project
Returns:
Dictionary containing the API response.
@@ -521,13 +542,25 @@ class MemoryClient:
if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories")
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
if (
custom_instructions is None
and custom_categories is None
and retrieval_criteria is None
and enable_graph is None
and version is None
):
raise ValueError(
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
)
payload = self._prepare_params(
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
{
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"version": version,
}
)
response = self.client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
@@ -537,7 +570,14 @@ class MemoryClient:
capture_client_event(
"client.update_project",
self,
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "sync"},
{
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"version": version,
"sync_type": "sync",
},
)
return response.json()
@@ -657,9 +697,7 @@ class MemoryClient:
capture_client_event("client.feedback", self, data, {"sync_type": "sync"})
return response.json()
def _prepare_payload(
self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any]
) -> Dict[str, Any]:
def _prepare_payload(self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""Prepare the payload for API requests.
Args:
@@ -670,10 +708,7 @@ class MemoryClient:
A dictionary containing the prepared payload.
"""
payload = {}
if isinstance(messages, str):
payload["messages"] = [{"role": "user", "content": messages}]
elif isinstance(messages, list):
payload["messages"] = messages
payload["messages"] = messages
payload.update({k: v for k, v in kwargs.items() if v is not None})
return payload
@@ -750,10 +785,7 @@ class AsyncMemoryClient:
self.async_client = client
# Ensure the client has the correct base_url and headers
self.async_client.base_url = httpx.URL(self.host)
self.async_client.headers.update({
"Authorization": f"Token {self.api_key}",
"Mem0-User-ID": self.user_id
})
self.async_client.headers.update({"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id})
else:
self.async_client = httpx.AsyncClient(
base_url=self.host,
@@ -768,7 +800,11 @@ class AsyncMemoryClient:
"""Validate the API key by making a test request."""
try:
params = self._prepare_params()
response = requests.get(f"{self.host}/v1/ping/", headers={"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id}, params=params)
response = requests.get(
f"{self.host}/v1/ping/",
headers={"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id},
params=params,
)
data = response.json()
response.raise_for_status()
@@ -779,7 +815,7 @@ class AsyncMemoryClient:
return data.get("user_email")
except requests.HTTPStatusError as e:
except requests.exceptions.HTTPError as e:
try:
error_data = e.response.json()
error_message = error_data.get("detail", str(e))
@@ -787,9 +823,7 @@ class AsyncMemoryClient:
error_message = str(e)
raise ValueError(f"Error: {error_message}")
def _prepare_payload(
self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any]
) -> Dict[str, Any]:
def _prepare_payload(self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""Prepare the payload for API requests.
Args:
@@ -800,10 +834,7 @@ class AsyncMemoryClient:
A dictionary containing the prepared payload.
"""
payload = {}
if isinstance(messages, str):
payload["messages"] = [{"role": "user", "content": messages}]
elif isinstance(messages, list):
payload["messages"] = messages
payload["messages"] = messages
payload.update({k: v for k, v in kwargs.items() if v is not None})
return payload
@@ -840,7 +871,7 @@ class AsyncMemoryClient:
await self.async_client.aclose()
@api_error_handler
async def add(self, messages: Union[str, List[Dict[str, str]]], **kwargs) -> Dict[str, Any]:
async def add(self, messages: List[Dict[str, str]], **kwargs) -> Dict[str, Any]:
kwargs = self._prepare_params(kwargs)
if kwargs.get("output_format") != "v1.1":
kwargs["output_format"] = "v1.1"
@@ -973,10 +1004,7 @@ class AsyncMemoryClient:
else:
entities = await self.users()
# Filter entities based on provided IDs using list comprehension
to_delete = [
{"type": entity["type"], "name": entity["name"]}
for entity in entities["results"]
]
to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
params = self._prepare_params()
@@ -988,7 +1016,11 @@ class AsyncMemoryClient:
response = await self.async_client.delete(f"/v2/entities/{entity['type']}/{entity['name']}/", params=params)
response.raise_for_status()
capture_client_event("client.delete_users", self, {"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "async"})
capture_client_event(
"client.delete_users",
self,
{"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "async"},
)
return {
"message": "Entity deleted successfully."
if (user_id or agent_id or app_id or run_id)
@@ -1091,19 +1123,35 @@ class AsyncMemoryClient:
@api_error_handler
async def update_project(
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None
self,
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
enable_graph: Optional[bool] = None,
version: Optional[str] = None,
) -> Dict[str, Any]:
if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories")
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
if (
custom_instructions is None
and custom_categories is None
and retrieval_criteria is None
and enable_graph is None
and version is None
):
raise ValueError(
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
)
payload = self._prepare_params(
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
{
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"version": version,
}
)
response = await self.async_client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
@@ -1113,7 +1161,14 @@ class AsyncMemoryClient:
capture_client_event(
"client.update_project",
self,
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "async"},
{
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
"enable_graph": enable_graph,
"version": version,
"sync_type": "async",
},
)
return response.json()
@@ -1174,4 +1229,3 @@ class AsyncMemoryClient:
response.raise_for_status()
capture_client_event("client.feedback", self, data, {"sync_type": "async"})
return response.json()
+7
View File
@@ -39,6 +39,8 @@ class BaseLlmConfig(ABC):
deepseek_base_url: Optional[str] = None,
# XAI specific
xai_base_url: Optional[str] = None,
# Sarvam specific
sarvam_base_url: Optional[str] = "https://api.sarvam.ai/v1",
# LM Studio specific
lmstudio_base_url: Optional[str] = "http://localhost:1234/v1",
# AWS Bedrock specific
@@ -89,6 +91,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 sarvam_base_url: Sarvam base URL to be use, defaults to "https://api.sarvam.ai/v1"
:type sarvam_base_url: Optional[str], optional
:param lmstudio_base_url: LM Studio base URL to be use, defaults to "http://localhost:1234/v1"
:type lmstudio_base_url: Optional[str], optional
"""
@@ -125,6 +129,9 @@ class BaseLlmConfig(ABC):
# XAI specific
self.xai_base_url = xai_base_url
# Sarvam specific
self.sarvam_base_url = sarvam_base_url
# LM Studio specific
self.lmstudio_base_url = lmstudio_base_url
+11 -28
View File
@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional, Union, Type
from typing import Any, Dict, Optional, Type, Union
from pydantic import BaseModel, Field, model_validator
@@ -7,33 +7,17 @@ class OpenSearchConfig(BaseModel):
collection_name: str = Field("mem0", description="Name of the index")
host: str = Field("localhost", description="OpenSearch host")
port: int = Field(9200, description="OpenSearch port")
user: Optional[str] = Field(
None, description="Username for authentication"
)
password: Optional[str] = Field(
None, description="Password for authentication"
)
api_key: Optional[str] = Field(
None, description="API key for authentication (if applicable)"
)
embedding_model_dims: int = Field(
1536, description="Dimension of the embedding vector"
)
verify_certs: bool = Field(
False, description="Verify SSL certificates (default False for OpenSearch)"
)
use_ssl: bool = Field(
False, description="Use SSL for connection (default False for OpenSearch)"
)
http_auth: Optional[object] = Field(
None, description="HTTP authentication method / AWS SigV4"
)
user: Optional[str] = Field(None, description="Username for authentication")
password: Optional[str] = Field(None, description="Password for authentication")
api_key: Optional[str] = Field(None, description="API key for authentication (if applicable)")
embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
verify_certs: bool = Field(False, description="Verify SSL certificates (default False for OpenSearch)")
use_ssl: bool = Field(False, description="Use SSL for connection (default False for OpenSearch)")
http_auth: Optional[object] = Field(None, description="HTTP authentication method / AWS SigV4")
connection_class: Optional[Union[str, Type]] = Field(
"RequestsHttpConnection", description="Connection class for OpenSearch"
)
pool_maxsize: int = Field(
20, description="Maximum number of connections in the pool"
)
pool_maxsize: int = Field(20, description="Maximum number of connections in the pool")
@model_validator(mode="before")
@classmethod
@@ -41,7 +25,7 @@ class OpenSearchConfig(BaseModel):
# Check if host is provided
if not values.get("host"):
raise ValueError("Host must be provided for OpenSearch")
return values
@model_validator(mode="before")
@@ -52,7 +36,6 @@ class OpenSearchConfig(BaseModel):
extra_fields = input_fields - allowed_fields
if extra_fields:
raise ValueError(
f"Extra fields not allowed: {', '.join(extra_fields)}. "
f"Allowed fields: {', '.join(allowed_fields)}"
f"Extra fields not allowed: {', '.join(extra_fields)}. " f"Allowed fields: {', '.join(allowed_fields)}"
)
return values
+3 -3
View File
@@ -23,12 +23,12 @@ class AWSBedrockEmbedding(EmbeddingBase):
super().__init__(config)
self.config.model = self.config.model or "amazon.titan-embed-text-v1"
# Get AWS config from environment variables or use defaults
aws_access_key = os.environ.get("AWS_ACCESS_KEY_ID", "")
aws_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
aws_region = os.environ.get("AWS_REGION", "us-west-2")
# Check if AWS config is provided in the config
if hasattr(self.config, "aws_access_key_id"):
aws_access_key = self.config.aws_access_key_id
@@ -36,7 +36,7 @@ class AWSBedrockEmbedding(EmbeddingBase):
aws_secret_key = self.config.aws_secret_access_key
if hasattr(self.config, "aws_region"):
aws_region = self.config.aws_region
self.client = boto3.client(
"bedrock-runtime",
region_name=aws_region,
+1
View File
@@ -11,6 +11,7 @@ logging.getLogger("transformers").setLevel(logging.WARNING)
logging.getLogger("sentence_transformers").setLevel(logging.WARNING)
logging.getLogger("huggingface_hub").setLevel(logging.WARNING)
class HuggingFaceEmbedding(EmbeddingBase):
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config)
+3 -1
View File
@@ -10,6 +10,7 @@ class Neo4jConfig(BaseModel):
username: Optional[str] = Field(None, description="Username for the graph database")
password: Optional[str] = Field(None, description="Password for the graph database")
database: Optional[str] = Field(None, description="Database for the graph database")
base_label: Optional[bool] = Field(None, description="Whether to use base node label __Entity__ for all entities")
@model_validator(mode="before")
def check_host_port_or_path(cls, values):
@@ -21,7 +22,8 @@ class Neo4jConfig(BaseModel):
if not url or not username or not password:
raise ValueError("Please provide 'url', 'username' and 'password'.")
return values
class MemgraphConfig(BaseModel):
url: Optional[str] = Field(None, description="Host address for the graph database")
username: Optional[str] = Field(None, description="Username for the graph database")
+11 -15
View File
@@ -20,18 +20,19 @@ def extract_provider(model: str) -> str:
return provider
raise ValueError(f"Unknown provider in model: {model}")
class AWSBedrockLLM(LLMBase):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config)
if not self.config.model:
self.config.model = "anthropic.claude-3-5-sonnet-20240620-v1:0"
# Get AWS config from environment variables or use defaults
aws_access_key = os.environ.get("AWS_ACCESS_KEY_ID", "")
aws_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
aws_region = os.environ.get("AWS_REGION", "us-west-2")
# Check if AWS config is provided in the config
if hasattr(self.config, "aws_access_key_id"):
aws_access_key = self.config.aws_access_key_id
@@ -39,14 +40,14 @@ class AWSBedrockLLM(LLMBase):
aws_secret_key = self.config.aws_secret_access_key
if hasattr(self.config, "aws_region"):
aws_region = self.config.aws_region
self.client = boto3.client(
"bedrock-runtime",
region_name=aws_region,
aws_access_key_id=aws_access_key if aws_access_key else None,
aws_secret_access_key=aws_secret_key if aws_secret_key else None,
)
self.model_kwargs = {
"temperature": self.config.temperature,
"max_tokens_to_sample": self.config.max_tokens,
@@ -145,7 +146,9 @@ class AWSBedrockLLM(LLMBase):
input_body = {
"inputText": prompt,
"textGenerationConfig": {
"maxTokenCount": self.model_kwargs["max_tokens_to_sample"] or self.model_kwargs["max_tokens"] or 5000,
"maxTokenCount": self.model_kwargs["max_tokens_to_sample"]
or self.model_kwargs["max_tokens"]
or 5000,
"topP": self.model_kwargs["top_p"] or 0.9,
"temperature": self.model_kwargs["temperature"] or 0.1,
},
@@ -243,22 +246,15 @@ class AWSBedrockLLM(LLMBase):
body = json.dumps(input_body)
if provider == "anthropic" or provider == "deepseek":
input_body = {
"messages": [
{
"role": "user",
"content": [{"type": "text", "text": prompt}]
}
],
"messages": [{"role": "user", "content": [{"type": "text", "text": prompt}]}],
"max_tokens": self.model_kwargs["max_tokens_to_sample"] or self.model_kwargs["max_tokens"] or 5000,
"temperature": self.model_kwargs["temperature"] or 0.1,
"top_p": self.model_kwargs["top_p"] or 0.9,
"anthropic_version": "bedrock-2023-05-31",
}
body = json.dumps(input_body)
response = self.client.invoke_model(
body=body,
@@ -272,6 +268,6 @@ class AWSBedrockLLM(LLMBase):
modelId=self.config.model,
accept="application/json",
contentType="application/json",
)
)
return self._parse_response(response, tools)
+1
View File
@@ -24,6 +24,7 @@ class LlmConfig(BaseModel):
"gemini",
"deepseek",
"xai",
"sarvam",
"lmstudio",
"langchain",
):
+87
View File
@@ -0,0 +1,87 @@
import os
import requests
from typing import Dict, List, Optional
from mem0.configs.llms.base import BaseLlmConfig
from mem0.llms.base import LLMBase
class SarvamLLM(LLMBase):
def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config)
# Set default model if not provided
if not self.config.model:
self.config.model = "sarvam-m"
# Get API key from config or environment variable
self.api_key = self.config.api_key or os.getenv("SARVAM_API_KEY")
if not self.api_key:
raise ValueError(
"Sarvam API key is required. Set SARVAM_API_KEY environment variable " "or provide api_key in config."
)
# Set base URL - use config value or environment or default
self.base_url = (
getattr(self.config, "sarvam_base_url", None) or os.getenv("SARVAM_API_BASE") or "https://api.sarvam.ai/v1"
)
def generate_response(self, messages: List[Dict[str, str]], response_format=None) -> str:
"""
Generate a response based on the given messages using Sarvam-M.
Args:
messages (list): List of message dicts containing 'role' and 'content'.
response_format (str or object, optional): Format of the response.
Currently not used by Sarvam API.
Returns:
str: The generated response.
"""
url = f"{self.base_url}/chat/completions"
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
# Prepare the request payload
params = {
"messages": messages,
"model": self.config.model if isinstance(self.config.model, str) else "sarvam-m",
}
# Add standard parameters that already exist in BaseLlmConfig
if self.config.temperature is not None:
params["temperature"] = self.config.temperature
if self.config.max_tokens is not None:
params["max_tokens"] = self.config.max_tokens
if self.config.top_p is not None:
params["top_p"] = self.config.top_p
# Handle Sarvam-specific parameters if model is passed as dict
if isinstance(self.config.model, dict):
# Extract model name
params["model"] = self.config.model.get("name", "sarvam-m")
# Add Sarvam-specific parameters
sarvam_specific_params = ["reasoning_effort", "frequency_penalty", "presence_penalty", "seed", "stop", "n"]
for param in sarvam_specific_params:
if param in self.config.model:
params[param] = self.config.model[param]
try:
response = requests.post(url, headers=headers, json=params, timeout=30)
response.raise_for_status()
result = response.json()
if "choices" in result and len(result["choices"]) > 0:
return result["choices"][0]["message"]["content"]
else:
raise ValueError("No response choices found in Sarvam API response")
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Sarvam API request failed: {e}")
except KeyError as e:
raise ValueError(f"Unexpected response format from Sarvam API: {e}")
+55 -35
View File
@@ -35,11 +35,25 @@ class MemoryGraph:
self.config.graph_store.config.password,
self.config.graph_store.config.database,
refresh_schema=False,
driver_config={"notifications_min_severity":"OFF"},
driver_config={"notifications_min_severity": "OFF"},
)
self.embedding_model = EmbedderFactory.create(
self.config.embedder.provider, self.config.embedder.config, self.config.vector_store.config
)
self.node_label = ":`__Entity__`" if self.config.graph_store.config.base_label else ""
if self.config.graph_store.config.base_label:
# Safely add user_id index
try:
self.graph.query(f"CREATE INDEX entity_single IF NOT EXISTS FOR (n {self.node_label}) ON (n.user_id)")
except Exception:
pass
try: # Safely try to add composite index (Enterprise only)
self.graph.query(
f"CREATE INDEX entity_composite IF NOT EXISTS FOR (n {self.node_label}) ON (n.name, n.user_id)"
)
except Exception:
pass
self.llm_provider = "openai_structured"
if self.config.llm.provider:
@@ -108,8 +122,8 @@ class MemoryGraph:
return search_results
def delete_all(self, filters):
cypher = """
MATCH (n {user_id: $user_id})
cypher = f"""
MATCH (n {self.node_label} {{user_id: $user_id}})
DETACH DELETE n
"""
params = {"user_id": filters["user_id"]}
@@ -127,10 +141,9 @@ class MemoryGraph:
- 'contexts': The base data store response for each memory.
- 'entities': A list of strings representing the nodes and relationships
"""
# return all nodes and relationships
query = """
MATCH (n {user_id: $user_id})-[r]->(m {user_id: $user_id})
query = f"""
MATCH (n {self.node_label} {{user_id: $user_id}})-[r]->(m {self.node_label} {{user_id: $user_id}})
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit
"""
@@ -224,22 +237,21 @@ class MemoryGraph:
def _search_graph_db(self, node_list, filters, limit=100):
"""Search similar nodes among and their respective incoming and outgoing relations."""
result_relations = []
for node in node_list:
n_embedding = self.embedding_model.embed(node)
cypher_query = """
MATCH (n)
cypher_query = f"""
MATCH (n {self.node_label})
WHERE n.embedding IS NOT NULL AND n.user_id = $user_id
WITH n, round(2 * vector.similarity.cosine(n.embedding, $n_embedding) - 1, 4) AS similarity // denormalize for backward compatibility
WHERE similarity >= $threshold
CALL (n) {
CALL (n) {{
MATCH (n)-[r]->(m)
RETURN n.name AS source, elementId(n) AS source_id, type(r) AS relationship, elementId(r) AS relation_id, m.name AS destination, elementId(m) AS destination_id
UNION
MATCH (m)-[r]->(n)
RETURN m.name AS source, elementId(m) AS source_id, type(r) AS relationship, elementId(r) AS relation_id, n.name AS destination, elementId(n) AS destination_id
}
}}
WITH distinct source, source_id, relationship, relation_id, destination, destination_id, similarity //deduplicate
RETURN source, source_id, relationship, relation_id, destination, destination_id, similarity
ORDER BY similarity DESC
@@ -294,9 +306,9 @@ class MemoryGraph:
# Delete the specific relationship between nodes
cypher = f"""
MATCH (n {{name: $source_name, user_id: $user_id}})
MATCH (n {self.node_label} {{name: $source_name, user_id: $user_id}})
-[r:{relationship}]->
(m {{name: $dest_name, user_id: $user_id}})
(m {self.node_label} {{name: $dest_name, user_id: $user_id}})
DELETE r
RETURN
n.name AS source,
@@ -323,7 +335,11 @@ class MemoryGraph:
# types
source_type = entity_type_map.get(source, "__User__")
source_label = self.node_label if self.node_label else f":`{source_type}`"
source_extra_set = f", source:`{source_type}`" if self.node_label else ""
destination_type = entity_type_map.get(destination, "__User__")
destination_label = self.node_label if self.node_label else f":`{destination_type}`"
destination_extra_set = f", destination:`{destination_type}`" if self.node_label else ""
# embeddings
source_embedding = self.embedding_model.embed(source)
@@ -340,10 +356,11 @@ class MemoryGraph:
WHERE elementId(source) = $source_id
SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
MERGE (destination:{destination_type} {{name: $destination_name, user_id: $user_id}})
MERGE (destination {destination_label} {{name: $destination_name, user_id: $user_id}})
ON CREATE SET
destination.created = timestamp(),
destination.mentions = 1
{destination_extra_set}
ON MATCH SET
destination.mentions = coalesce(destination.mentions, 0) + 1
WITH source, destination
@@ -370,10 +387,11 @@ class MemoryGraph:
WHERE elementId(destination) = $destination_id
SET destination.mentions = coalesce(destination.mentions, 0) + 1
WITH destination
MERGE (source:{source_type} {{name: $source_name, user_id: $user_id}})
MERGE (source {source_label} {{name: $source_name, user_id: $user_id}})
ON CREATE SET
source.created = timestamp(),
source.mentions = 1
{source_extra_set}
ON MATCH SET
source.mentions = coalesce(source.mentions, 0) + 1
WITH source, destination
@@ -420,24 +438,26 @@ class MemoryGraph:
}
else:
cypher = f"""
MERGE (n:{source_type} {{name: $source_name, user_id: $user_id}})
ON CREATE SET n.created = timestamp(),
n.mentions = 1
ON MATCH SET n.mentions = coalesce(n.mentions, 0) + 1
WITH n
CALL db.create.setNodeVectorProperty(n, 'embedding', $source_embedding)
WITH n
MERGE (m:{destination_type} {{name: $dest_name, user_id: $user_id}})
ON CREATE SET m.created = timestamp(),
m.mentions = 1
ON MATCH SET m.mentions = coalesce(m.mentions, 0) + 1
WITH n, m
CALL db.create.setNodeVectorProperty(m, 'embedding', $source_embedding)
WITH n, m
MERGE (n)-[rel:{relationship}]->(m)
MERGE (source {source_label} {{name: $source_name, user_id: $user_id}})
ON CREATE SET source.created = timestamp(),
source.mentions = 1
{source_extra_set}
ON MATCH SET source.mentions = coalesce(source.mentions, 0) + 1
WITH source
CALL db.create.setNodeVectorProperty(source, 'embedding', $source_embedding)
WITH source
MERGE (destination {destination_label} {{name: $dest_name, user_id: $user_id}})
ON CREATE SET destination.created = timestamp(),
destination.mentions = 1
{destination_extra_set}
ON MATCH SET destination.mentions = coalesce(destination.mentions, 0) + 1
WITH source, destination
CALL db.create.setNodeVectorProperty(destination, 'embedding', $source_embedding)
WITH source, destination
MERGE (source)-[rel:{relationship}]->(destination)
ON CREATE SET rel.created = timestamp(), rel.mentions = 1
ON MATCH SET rel.mentions = coalesce(rel.mentions, 0) + 1
RETURN n.name AS source, type(rel) AS relationship, m.name AS target
RETURN source.name AS source, type(rel) AS relationship, destination.name AS target
"""
params = {
"source_name": source,
@@ -458,8 +478,8 @@ class MemoryGraph:
return entity_list
def _search_source_node(self, source_embedding, user_id, threshold=0.9):
cypher = """
MATCH (source_candidate)
cypher = f"""
MATCH (source_candidate {self.node_label})
WHERE source_candidate.embedding IS NOT NULL
AND source_candidate.user_id = $user_id
@@ -484,8 +504,8 @@ class MemoryGraph:
return result
def _search_destination_node(self, destination_embedding, user_id, threshold=0.9):
cypher = """
MATCH (destination_candidate)
cypher = f"""
MATCH (destination_candidate {self.node_label})
WHERE destination_candidate.embedding IS NOT NULL
AND destination_candidate.user_id = $user_id
+689 -412
View File
File diff suppressed because it is too large Load Diff
+19 -57
View File
@@ -5,16 +5,12 @@ from mem0.memory.utils import format_entities
try:
from langchain_memgraph import Memgraph
except ImportError:
raise ImportError(
"langchain_memgraph is not installed. Please install it using pip install langchain-memgraph"
)
raise ImportError("langchain_memgraph is not installed. Please install it using pip install langchain-memgraph")
try:
from rank_bm25 import BM25Okapi
except ImportError:
raise ImportError(
"rank_bm25 is not installed. Please install it using pip install rank-bm25"
)
raise ImportError("rank_bm25 is not installed. Please install it using pip install rank-bm25")
from mem0.graphs.tools import (
DELETE_MEMORY_STRUCT_TOOL_GRAPH,
@@ -74,22 +70,14 @@ class MemoryGraph:
filters (dict): A dictionary containing filters to be applied during the addition.
"""
entity_type_map = self._retrieve_nodes_from_data(data, filters)
to_be_added = self._establish_nodes_relations_from_data(
data, filters, entity_type_map
)
search_output = self._search_graph_db(
node_list=list(entity_type_map.keys()), filters=filters
)
to_be_deleted = self._get_delete_entities_from_search_output(
search_output, data, filters
)
to_be_added = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
search_output = self._search_graph_db(node_list=list(entity_type_map.keys()), filters=filters)
to_be_deleted = self._get_delete_entities_from_search_output(search_output, data, filters)
# TODO: Batch queries with APOC plugin
# TODO: Add more filter support
deleted_entities = self._delete_entities(to_be_deleted, filters["user_id"])
added_entities = self._add_entities(
to_be_added, filters["user_id"], entity_type_map
)
added_entities = self._add_entities(to_be_added, filters["user_id"], entity_type_map)
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
@@ -108,16 +96,13 @@ class MemoryGraph:
- "entities": List of related graph data based on the query.
"""
entity_type_map = self._retrieve_nodes_from_data(query, filters)
search_output = self._search_graph_db(
node_list=list(entity_type_map.keys()), filters=filters
)
search_output = self._search_graph_db(node_list=list(entity_type_map.keys()), filters=filters)
if not search_output:
return []
search_outputs_sequence = [
[item["source"], item["relationship"], item["destination"]]
for item in search_output
[item["source"], item["relationship"], item["destination"]] for item in search_output
]
bm25 = BM25Okapi(search_outputs_sequence)
@@ -126,9 +111,7 @@ class MemoryGraph:
search_results = []
for item in reranked_results:
search_results.append(
{"source": item[0], "relationship": item[1], "destination": item[2]}
)
search_results.append({"source": item[0], "relationship": item[1], "destination": item[2]})
logger.info(f"Returned {len(search_results)} search results")
@@ -161,9 +144,7 @@ class MemoryGraph:
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit
"""
results = self.graph.query(
query, params={"user_id": filters["user_id"], "limit": limit}
)
results = self.graph.query(query, params={"user_id": filters["user_id"], "limit": limit})
final_results = []
for result in results:
@@ -208,13 +189,8 @@ class MemoryGraph:
f"Error in search tool: {e}, llm_provider={self.llm_provider}, search_results={search_results}"
)
entity_type_map = {
k.lower().replace(" ", "_"): v.lower().replace(" ", "_")
for k, v in entity_type_map.items()
}
logger.debug(
f"Entity type map: {entity_type_map}\n search_results={search_results}"
)
entity_type_map = {k.lower().replace(" ", "_"): v.lower().replace(" ", "_") for k, v in entity_type_map.items()}
logger.debug(f"Entity type map: {entity_type_map}\n search_results={search_results}")
return entity_type_map
def _establish_nodes_relations_from_data(self, data, filters, entity_type_map):
@@ -223,9 +199,7 @@ class MemoryGraph:
messages = [
{
"role": "system",
"content": EXTRACT_RELATIONS_PROMPT.replace(
"USER_ID", filters["user_id"]
).replace(
"content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]).replace(
"CUSTOM_PROMPT", f"4. {self.config.graph_store.custom_prompt}"
),
},
@@ -235,9 +209,7 @@ class MemoryGraph:
messages = [
{
"role": "system",
"content": EXTRACT_RELATIONS_PROMPT.replace(
"USER_ID", filters["user_id"]
),
"content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]),
},
{
"role": "user",
@@ -304,9 +276,7 @@ class MemoryGraph:
def _get_delete_entities_from_search_output(self, search_output, data, filters):
"""Get the entities to be deleted from the search output."""
search_output_string = format_entities(search_output)
system_prompt, user_prompt = get_delete_messages(
search_output_string, data, filters["user_id"]
)
system_prompt, user_prompt = get_delete_messages(search_output_string, data, filters["user_id"])
_tools = [DELETE_MEMORY_TOOL_GRAPH]
if self.llm_provider in ["azure_openai_structured", "openai_structured"]:
@@ -379,12 +349,8 @@ class MemoryGraph:
# search for the nodes with the closest embeddings; this is basically
# comparison of one embedding to all embeddings in a graph -> vector
# search with cosine similarity metric
source_node_search_result = self._search_source_node(
source_embedding, user_id, threshold=0.9
)
destination_node_search_result = self._search_destination_node(
dest_embedding, user_id, threshold=0.9
)
source_node_search_result = self._search_source_node(source_embedding, user_id, threshold=0.9)
destination_node_search_result = self._search_destination_node(dest_embedding, user_id, threshold=0.9)
# TODO: Create a cypher query and common params for all the cases
if not destination_node_search_result and source_node_search_result:
@@ -424,9 +390,7 @@ class MemoryGraph:
"""
params = {
"destination_id": destination_node_search_result[0][
"id(destination_candidate)"
],
"destination_id": destination_node_search_result[0]["id(destination_candidate)"],
"source_name": source,
"source_embedding": source_embedding,
"user_id": user_id,
@@ -445,9 +409,7 @@ class MemoryGraph:
"""
params = {
"source_id": source_node_search_result[0]["id(source_candidate)"],
"destination_id": destination_node_search_result[0][
"id(destination_candidate)"
],
"destination_id": destination_node_search_result[0]["id(destination_candidate)"],
"user_id": user_id,
}
else:
+171 -97
View File
@@ -1,110 +1,151 @@
import logging
import sqlite3
import threading
import uuid
from typing import Any, Dict, List, Optional
logger = logging.getLogger(__name__)
class SQLiteManager:
def __init__(self, db_path=":memory:"):
self.connection = sqlite3.connect(db_path, check_same_thread=False)
def __init__(self, db_path: str = ":memory:"):
self.db_path = db_path
self.connection = sqlite3.connect(self.db_path, check_same_thread=False)
self._lock = threading.Lock()
self._migrate_history_table()
self._create_history_table()
def _migrate_history_table(self):
def _migrate_history_table(self) -> None:
"""
If a pre-existing history table had the old group-chat columns,
rename it, create the new schema, copy the intersecting data, then
drop the old table.
"""
with self._lock:
with self.connection:
cursor = self.connection.cursor()
try:
# Start a transaction
self.connection.execute("BEGIN")
cur = self.connection.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='history'")
table_exists = cursor.fetchone() is not None
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='history'")
if cur.fetchone() is None:
self.connection.execute("COMMIT")
return # nothing to migrate
if table_exists:
# Get the current schema of the history table
cursor.execute("PRAGMA table_info(history)")
current_schema = {row[1]: row[2] for row in cursor.fetchall()}
cur.execute("PRAGMA table_info(history)")
old_cols = {row[1] for row in cur.fetchall()}
# Define the expected schema
expected_schema = {
"id": "TEXT",
"memory_id": "TEXT",
"old_memory": "TEXT",
"new_memory": "TEXT",
"new_value": "TEXT",
"event": "TEXT",
"created_at": "DATETIME",
"updated_at": "DATETIME",
"is_deleted": "INTEGER",
}
expected_cols = {
"id",
"memory_id",
"old_memory",
"new_memory",
"event",
"created_at",
"updated_at",
"is_deleted",
"actor_id",
"role",
}
# Check if the schemas are the same
if current_schema != expected_schema:
# Rename the old table
cursor.execute("ALTER TABLE history RENAME TO old_history")
if old_cols == expected_cols:
self.connection.execute("COMMIT")
return
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS history (
id TEXT PRIMARY KEY,
memory_id TEXT,
old_memory TEXT,
new_memory TEXT,
new_value TEXT,
event TEXT,
created_at DATETIME,
updated_at DATETIME,
is_deleted INTEGER
)
"""
)
logger.info("Migrating history table to new schema (no convo columns).")
# Copy data from the old table to the new table
cursor.execute(
"""
INSERT INTO history (id, memory_id, old_memory, new_memory, new_value, event, created_at, updated_at, is_deleted)
SELECT id, memory_id, prev_value, new_value, new_value, event, timestamp, timestamp, is_deleted
FROM old_history
""" # noqa: E501
)
# Clean up any existing history_old table from previous failed migration
cur.execute("DROP TABLE IF EXISTS history_old")
cursor.execute("DROP TABLE old_history")
# Rename the current history table
cur.execute("ALTER TABLE history RENAME TO history_old")
self.connection.commit()
def _create_history_table(self):
with self._lock:
with self.connection:
self.connection.execute(
# Create the new history table with updated schema
cur.execute(
"""
CREATE TABLE IF NOT EXISTS history (
id TEXT PRIMARY KEY,
memory_id TEXT,
old_memory TEXT,
new_memory TEXT,
new_value TEXT,
event TEXT,
created_at DATETIME,
updated_at DATETIME,
is_deleted INTEGER
CREATE TABLE history (
id TEXT PRIMARY KEY,
memory_id TEXT,
old_memory TEXT,
new_memory TEXT,
event TEXT,
created_at DATETIME,
updated_at DATETIME,
is_deleted INTEGER,
actor_id TEXT,
role TEXT
)
"""
)
def add_history(
self,
memory_id,
old_memory,
new_memory,
event,
created_at=None,
updated_at=None,
is_deleted=0,
):
# Copy data from old table to new table
intersecting = list(expected_cols & old_cols)
if intersecting:
cols_csv = ", ".join(intersecting)
cur.execute(f"INSERT INTO history ({cols_csv}) SELECT {cols_csv} FROM history_old")
# Drop the old table
cur.execute("DROP TABLE history_old")
# Commit the transaction
self.connection.execute("COMMIT")
logger.info("History table migration completed successfully.")
except Exception as e:
# Rollback the transaction on any error
self.connection.execute("ROLLBACK")
logger.error(f"History table migration failed: {e}")
raise
def _create_history_table(self) -> None:
with self._lock:
with self.connection:
try:
self.connection.execute("BEGIN")
self.connection.execute(
"""
INSERT INTO history (id, memory_id, old_memory, new_memory, event, created_at, updated_at, is_deleted)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
CREATE TABLE IF NOT EXISTS history (
id TEXT PRIMARY KEY,
memory_id TEXT,
old_memory TEXT,
new_memory TEXT,
event TEXT,
created_at DATETIME,
updated_at DATETIME,
is_deleted INTEGER,
actor_id TEXT,
role TEXT
)
"""
)
self.connection.execute("COMMIT")
except Exception as e:
self.connection.execute("ROLLBACK")
logger.error(f"Failed to create history table: {e}")
raise
def add_history(
self,
memory_id: str,
old_memory: Optional[str],
new_memory: Optional[str],
event: str,
*,
created_at: Optional[str] = None,
updated_at: Optional[str] = None,
is_deleted: int = 0,
actor_id: Optional[str] = None,
role: Optional[str] = None,
) -> None:
with self._lock:
try:
self.connection.execute("BEGIN")
self.connection.execute(
"""
INSERT INTO history (
id, memory_id, old_memory, new_memory, event,
created_at, updated_at, is_deleted, actor_id, role
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
str(uuid.uuid4()),
@@ -115,30 +156,63 @@ class SQLiteManager:
created_at,
updated_at,
is_deleted,
actor_id,
role,
),
)
self.connection.execute("COMMIT")
except Exception as e:
self.connection.execute("ROLLBACK")
logger.error(f"Failed to add history record: {e}")
raise
def get_history(self, memory_id):
def get_history(self, memory_id: str) -> List[Dict[str, Any]]:
with self._lock:
cursor = self.connection.execute(
cur = self.connection.execute(
"""
SELECT id, memory_id, old_memory, new_memory, event, created_at, updated_at
SELECT id, memory_id, old_memory, new_memory, event,
created_at, updated_at, is_deleted, actor_id, role
FROM history
WHERE memory_id = ?
ORDER BY updated_at ASC
ORDER BY created_at ASC, DATETIME(updated_at) ASC
""",
(memory_id,),
)
rows = cursor.fetchall()
return [
{
"id": row[0],
"memory_id": row[1],
"old_memory": row[2],
"new_memory": row[3],
"event": row[4],
"created_at": row[5],
"updated_at": row[6],
}
for row in rows
]
rows = cur.fetchall()
return [
{
"id": r[0],
"memory_id": r[1],
"old_memory": r[2],
"new_memory": r[3],
"event": r[4],
"created_at": r[5],
"updated_at": r[6],
"is_deleted": bool(r[7]),
"actor_id": r[8],
"role": r[9],
}
for r in rows
]
def reset(self) -> None:
"""Drop and recreate the history table."""
with self._lock:
try:
self.connection.execute("BEGIN")
self.connection.execute("DROP TABLE IF EXISTS history")
self.connection.execute("COMMIT")
self._create_history_table()
except Exception as e:
self.connection.execute("ROLLBACK")
logger.error(f"Failed to reset history table: {e}")
raise
def close(self) -> None:
if self.connection:
self.connection.close()
self.connection = None
def __del__(self):
self.close()

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