Compare commits

..
27 Commits
Author SHA1 Message Date
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
151 changed files with 5094 additions and 2165 deletions
+1
View File
@@ -56,6 +56,7 @@ jobs:
run: | run: |
make install_all make install_all
pip install -e ".[test]" pip install -e ".[test]"
pip install pinecone pinecone-text
if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true' if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
- name: Run Formatting - name: Run Formatting
run: | run: |
+11 -25
View File
@@ -13,7 +13,7 @@
"import anthropic\n", "import anthropic\n",
"\n", "\n",
"# Set up environment variables\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\"" "os.environ[\"ANTHROPIC_API_KEY\"] = \"your_anthropic_api_key\""
] ]
}, },
@@ -33,7 +33,7 @@
" \"model\": \"claude-3-5-sonnet-latest\",\n", " \"model\": \"claude-3-5-sonnet-latest\",\n",
" \"temperature\": 0.1,\n", " \"temperature\": 0.1,\n",
" \"max_tokens\": 2000,\n", " \"max_tokens\": 2000,\n",
" }\n", " },\n",
" }\n", " }\n",
" }\n", " }\n",
" self.client = anthropic.Client(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\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", " - Keep track of open issues and follow-ups\n",
" \"\"\"\n", " \"\"\"\n",
"\n", "\n",
" def store_customer_interaction(self,\n", " def store_customer_interaction(self, user_id: str, message: str, response: str, metadata: Dict = None):\n",
" user_id: str,\n",
" message: str,\n",
" response: str,\n",
" metadata: Dict = None):\n",
" \"\"\"Store customer interaction in memory.\"\"\"\n", " \"\"\"Store customer interaction in memory.\"\"\"\n",
" if metadata is None:\n", " if metadata is None:\n",
" metadata = {}\n", " metadata = {}\n",
@@ -63,24 +59,17 @@
" metadata[\"timestamp\"] = datetime.now().isoformat()\n", " metadata[\"timestamp\"] = datetime.now().isoformat()\n",
"\n", "\n",
" # Format conversation for storage\n", " # Format conversation for storage\n",
" conversation = [\n", " conversation = [{\"role\": \"user\", \"content\": message}, {\"role\": \"assistant\", \"content\": response}]\n",
" {\"role\": \"user\", \"content\": message},\n",
" {\"role\": \"assistant\", \"content\": response}\n",
" ]\n",
"\n", "\n",
" # Store in Mem0\n", " # Store in Mem0\n",
" self.memory.add(\n", " self.memory.add(conversation, user_id=user_id, metadata=metadata)\n",
" conversation,\n",
" user_id=user_id,\n",
" metadata=metadata\n",
" )\n",
"\n", "\n",
" def get_relevant_history(self, user_id: str, query: str) -> List[Dict]:\n", " def get_relevant_history(self, user_id: str, query: str) -> List[Dict]:\n",
" \"\"\"Retrieve relevant past interactions.\"\"\"\n", " \"\"\"Retrieve relevant past interactions.\"\"\"\n",
" return self.memory.search(\n", " return self.memory.search(\n",
" query=query,\n", " query=query,\n",
" user_id=user_id,\n", " user_id=user_id,\n",
" limit=5 # Adjust based on needs\n", " limit=5, # Adjust based on needs\n",
" )\n", " )\n",
"\n", "\n",
" def handle_customer_query(self, user_id: str, query: str) -> str:\n", " def handle_customer_query(self, user_id: str, query: str) -> str:\n",
@@ -112,15 +101,12 @@
" model=\"claude-3-5-sonnet-latest\",\n", " model=\"claude-3-5-sonnet-latest\",\n",
" messages=[{\"role\": \"user\", \"content\": prompt}],\n", " messages=[{\"role\": \"user\", \"content\": prompt}],\n",
" max_tokens=2000,\n", " max_tokens=2000,\n",
" temperature=0.1\n", " temperature=0.1,\n",
" )\n", " )\n",
"\n", "\n",
" # Store interaction\n", " # Store interaction\n",
" self.store_customer_interaction(\n", " self.store_customer_interaction(\n",
" user_id=user_id,\n", " user_id=user_id, message=query, response=response, metadata={\"type\": \"support_query\"}\n",
" message=query,\n",
" response=response,\n",
" metadata={\"type\": \"support_query\"}\n",
" )\n", " )\n",
"\n", "\n",
" return response.content[0].text" " return response.content[0].text"
@@ -203,12 +189,12 @@
" # Get user input\n", " # Get user input\n",
" query = input()\n", " query = input()\n",
" print(\"Customer:\", query)\n", " print(\"Customer:\", query)\n",
" \n", "\n",
" # Check if user wants to exit\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", " print(\"Thank you for using our support service. Goodbye!\")\n",
" break\n", " break\n",
" \n", "\n",
" # Handle the query and print the response\n", " # Handle the query and print the response\n",
" response = chatbot.handle_customer_query(user_id, query)\n", " response = chatbot.handle_customer_query(user_id, query)\n",
" print(\"Support:\", response, \"\\n\\n\")" " print(\"Support:\", response, \"\\n\\n\")"
+14 -16
View File
@@ -25,7 +25,8 @@
"source": [ "source": [
"# Set up ENV Vars\n", "# Set up ENV Vars\n",
"import os\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", "assistant_id = os.environ.get(\"ASSISTANT_ID\", None)\n",
"\n", "\n",
"# LLM Configuration\n", "# LLM Configuration\n",
"CACHE_SEED = 42 # choose your poison\n", "CACHE_SEED = 42 # choose your poison\n",
"llm_config = {\n", "llm_config = {\n",
" \"config_list\": [\n", " \"config_list\": [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}],\n",
" {\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}\n",
" ],\n",
" \"cache_seed\": CACHE_SEED,\n", " \"cache_seed\": CACHE_SEED,\n",
" \"timeout\": 120,\n", " \"timeout\": 120,\n",
" \"temperature\": 0.0,\n", " \"temperature\": 0.0,\n",
@@ -348,7 +347,7 @@
"source": [ "source": [
"# Retrieve the memory\n", "# Retrieve the memory\n",
"relevant_memories = MEM0_MEMORY_CLIENT.search(user_query, user_id=USER_ID, limit=3)\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:\")\n",
"print(relevant_memories_text)\n", "print(relevant_memories_text)\n",
"\n", "\n",
@@ -389,8 +388,8 @@
"# - Enables more context-aware and personalized agent responses.\n", "# - Enables more context-aware and personalized agent responses.\n",
"# - Bridges the gap between human input and AI processing in complex workflows.\n", "# - Bridges the gap between human input and AI processing in complex workflows.\n",
"\n", "\n",
"class Mem0ProxyCoderAgent(UserProxyAgent):\n",
"\n", "\n",
"class Mem0ProxyCoderAgent(UserProxyAgent):\n",
" def __init__(self, *args, **kwargs):\n", " def __init__(self, *args, **kwargs):\n",
" super().__init__(*args, **kwargs)\n", " super().__init__(*args, **kwargs)\n",
" self.memory = MEM0_MEMORY_CLIENT\n", " self.memory = MEM0_MEMORY_CLIENT\n",
@@ -399,15 +398,14 @@
" def initiate_chat(self, assistant, message):\n", " def initiate_chat(self, assistant, message):\n",
" # Retrieve memory for the agent\n", " # Retrieve memory for the agent\n",
" agent_memories = self.memory.search(message, agent_id=self.agent_id, limit=3)\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", " prompt = f\"{message}\\n Coding Preferences: \\n{str(agent_memories_txt)}\"\n",
" response = super().initiate_chat(assistant, message=prompt)\n", " response = super().initiate_chat(assistant, message=prompt)\n",
" # Add new memory after processing the message\n", " # Add new memory after processing the message\n",
" response_dist = response.__dict__ if not isinstance(response, dict) else response\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", " MEMORY_DATA = [{\"role\": \"user\", \"content\": message}, {\"role\": \"assistant\", \"content\": response_dist}]\n",
" self.memory.add(MEMORY_DATA, agent_id=self.agent_id)\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", "from cookbooks.helper.mem0_teachability import Mem0Teachability\n",
"\n", "\n",
"teachability = Mem0Teachability(\n", "teachability = Mem0Teachability(\n",
" verbosity=2, # for visibility of what's happening\n", " verbosity=2, # for visibility of what's happening\n",
" recall_threshold=0.5,\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", " reset_db=False, # Use True to force-reset the memo DB, and False to use an existing DB.\n",
" agent_id=AGENT_ID,\n", " agent_id=AGENT_ID,\n",
" memory_client = MEM0_MEMORY_CLIENT,\n", " memory_client=MEM0_MEMORY_CLIENT,\n",
" )\n", ")\n",
"teachability.add_to_agent(user_proxy)" "teachability.add_to_agent(user_proxy)"
] ]
}, },
@@ -3,7 +3,7 @@ title: 'Get Memories (v2)'
openapi: post /v2/memories/ 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 - `in`: Matches any of the values specified
- `gte`: Greater than or equal to - `gte`: Greater than or equal to
- `lte`: Less than or equal to - `lte`: Less than or equal to
@@ -3,7 +3,7 @@ title: 'Search Memories (v2)'
openapi: post /v2/memories/search/ 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 - `in`: Matches any of the values specified
- `gte`: Greater than or equal to - `gte`: Greater than or equal to
- `lte`: Less than or equal to - `lte`: Less than or equal to
+25
View File
@@ -288,6 +288,26 @@ mode: "wide"
<Tab title="TypeScript"> <Tab title="TypeScript">
<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"> <Update label="2025-05-08" description="v2.1.25">
**Improvements:** **Improvements:**
- **Client:** Improved error handling in client. - **Client:** Improved error handling in client.
@@ -664,6 +684,11 @@ mode: "wide"
<Tab title="Vercel AI SDK"> <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"> <Update label="2025-05-10" description="v1.0.4">
**New Features:** **New Features:**
- **Vercel AI SDK:** Added support for new param `output_format`. - **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 | | `azure_kwargs` | Azure LLM args for initialization | AzureOpenAI |
| `deepseek_base_url` | Base URL for DeepSeek API | DeepSeek | | `deepseek_base_url` | Base URL for DeepSeek API | DeepSeek |
| `xai_base_url` | Base URL for XAI API | XAI | | `xai_base_url` | Base URL for XAI API | XAI |
| `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 | | `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
</Tab> </Tab>
<Tab title="TypeScript"> <Tab title="TypeScript">
+2 -2
View File
@@ -20,7 +20,7 @@ config = {
"llm": { "llm": {
"provider": "anthropic", "provider": "anthropic",
"config": { "config": {
"model": "claude-3-7-sonnet-latest", "model": "claude-sonnet-4-20250514",
"temperature": 0.1, "temperature": 0.1,
"max_tokens": 2000, "max_tokens": 2000,
} }
@@ -45,7 +45,7 @@ const config = {
provider: 'anthropic', provider: 'anthropic',
config: { config: {
apiKey: process.env.ANTHROPIC_API_KEY || '', apiKey: process.env.ANTHROPIC_API_KEY || '',
model: 'claude-3-7-sonnet-latest', model: 'claude-sonnet-4-20250514',
temperature: 0.1, temperature: 0.1,
maxTokens: 2000, 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="Gemini" href="/components/llms/models/gemini" />
<Card title="DeepSeek" href="/components/llms/models/deepseek" /> <Card title="DeepSeek" href="/components/llms/models/deepseek" />
<Card title="xAI" href="/components/llms/models/xAI" /> <Card title="xAI" href="/components/llms/models/xAI" />
<Card title="XAI" href="/components/llms/models/sarvam" />
<Card title="LM Studio" href="/components/llms/models/lmstudio" /> <Card title="LM Studio" href="/components/llms/models/lmstudio" />
<Card title="Langchain" href="/components/llms/models/langchain" /> <Card title="Langchain" href="/components/llms/models/langchain" />
</CardGroup> </CardGroup>
+24 -21
View File
@@ -45,22 +45,22 @@
"group": "Features", "group": "Features",
"icon": "star", "icon": "star",
"pages": [ "pages": [
"features/platform-overview", "platform/features/platform-overview",
"features/advanced-retrieval", "platform/features/advanced-retrieval",
"features/criteria-retrieval", "platform/features/criteria-retrieval",
"features/contextual-add", "platform/features/contextual-add",
"features/multimodal-support", "platform/features/multimodal-support",
"features/timestamp", "platform/features/timestamp",
"features/selective-memory", "platform/features/selective-memory",
"features/custom-categories", "platform/features/custom-categories",
"features/custom-instructions", "platform/features/custom-instructions",
"features/direct-import", "platform/features/direct-import",
"features/async-client", "platform/features/async-client",
"features/memory-export", "platform/features/memory-export",
"features/webhooks", "platform/features/webhooks",
"features/graph-memory", "platform/features/graph-memory",
"features/feedback-mechanism", "platform/features/feedback-mechanism",
"features/expiration-date" "platform/features/expiration-date"
] ]
} }
] ]
@@ -77,10 +77,10 @@
"icon": "wrench", "icon": "wrench",
"pages": [ "pages": [
"open-source/features/async-memory", "open-source/features/async-memory",
"features/openai_compatibility", "open-source/features/openai_compatibility",
"features/custom-fact-extraction-prompt", "open-source/features/custom-fact-extraction-prompt",
"features/custom-update-memory-prompt", "open-source/features/custom-update-memory-prompt",
"open-source/multimodal-support", "open-source/features/multimodal-support",
"open-source/features/rest-api" "open-source/features/rest-api"
] ]
}, },
@@ -115,6 +115,7 @@
"components/llms/models/gemini", "components/llms/models/gemini",
"components/llms/models/deepseek", "components/llms/models/deepseek",
"components/llms/models/xAI", "components/llms/models/xAI",
"components/llms/models/sarvam",
"components/llms/models/lmstudio", "components/llms/models/lmstudio",
"components/llms/models/langchain" "components/llms/models/langchain"
] ]
@@ -248,7 +249,9 @@
"integrations/elevenlabs", "integrations/elevenlabs",
"integrations/pipecat", "integrations/pipecat",
"integrations/agno", "integrations/agno",
"integrations/keywords" "integrations/keywords",
"integrations/raycast",
"integrations/mastra"
] ]
} }
] ]
+56
View File
@@ -322,4 +322,60 @@ Here are the available integrations for Mem0:
> >
Build AI applications with persistent memory and comprehensive LLM observability. Build AI applications with persistent memory and comprehensive LLM observability.
</Card> </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> </CardGroup>
+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 ## 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. [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/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. [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/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. [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/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. [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/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. [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/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. [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/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. [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 ## OSS
@@ -72,9 +72,9 @@
### Features ### 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. [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/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 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/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. [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. [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. [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" />
+3 -4
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 is a powerful feature that allows users to create and utilize complex relationships between pieces of information.
## Graph Memory supports the following features: ## 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. 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. 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: Here's an example of how to specify a custom prompt:
<CodeGroup> <CodeGroup>
```python Python ```python Python
+57 -47
View File
@@ -2316,6 +2316,11 @@
"message": { "message": {
"type": "string", "type": "string",
"example": "Organization created successfully." "example": "Organization created successfully."
},
"org_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the organization"
} }
} }
} }
@@ -2722,13 +2727,13 @@
"schema": { "schema": {
"type": "object", "type": "object",
"required": [ "required": [
"username", "email",
"role" "role"
], ],
"properties": { "properties": {
"username": { "email": {
"type": "string", "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": { "role": {
"type": "string", "type": "string",
@@ -2798,27 +2803,27 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "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", "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", "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", "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": { "schema": {
"type": "object", "type": "object",
"required": [ "required": [
"username", "email",
"role" "role"
], ],
"properties": { "properties": {
"username": { "email": {
"type": "string", "type": "string",
"description": "Username of the member to be added" "description": "Email of the member to be added"
}, },
"role": { "role": {
"type": "string", "type": "string",
@@ -2923,23 +2928,23 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "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", "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", "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", "lang": "Java",
@@ -2971,12 +2976,12 @@
"schema": { "schema": {
"type": "object", "type": "object",
"required": [ "required": [
"username" "email"
], ],
"properties": { "properties": {
"username": { "email": {
"type": "string", "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": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "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", "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", "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", "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": { "message": {
"type": "string", "type": "string",
"example": "Project created successfully." "example": "Project created successfully."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the project"
} }
} }
} }
@@ -3753,13 +3763,13 @@
"schema": { "schema": {
"type": "object", "type": "object",
"required": [ "required": [
"username", "email",
"role" "role"
], ],
"properties": { "properties": {
"username": { "email": {
"type": "string", "type": "string",
"description": "Username of the member to be added" "description": "Email of the member to be added"
}, },
"role": { "role": {
"type": "string", "type": "string",
@@ -3823,27 +3833,27 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "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", "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", "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", "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": { "schema": {
"type": "object", "type": "object",
"required": [ "required": [
"username", "email",
"role" "role"
], ],
"properties": { "properties": {
"username": { "email": {
"type": "string", "type": "string",
"description": "Username of the member to be updated" "description": "Email of the member to be updated"
}, },
"role": { "role": {
"type": "string", "type": "string",
@@ -3951,27 +3961,27 @@
"x-code-samples": [ "x-code-samples": [
{ {
"lang": "Python", "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", "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", "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", "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", "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", "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", "in": "query",
"required": true, "required": true,
"description": "Username of the member to be removed", "description": "Email of the member to be removed",
"schema": { "schema": {
"type": "string" "type": "string"
} }
@@ -4809,7 +4819,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"messages": { "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", "type": "array",
"items": { "items": {
"type": "object", "type": "object",
+2 -3
View File
@@ -95,9 +95,8 @@ This is just the beginning. The MCP server is the first core layer in the OpenMe
## Getting Started Today ## Getting Started Today
- Github Repository: https://github.com/mem0ai/mem0 - Repository: [GitHub](https://github.com/mem0ai/mem0/tree/main/openmemory)
- Read the documentation: [Docs Link] - Join our community: [Discord](https://discord.gg/6PzXDgEjG5)
- Join our community: [Discord link]
With OpenMemory, your AI memories stay private, portable, and under your control, exactly where they belong. With OpenMemory, your AI memories stay private, portable, and under your control, exactly where they belong.
+1 -1
View File
@@ -66,4 +66,4 @@ Once set up, OpenMemory runs locally on your machine, ensuring all your AI memor
### Getting Started Today ### 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" /> <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 # Introduction
@@ -281,6 +281,67 @@ console.log(memories);
</CodeGroup> </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 ## Best Practices
- Enable Graph Memory for applications where understanding context and relationships between memories is important - 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 ### 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> <CodeGroup>
```python Python ```python Python
# Corrected date range (assuming you meant July 10 to July 20) # Retrieve using filters
filters = { filters = {
"AND": [ "AND": [
{"created_at": {"gte": "2024-07-10", "lte": "2024-07-20"}}, {"created_at": {"gte": "2024-07-10", "lte": "2024-07-20"}},
@@ -148,9 +150,27 @@ response = client.get_memory_export(filters=filters)
print(response) print(response)
``` ```
```bash cURL ```json Output
curl -X GET "https://api.mem0.ai/v1/memories/export/?user_id=alice" \ {
-H "Authorization: Token your-api-key" "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 ```json Output
@@ -59,7 +59,10 @@ five_days_ago = current_time - timedelta(days=5)
unix_timestamp = int(five_days_ago.timestamp()) unix_timestamp = int(five_days_ago.timestamp())
# Add memory with custom 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 ```javascript JavaScript
@@ -77,7 +80,10 @@ fiveDaysAgo.setDate(currentTime.getDate() - 5);
const unixTimestamp = Math.floor(fiveDaysAgo.getTime() / 1000); const unixTimestamp = Math.floor(fiveDaysAgo.getTime() / 1000);
// Add memory with custom timestamp // 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)) .then(response => console.log(response))
.catch(error => console.error(error)); .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 1, 2023 timestamp
january_2023_timestamp = 1672531200 # Unix timestamp for 2023-01-01 00:00:00 UTC 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 ```javascript JavaScript
// January 1, 2023 timestamp // January 1, 2023 timestamp
const january2023Timestamp = 1672531200; // Unix timestamp for 2023-01-01 00:00:00 UTC 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)) .then(response => console.log(response))
.catch(error => console.error(error)); .catch(error => console.error(error));
``` ```
+212 -4
View File
@@ -64,7 +64,10 @@ client = AsyncMemoryClient()
async def main(): 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) print(response)
await main() await main()
@@ -139,7 +142,20 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
</CodeGroup> </CodeGroup>
<Note> <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>
<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> <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>
@@ -678,6 +694,77 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
</CodeGroup> </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>
### 4.3 Get All Users ### 4.3 Get All Users
@@ -1295,6 +1382,121 @@ curl -X GET "https://api.mem0.ai/v1/memories/?version=v2&page=1&page_size=50" \
</CodeGroup> </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>
### 4.5 Memory History ### 4.5 Memory History
@@ -1547,11 +1749,17 @@ Fun fact: You can also delete the memory using the `add()` method by passing a n
<CodeGroup> <CodeGroup>
```python Python ```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 ```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)) .then(result => console.log(result))
.catch(error => console.error(error)); .catch(error => console.error(error));
``` ```
+3
View File
@@ -5,6 +5,9 @@ iconType: "solid"
--- ---
<Snippet file="paper-release.mdx" /> <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). 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) local_results = defaultdict(list)
for item in v: for item in v:
gt_answer = str(item['answer']) gt_answer = str(item["answer"])
pred_answer = str(item['response']) pred_answer = str(item["response"])
category = str(item['category']) category = str(item["category"])
question = str(item['question']) question = str(item["question"])
# Skip category 5 # Skip category 5
if category == '5': if category == "5":
continue continue
metrics = calculate_metrics(pred_answer, gt_answer) metrics = calculate_metrics(pred_answer, gt_answer)
bleu_scores = calculate_bleu_scores(pred_answer, gt_answer) bleu_scores = calculate_bleu_scores(pred_answer, gt_answer)
llm_score = evaluate_llm_judge(question, gt_answer, pred_answer) llm_score = evaluate_llm_judge(question, gt_answer, pred_answer)
local_results[k].append({ local_results[k].append(
"question": question, {
"answer": gt_answer, "question": question,
"response": pred_answer, "answer": gt_answer,
"category": category, "response": pred_answer,
"bleu_score": bleu_scores["bleu1"], "category": category,
"f1_score": metrics["f1"], "bleu_score": bleu_scores["bleu1"],
"llm_score": llm_score "f1_score": metrics["f1"],
}) "llm_score": llm_score,
}
)
return local_results return local_results
def main(): def main():
parser = argparse.ArgumentParser(description='Evaluate RAG results') parser = argparse.ArgumentParser(description="Evaluate RAG results")
parser.add_argument('--input_file', type=str, parser.add_argument(
default="results/rag_results_500_k1.json", "--input_file", type=str, default="results/rag_results_500_k1.json", help="Path to the input dataset file"
help='Path to the input dataset file') )
parser.add_argument('--output_file', type=str, parser.add_argument(
default="evaluation_metrics.json", "--output_file", type=str, default="evaluation_metrics.json", help="Path to save the evaluation results"
help='Path to save the evaluation results') )
parser.add_argument('--max_workers', type=int, default=10, parser.add_argument("--max_workers", type=int, default=10, help="Maximum number of worker threads")
help='Maximum number of worker threads')
args = parser.parse_args() args = parser.parse_args()
with open(args.input_file, 'r') as f: with open(args.input_file, "r") as f:
data = json.load(f) data = json.load(f)
results = defaultdict(list) results = defaultdict(list)
@@ -61,18 +62,16 @@ def main():
# Use ThreadPoolExecutor with specified workers # Use ThreadPoolExecutor with specified workers
with concurrent.futures.ThreadPoolExecutor(max_workers=args.max_workers) as executor: with concurrent.futures.ThreadPoolExecutor(max_workers=args.max_workers) as executor:
futures = [executor.submit(process_item, item_data) futures = [executor.submit(process_item, item_data) for item_data in data.items()]
for item_data in data.items()]
for future in tqdm(concurrent.futures.as_completed(futures), for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures)):
total=len(futures)):
local_results = future.result() local_results = future.result()
with results_lock: with results_lock:
for k, items in local_results.items(): for k, items in local_results.items():
results[k].extend(items) results[k].extend(items)
# Save results to JSON file # 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) json.dump(results, f, indent=4)
print(f"Results saved to {args.output_file}") print(f"Results saved to {args.output_file}")
+5 -13
View File
@@ -3,7 +3,7 @@ import json
import pandas as pd import pandas as pd
# Load the evaluation metrics data # 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) data = json.load(f)
# Flatten the data into a list of question items # Flatten the data into a list of question items
@@ -15,28 +15,20 @@ for key in data:
df = pd.DataFrame(all_items) df = pd.DataFrame(all_items)
# Convert category to numeric type # Convert category to numeric type
df['category'] = pd.to_numeric(df['category']) df["category"] = pd.to_numeric(df["category"])
# Calculate mean scores by category # Calculate mean scores by category
result = df.groupby('category').agg({ result = df.groupby("category").agg({"bleu_score": "mean", "f1_score": "mean", "llm_score": "mean"}).round(4)
'bleu_score': 'mean',
'f1_score': 'mean',
'llm_score': 'mean'
}).round(4)
# Add count of questions per category # Add count of questions per category
result['count'] = df.groupby('category').size() result["count"] = df.groupby("category").size()
# Print the results # Print the results
print("Mean Scores Per Category:") print("Mean Scores Per Category:")
print(result) print(result)
# Calculate overall means # Calculate overall means
overall_means = df.agg({ overall_means = df.agg({"bleu_score": "mean", "f1_score": "mean", "llm_score": "mean"}).round(4)
'bleu_score': 'mean',
'f1_score': 'mean',
'llm_score': 'mean'
}).round(4)
print("\nOverall Mean Scores:") 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". 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): def evaluate_llm_judge(question, gold_answer, generated_answer):
"""Evaluate the generated answer against the gold answer using an LLM judge.""" """Evaluate the generated answer against the gold answer using an LLM judge."""
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o-mini", model="gpt-4o-mini",
messages=[{ messages=[
"role": "user", {
"content": ACCURACY_PROMPT.format( "role": "user",
question=question, "content": ACCURACY_PROMPT.format(
gold_answer=gold_answer, question=question, gold_answer=gold_answer, generated_answer=generated_answer
generated_answer=generated_answer ),
) }
}], ],
response_format={"type": "json_object"}, 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 return 1 if label == "CORRECT" else 0
def main(): def main():
"""Main function to evaluate RAG results using LLM judge.""" """Main function to evaluate RAG results using LLM judge."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(description="Evaluate RAG results using LLM judge")
description='Evaluate RAG results using LLM judge'
)
parser.add_argument( parser.add_argument(
'--input_file', "--input_file",
type=str, type=str,
default="results/default_run_v4_k30_new_graph.json", 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() args = parser.parse_args()
@@ -78,10 +77,10 @@ def main():
index = 0 index = 0
for k, v in data.items(): for k, v in data.items():
for x in v: for x in v:
question = x['question'] question = x["question"]
gold_answer = x['answer'] gold_answer = x["answer"]
generated_answer = x['response'] generated_answer = x["response"]
category = x['category'] category = x["category"]
# Skip category 5 # Skip category 5
if int(category) == 5: if int(category) == 5:
@@ -92,13 +91,15 @@ def main():
LLM_JUDGE[category].append(label) LLM_JUDGE[category].append(label)
# Store the results # Store the results
RESULTS[index].append({ RESULTS[index].append(
"question": question, {
"gt_answer": gold_answer, "question": question,
"response": generated_answer, "gt_answer": gold_answer,
"category": category, "response": generated_answer,
"llm_label": label "category": category,
}) "llm_label": label,
}
)
# Save intermediate results # Save intermediate results
with open(output_path, "w") as f: with open(output_path, "w") as f:
@@ -108,8 +109,7 @@ def main():
print("All categories accuracy:") print("All categories accuracy:")
for cat, results in LLM_JUDGE.items(): for cat, results in LLM_JUDGE.items():
if results: # Only print if there are results for this category if results: # Only print if there are results for this category
print(f" Category {cat}: {np.mean(results):.4f} " print(f" Category {cat}: {np.mean(results):.4f} " f"({sum(results)}/{len(results)})")
f"({sum(results)}/{len(results)})")
print("------------------------------------------") print("------------------------------------------")
index += 1 index += 1
+37 -37
View File
@@ -26,34 +26,37 @@ from sentence_transformers.util import pytorch_cos_sim
# Download required NLTK data # Download required NLTK data
try: try:
nltk.download('punkt', quiet=True) nltk.download("punkt", quiet=True)
nltk.download('wordnet', quiet=True) nltk.download("wordnet", quiet=True)
except Exception as e: except Exception as e:
print(f"Error downloading NLTK data: {e}") print(f"Error downloading NLTK data: {e}")
# Initialize SentenceTransformer model (this will be reused) # Initialize SentenceTransformer model (this will be reused)
try: try:
sentence_model = SentenceTransformer('all-MiniLM-L6-v2') sentence_model = SentenceTransformer("all-MiniLM-L6-v2")
except Exception as e: except Exception as e:
print(f"Warning: Could not load SentenceTransformer model: {e}") print(f"Warning: Could not load SentenceTransformer model: {e}")
sentence_model = None sentence_model = None
def simple_tokenize(text): def simple_tokenize(text):
"""Simple tokenization function.""" """Simple tokenization function."""
# Convert to string if not already # Convert to string if not already
text = str(text) 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]: def calculate_rouge_scores(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate ROUGE scores for prediction against reference.""" """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) scores = scorer.score(reference, prediction)
return { return {
'rouge1_f': scores['rouge1'].fmeasure, "rouge1_f": scores["rouge1"].fmeasure,
'rouge2_f': scores['rouge2'].fmeasure, "rouge2_f": scores["rouge2"].fmeasure,
'rougeL_f': scores['rougeL'].fmeasure "rougeL_f": scores["rougeL"].fmeasure,
} }
def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]: def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate BLEU scores with different n-gram settings.""" """Calculate BLEU scores with different n-gram settings."""
pred_tokens = nltk.word_tokenize(prediction.lower()) pred_tokens = nltk.word_tokenize(prediction.lower())
@@ -69,26 +72,20 @@ def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]:
except Exception as e: except Exception as e:
print(f"Error calculating BLEU score: {e}") print(f"Error calculating BLEU score: {e}")
score = 0.0 score = 0.0
scores[f'bleu{n}'] = score scores[f"bleu{n}"] = score
return scores return scores
def calculate_bert_scores(prediction: str, reference: str) -> Dict[str, float]: def calculate_bert_scores(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate BERTScore for semantic similarity.""" """Calculate BERTScore for semantic similarity."""
try: try:
P, R, F1 = bert_score([prediction], [reference], lang='en', verbose=False) P, R, F1 = bert_score([prediction], [reference], lang="en", verbose=False)
return { return {"bert_precision": P.item(), "bert_recall": R.item(), "bert_f1": F1.item()}
'bert_precision': P.item(),
'bert_recall': R.item(),
'bert_f1': F1.item()
}
except Exception as e: except Exception as e:
print(f"Error calculating BERTScore: {e}") print(f"Error calculating BERTScore: {e}")
return { return {"bert_precision": 0.0, "bert_recall": 0.0, "bert_f1": 0.0}
'bert_precision': 0.0,
'bert_recall': 0.0,
'bert_f1': 0.0
}
def calculate_meteor_score(prediction: str, reference: str) -> float: def calculate_meteor_score(prediction: str, reference: str) -> float:
"""Calculate METEOR score for the prediction.""" """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}") print(f"Error calculating METEOR score: {e}")
return 0.0 return 0.0
def calculate_sentence_similarity(prediction: str, reference: str) -> float: def calculate_sentence_similarity(prediction: str, reference: str) -> float:
"""Calculate sentence embedding similarity using SentenceBERT.""" """Calculate sentence embedding similarity using SentenceBERT."""
if sentence_model is None: if sentence_model is None:
@@ -114,6 +112,7 @@ def calculate_sentence_similarity(prediction: str, reference: str) -> float:
print(f"Error calculating sentence similarity: {e}") print(f"Error calculating sentence similarity: {e}")
return 0.0 return 0.0
def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]: def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
"""Calculate comprehensive evaluation metrics for a prediction.""" """Calculate comprehensive evaluation metrics for a prediction."""
# Handle empty or None values # Handle empty or None values
@@ -130,7 +129,7 @@ def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
"bleu4": 0.0, "bleu4": 0.0,
"bert_f1": 0.0, "bert_f1": 0.0,
"meteor": 0.0, "meteor": 0.0,
"sbert_similarity": 0.0 "sbert_similarity": 0.0,
} }
# Convert to strings if they're not already # Convert to strings if they're not already
@@ -164,7 +163,10 @@ def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
return metrics 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.""" """Calculate aggregate statistics for all metrics, split by category."""
if not all_metrics: if not all_metrics:
return {} return {}
@@ -180,18 +182,16 @@ def aggregate_metrics(all_metrics: List[Dict[str, float]], all_categories: List[
category_aggregates[category][metric_name].append(value) category_aggregates[category][metric_name].append(value)
# Calculate statistics for overall metrics # Calculate statistics for overall metrics
results = { results = {"overall": {}}
"overall": {}
}
for metric_name, values in aggregates.items(): for metric_name, values in aggregates.items():
results["overall"][metric_name] = { results["overall"][metric_name] = {
'mean': statistics.mean(values), "mean": statistics.mean(values),
'std': statistics.stdev(values) if len(values) > 1 else 0.0, "std": statistics.stdev(values) if len(values) > 1 else 0.0,
'median': statistics.median(values), "median": statistics.median(values),
'min': min(values), "min": min(values),
'max': max(values), "max": max(values),
'count': len(values) "count": len(values),
} }
# Calculate statistics for each category # Calculate statistics for each category
@@ -200,12 +200,12 @@ def aggregate_metrics(all_metrics: List[Dict[str, float]], all_categories: List[
for metric_name, values in category_aggregates[category].items(): for metric_name, values in category_aggregates[category].items():
if values: # Only calculate if we have values for this category if values: # Only calculate if we have values for this category
results[f"category_{category}"][metric_name] = { results[f"category_{category}"][metric_name] = {
'mean': statistics.mean(values), "mean": statistics.mean(values),
'std': statistics.stdev(values) if len(values) > 1 else 0.0, "std": statistics.stdev(values) if len(values) > 1 else 0.0,
'median': statistics.median(values), "median": statistics.median(values),
'min': min(values), "min": min(values),
'max': max(values), "max": max(values),
'count': len(values) "count": len(values),
} }
return results return results
+16 -43
View File
@@ -21,23 +21,15 @@ class Experiment:
def main(): def main():
parser = argparse.ArgumentParser(description='Run memory experiments') parser = argparse.ArgumentParser(description="Run memory experiments")
parser.add_argument('--technique_type', choices=TECHNIQUES, default='mem0', parser.add_argument("--technique_type", choices=TECHNIQUES, default="mem0", help="Memory technique to use")
help='Memory technique to use') parser.add_argument("--method", choices=METHODS, default="add", help="Method to use")
parser.add_argument('--method', choices=METHODS, default='add', parser.add_argument("--chunk_size", type=int, default=1000, help="Chunk size for processing")
help='Method to use') parser.add_argument("--output_folder", type=str, default="results/", help="Output path for results")
parser.add_argument('--chunk_size', type=int, default=1000, parser.add_argument("--top_k", type=int, default=30, help="Number of top memories to retrieve")
help='Chunk size for processing') parser.add_argument("--filter_memories", action="store_true", default=False, help="Whether to filter memories")
parser.add_argument('--output_folder', type=str, default='results/', parser.add_argument("--is_graph", action="store_true", default=False, help="Whether to use graph-based search")
help='Output path for results') parser.add_argument("--num_chunks", type=int, default=1, help="Number of chunks to process")
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() args = parser.parse_args()
@@ -46,33 +38,18 @@ def main():
if args.technique_type == "mem0": if args.technique_type == "mem0":
if args.method == "add": if args.method == "add":
memory_manager = MemoryADD( memory_manager = MemoryADD(data_path="dataset/locomo10.json", is_graph=args.is_graph)
data_path='dataset/locomo10.json',
is_graph=args.is_graph
)
memory_manager.process_all_conversations() memory_manager.process_all_conversations()
elif args.method == "search": elif args.method == "search":
output_file_path = os.path.join( output_file_path = os.path.join(
args.output_folder, 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( memory_searcher = MemorySearch(output_file_path, args.top_k, args.filter_memories, args.is_graph)
output_file_path, memory_searcher.process_data_file("dataset/locomo10.json")
args.top_k,
args.filter_memories,
args.is_graph
)
memory_searcher.process_data_file('dataset/locomo10.json')
elif args.technique_type == "rag": elif args.technique_type == "rag":
output_file_path = os.path.join( output_file_path = os.path.join(args.output_folder, f"rag_results_{args.chunk_size}_k{args.num_chunks}.json")
args.output_folder, rag_manager = RAGManager(data_path="dataset/locomo10_rag.json", chunk_size=args.chunk_size, k=args.num_chunks)
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) rag_manager.process_all_conversations(output_file_path)
elif args.technique_type == "langmem": elif args.technique_type == "langmem":
output_file_path = os.path.join(args.output_folder, "langmem_results.json") output_file_path = os.path.join(args.output_folder, "langmem_results.json")
@@ -85,11 +62,7 @@ def main():
elif args.method == "search": elif args.method == "search":
output_file_path = os.path.join(args.output_folder, "zep_search_results.json") output_file_path = os.path.join(args.output_folder, "zep_search_results.json")
zep_manager = ZepSearch() zep_manager = ZepSearch()
zep_manager.process_data_file( zep_manager.process_data_file("dataset/locomo10.json", "1", output_file_path)
"dataset/locomo10.json",
"1",
output_file_path
)
elif args.technique_type == "openai": elif args.technique_type == "openai":
output_file_path = os.path.join(args.output_folder, "openai_results.json") output_file_path = os.path.join(args.output_folder, "openai_results.json")
openai_manager = OpenAIPredict() 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_user_id=speaker_1_user_id,
speaker_1_memories=speaker_1_memories, speaker_1_memories=speaker_1_memories,
speaker_2_user_id=speaker_2_user_id, speaker_2_user_id=speaker_2_user_id,
speaker_2_memories=speaker_2_memories speaker_2_memories=speaker_2_memories,
) )
t1 = time.time() t1 = time.time()
response = client.chat.completions.create( response = client.chat.completions.create(
model=os.getenv("MODEL"), model=os.getenv("MODEL"), messages=[{"role": "system", "content": prompt}], temperature=0.0
messages=[{"role": "system", "content": prompt}],
temperature=0.0
) )
t2 = time.time() t2 = time.time()
return response.choices[0].message.content, t2 - t1 return response.choices[0].message.content, t2 - t1
@@ -59,7 +57,9 @@ def prompt(state):
class LangMem: class LangMem:
def __init__(self,): def __init__(
self,
):
self.store = InMemoryStore( self.store = InMemoryStore(
index={ index={
"dims": 1536, "dims": 1536,
@@ -80,18 +80,12 @@ class LangMem:
) )
def add_memory(self, message, config): def add_memory(self, message, config):
return self.agent.invoke( return self.agent.invoke({"messages": [{"role": "user", "content": message}]}, config=config)
{"messages": [{"role": "user", "content": message}]},
config=config
)
def search_memory(self, query, config): def search_memory(self, query, config):
try: try:
t1 = time.time() t1 = time.time()
response = self.agent.invoke( response = self.agent.invoke({"messages": [{"role": "user", "content": query}]}, config=config)
{"messages": [{"role": "user", "content": query}]},
config=config
)
t2 = time.time() t2 = time.time()
return response["messages"][-1].content, t2 - t1 return response["messages"][-1].content, t2 - t1
except Exception as e: except Exception as e:
@@ -102,7 +96,7 @@ class LangMem:
class LangMemManager: class LangMemManager:
def __init__(self, dataset_path): def __init__(self, dataset_path):
self.dataset_path = 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) self.data = json.load(f)
def process_all_conversations(self, output_file_path): def process_all_conversations(self, output_file_path):
@@ -123,7 +117,7 @@ class LangMemManager:
# Identify speakers # Identify speakers
for conv in chat_history: for conv in chat_history:
speakers.add(conv['speaker']) speakers.add(conv["speaker"])
if len(speakers) != 2: if len(speakers) != 2:
raise ValueError(f"Expected 2 speakers, got {len(speakers)}") raise ValueError(f"Expected 2 speakers, got {len(speakers)}")
@@ -134,50 +128,52 @@ class LangMemManager:
# Add memories for each message # Add memories for each message
for conv in tqdm(chat_history, desc=f"Processing messages {key}", leave=False): for conv in tqdm(chat_history, desc=f"Processing messages {key}", leave=False):
message = f"{conv['timestamp']} | {conv['speaker']}: {conv['text']}" message = f"{conv['timestamp']} | {conv['speaker']}: {conv['text']}"
if conv['speaker'] == speaker1: if conv["speaker"] == speaker1:
agent1.add_memory(message, config) agent1.add_memory(message, config)
elif conv['speaker'] == speaker2: elif conv["speaker"] == speaker2:
agent2.add_memory(message, config) agent2.add_memory(message, config)
else: else:
raise ValueError(f"Expected speaker1 or speaker2, got {conv['speaker']}") raise ValueError(f"Expected speaker1 or speaker2, got {conv['speaker']}")
# Process questions # Process questions
for q in tqdm(questions, desc=f"Processing questions {key}", leave=False): for q in tqdm(questions, desc=f"Processing questions {key}", leave=False):
category = q['category'] category = q["category"]
if int(category) == 5: if int(category) == 5:
continue continue
answer = q['answer'] answer = q["answer"]
question = q['question'] question = q["question"]
response1, speaker1_memory_time = agent1.search_memory(question, config) response1, speaker1_memory_time = agent1.search_memory(question, config)
response2, speaker2_memory_time = agent2.search_memory(question, config) response2, speaker2_memory_time = agent2.search_memory(question, config)
generated_answer, response_time = get_answer( generated_answer, response_time = get_answer(question, speaker1, response1, speaker2, response2)
question, speaker1, response1, speaker2, response2
)
result[key].append({ result[key].append(
"question": question, {
"answer": answer, "question": question,
"response1": response1, "answer": answer,
"response2": response2, "response1": response1,
"category": category, "response2": response2,
"speaker1_memory_time": speaker1_memory_time, "category": category,
"speaker2_memory_time": speaker2_memory_time, "speaker1_memory_time": speaker1_memory_time,
"response_time": response_time, "speaker2_memory_time": speaker2_memory_time,
'response': generated_answer "response_time": response_time,
}) "response": generated_answer,
}
)
return result return result
# Use multiprocessing to process conversations in parallel # Use multiprocessing to process conversations in parallel
with mp.Pool(processes=10) as pool: with mp.Pool(processes=10) as pool:
results = list(tqdm( results = list(
pool.imap(process_conversation, list(self.data.items())), tqdm(
total=len(self.data), pool.imap(process_conversation, list(self.data.items())),
desc="Processing conversations" total=len(self.data),
)) desc="Processing conversations",
)
)
# Combine results from all workers # Combine results from all workers
for result in results: for result in results:
@@ -185,5 +181,5 @@ class LangMemManager:
OUTPUT[key].extend(items) OUTPUT[key].extend(items)
# Save final results # 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) json.dump(OUTPUT, f, indent=4)
+16 -18
View File
@@ -13,7 +13,7 @@ load_dotenv()
# Update custom instructions # Update custom instructions
custom_instructions =""" custom_instructions = """
Generate personal memories that follow these guidelines: Generate personal memories that follow these guidelines:
1. Each memory should be self-contained with complete context, including: 1. Each memory should be self-contained with complete context, including:
@@ -47,7 +47,7 @@ class MemoryADD:
self.mem0_client = MemoryClient( self.mem0_client = MemoryClient(
api_key=os.getenv("MEM0_API_KEY"), api_key=os.getenv("MEM0_API_KEY"),
org_id=os.getenv("MEM0_ORGANIZATION_ID"), 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) self.mem0_client.update_project(custom_instructions=custom_instructions)
@@ -59,15 +59,16 @@ class MemoryADD:
self.load_data() self.load_data()
def load_data(self): 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) self.data = json.load(f)
return self.data return self.data
def add_memory(self, user_id, message, metadata, retries=3): def add_memory(self, user_id, message, metadata, retries=3):
for attempt in range(retries): for attempt in range(retries):
try: try:
_ = self.mem0_client.add(message, user_id=user_id, version="v2", _ = self.mem0_client.add(
metadata=metadata, enable_graph=self.is_graph) message, user_id=user_id, version="v2", metadata=metadata, enable_graph=self.is_graph
)
return return
except Exception as e: except Exception as e:
if attempt < retries - 1: if attempt < retries - 1:
@@ -78,13 +79,13 @@ class MemoryADD:
def add_memories_for_speaker(self, speaker, messages, timestamp, desc): def add_memories_for_speaker(self, speaker, messages, timestamp, desc):
for i in tqdm(range(0, len(messages), self.batch_size), desc=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}) self.add_memory(speaker, batch_messages, metadata={"timestamp": timestamp})
def process_conversation(self, item, idx): def process_conversation(self, item, idx):
conversation = item['conversation'] conversation = item["conversation"]
speaker_a = conversation['speaker_a'] speaker_a = conversation["speaker_a"]
speaker_b = conversation['speaker_b'] speaker_b = conversation["speaker_b"]
speaker_a_user_id = f"{speaker_a}_{idx}" speaker_a_user_id = f"{speaker_a}_{idx}"
speaker_b_user_id = f"{speaker_b}_{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) self.mem0_client.delete_all(user_id=speaker_b_user_id)
for key in conversation.keys(): 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 continue
date_time_key = key + "_date_time" date_time_key = key + "_date_time"
@@ -104,10 +105,10 @@ class MemoryADD:
messages = [] messages = []
messages_reverse = [] messages_reverse = []
for chat in chats: 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.append({"role": "user", "content": f"{speaker_a}: {chat['text']}"})
messages_reverse.append({"role": "assistant", "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.append({"role": "assistant", "content": f"{speaker_b}: {chat['text']}"})
messages_reverse.append({"role": "user", "content": f"{speaker_b}: {chat['text']}"}) messages_reverse.append({"role": "user", "content": f"{speaker_b}: {chat['text']}"})
else: else:
@@ -116,11 +117,11 @@ class MemoryADD:
# add memories for the two users on different threads # add memories for the two users on different threads
thread_a = threading.Thread( thread_a = threading.Thread(
target=self.add_memories_for_speaker, 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( thread_b = threading.Thread(
target=self.add_memories_for_speaker, 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() thread_a.start()
@@ -134,10 +135,7 @@ class MemoryADD:
if not self.data: if not self.data:
raise ValueError("No data loaded. Please set data_path and call load_data() first.") raise ValueError("No data loaded. Please set data_path and call load_data() first.")
with ThreadPoolExecutor(max_workers=max_workers) as executor: with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [ futures = [executor.submit(self.process_conversation, item, idx) for idx, item in enumerate(self.data)]
executor.submit(self.process_conversation, item, idx)
for idx, item in enumerate(self.data)
]
for future in futures: for future in futures:
future.result() future.result()
+92 -68
View File
@@ -16,12 +16,11 @@ load_dotenv()
class MemorySearch: 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( self.mem0_client = MemoryClient(
api_key=os.getenv("MEM0_API_KEY"), api_key=os.getenv("MEM0_API_KEY"),
org_id=os.getenv("MEM0_ORGANIZATION_ID"), 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.top_k = top_k
self.openai_client = OpenAI() self.openai_client = OpenAI()
@@ -42,11 +41,18 @@ class MemorySearch:
try: try:
if self.is_graph: if self.is_graph:
print("Searching with graph") print("Searching with graph")
memories = self.mem0_client.search(query, user_id=user_id, top_k=self.top_k, memories = self.mem0_client.search(
filter_memories=self.filter_memories, enable_graph=True, output_format='v1.1') query,
user_id=user_id,
top_k=self.top_k,
filter_memories=self.filter_memories,
enable_graph=True,
output_format="v1.1",
)
else: else:
memories = self.mem0_client.search(query, user_id=user_id, top_k=self.top_k, memories = self.mem0_client.search(
filter_memories=self.filter_memories) query, user_id=user_id, top_k=self.top_k, filter_memories=self.filter_memories
)
break break
except Exception as e: except Exception as e:
print("Retrying...") print("Retrying...")
@@ -57,64 +63,86 @@ class MemorySearch:
end_time = time.time() end_time = time.time()
if not self.is_graph: if not self.is_graph:
semantic_memories = [{'memory': memory['memory'], semantic_memories = [
'timestamp': memory['metadata']['timestamp'], {
'score': round(memory['score'], 2)} "memory": memory["memory"],
for memory in memories] "timestamp": memory["metadata"]["timestamp"],
"score": round(memory["score"], 2),
}
for memory in memories
]
graph_memories = None graph_memories = None
else: else:
semantic_memories = [{'memory': memory['memory'], semantic_memories = [
'timestamp': memory['metadata']['timestamp'], {
'score': round(memory['score'], 2)} for memory in memories['results']] "memory": memory["memory"],
graph_memories = [{"source": relation['source'], "relationship": relation['relationship'], "target": relation['target']} for relation in memories['relations']] "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 return semantic_memories, graph_memories, end_time - start_time
def answer_question(self, speaker_1_user_id, speaker_2_user_id, question, answer, category): 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_1_memories, speaker_1_graph_memories, speaker_1_memory_time = self.search_memory(
speaker_2_memories, speaker_2_graph_memories, speaker_2_memory_time = self.search_memory(speaker_2_user_id, question) 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']}" search_1_memory = [f"{item['timestamp']}: {item['memory']}" for item in speaker_1_memories]
for item in speaker_1_memories] search_2_memory = [f"{item['timestamp']}: {item['memory']}" for item in speaker_2_memories]
search_2_memory = [f"{item['timestamp']}: {item['memory']}"
for item in speaker_2_memories]
template = Template(self.ANSWER_PROMPT) template = Template(self.ANSWER_PROMPT)
answer_prompt = template.render( answer_prompt = template.render(
speaker_1_user_id=speaker_1_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_2_user_id=speaker_2_user_id.split("_")[0],
speaker_1_memories=json.dumps(search_1_memory, indent=4), speaker_1_memories=json.dumps(search_1_memory, indent=4),
speaker_2_memories=json.dumps(search_2_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_1_graph_memories=json.dumps(speaker_1_graph_memories, indent=4),
speaker_2_graph_memories=json.dumps(speaker_2_graph_memories, indent=4), speaker_2_graph_memories=json.dumps(speaker_2_graph_memories, indent=4),
question=question question=question,
) )
t1 = time.time() t1 = time.time()
response = self.openai_client.chat.completions.create( response = self.openai_client.chat.completions.create(
model=os.getenv("MODEL"), model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
messages=[
{"role": "system", "content": answer_prompt}
],
temperature=0.0
) )
t2 = time.time() t2 = time.time()
response_time = t2 - t1 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): def process_question(self, val, speaker_a_user_id, speaker_b_user_id):
question = val.get('question', '') question = val.get("question", "")
answer = val.get('answer', '') answer = val.get("answer", "")
category = val.get('category', -1) category = val.get("category", -1)
evidence = val.get('evidence', []) evidence = val.get("evidence", [])
adversarial_answer = val.get('adversarial_answer', '') 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, response,
speaker_b_user_id, speaker_1_memories,
question, speaker_2_memories,
answer, speaker_1_memory_time,
category 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 = { result = {
"question": question, "question": question,
@@ -125,67 +153,63 @@ class MemorySearch:
"adversarial_answer": adversarial_answer, "adversarial_answer": adversarial_answer,
"speaker_1_memories": speaker_1_memories, "speaker_1_memories": speaker_1_memories,
"speaker_2_memories": speaker_2_memories, "speaker_2_memories": speaker_2_memories,
'num_speaker_1_memories': len(speaker_1_memories), "num_speaker_1_memories": len(speaker_1_memories),
'num_speaker_2_memories': len(speaker_2_memories), "num_speaker_2_memories": len(speaker_2_memories),
'speaker_1_memory_time': speaker_1_memory_time, "speaker_1_memory_time": speaker_1_memory_time,
'speaker_2_memory_time': speaker_2_memory_time, "speaker_2_memory_time": speaker_2_memory_time,
"speaker_1_graph_memories": speaker_1_graph_memories, "speaker_1_graph_memories": speaker_1_graph_memories,
"speaker_2_graph_memories": speaker_2_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 # 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) json.dump(self.results, f, indent=4)
return result return result
def process_data_file(self, file_path): 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) data = json.load(f)
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"): for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
qa = item['qa'] qa = item["qa"]
conversation = item['conversation'] conversation = item["conversation"]
speaker_a = conversation['speaker_a'] speaker_a = conversation["speaker_a"]
speaker_b = conversation['speaker_b'] speaker_b = conversation["speaker_b"]
speaker_a_user_id = f"{speaker_a}_{idx}" speaker_a_user_id = f"{speaker_a}_{idx}"
speaker_b_user_id = f"{speaker_b}_{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): for question_item in tqdm(
result = self.process_question( qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
question_item, ):
speaker_a_user_id, result = self.process_question(question_item, speaker_a_user_id, speaker_b_user_id)
speaker_b_user_id
)
self.results[idx].append(result) self.results[idx].append(result)
# Save results after each question is processed # 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) json.dump(self.results, f, indent=4)
# Final save at the end # 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) 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_questions_parallel(self, qa_list, speaker_a_user_id, speaker_b_user_id, max_workers=1):
def process_single_question(val): def process_single_question(val):
result = self.process_question(val, speaker_a_user_id, speaker_b_user_id) result = self.process_question(val, speaker_a_user_id, speaker_b_user_id)
# Save results after each question is processed # 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) json.dump(self.results, f, indent=4)
return result return result
with ThreadPoolExecutor(max_workers=max_workers) as executor: with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(tqdm( results = list(
executor.map(process_single_question, qa_list), tqdm(executor.map(process_single_question, qa_list), total=len(qa_list), desc="Answering Questions")
total=len(qa_list), )
desc="Answering Questions"
))
# Final save at the end # 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) json.dump(self.results, f, indent=4)
return results return results
+18 -31
View File
@@ -59,23 +59,19 @@ class OpenAIPredict:
self.results = defaultdict(list) self.results = defaultdict(list)
def search_memory(self, idx): 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() memories = file.read()
return memories, 0 return memories, 0
def process_question(self, val, idx): def process_question(self, val, idx):
question = val.get('question', '') question = val.get("question", "")
answer = val.get('answer', '') answer = val.get("answer", "")
category = val.get('category', -1) category = val.get("category", -1)
evidence = val.get('evidence', []) evidence = val.get("evidence", [])
adversarial_answer = val.get('adversarial_answer', '') adversarial_answer = val.get("adversarial_answer", "")
response, search_memory_time, response_time, context = self.answer_question( response, search_memory_time, response_time, context = self.answer_question(idx, question)
idx,
question
)
result = { result = {
"question": question, "question": question,
@@ -86,7 +82,7 @@ class OpenAIPredict:
"adversarial_answer": adversarial_answer, "adversarial_answer": adversarial_answer,
"search_memory_time": search_memory_time, "search_memory_time": search_memory_time,
"response_time": response_time, "response_time": response_time,
"context": context "context": context,
} }
return result return result
@@ -95,43 +91,35 @@ class OpenAIPredict:
memories, search_memory_time = self.search_memory(idx) memories, search_memory_time = self.search_memory(idx)
template = Template(ANSWER_PROMPT) template = Template(ANSWER_PROMPT)
answer_prompt = template.render( answer_prompt = template.render(memories=memories, question=question)
memories=memories,
question=question
)
t1 = time.time() t1 = time.time()
response = self.openai_client.chat.completions.create( response = self.openai_client.chat.completions.create(
model=os.getenv("MODEL"), model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
messages=[
{"role": "system", "content": answer_prompt}
],
temperature=0.0
) )
t2 = time.time() t2 = time.time()
response_time = t2 - t1 response_time = t2 - t1
return response.choices[0].message.content, search_memory_time, response_time, memories return response.choices[0].message.content, search_memory_time, response_time, memories
def process_data_file(self, file_path, output_file_path): 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) data = json.load(f)
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"): 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): for question_item in tqdm(
result = self.process_question( qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
question_item, ):
idx result = self.process_question(question_item, idx)
)
self.results[idx].append(result) self.results[idx].append(result)
# Save results after each question is processed # 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) json.dump(self.results, f, indent=4)
# Final save at the end # 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) json.dump(self.results, f, indent=4)
@@ -141,4 +129,3 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
openai_predict = OpenAIPredict() openai_predict = OpenAIPredict()
openai_predict.process_data_file("../../dataset/locomo10.json", args.output_file_path) 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): def generate_response(self, question, context):
template = Template(PROMPT) template = Template(PROMPT)
prompt = template.render( prompt = template.render(CONTEXT=context, QUESTION=question)
CONTEXT=context,
QUESTION=question
)
max_retries = 3 max_retries = 3
retries = 0 retries = 0
@@ -47,19 +44,21 @@ class RAGManager:
response = self.client.chat.completions.create( response = self.client.chat.completions.create(
model=self.model, model=self.model,
messages=[ messages=[
{"role": "system", {
"content": "You are a helpful assistant that can answer " "role": "system",
"questions based on the provided context." "content": "You are a helpful assistant that can answer "
"If the question involves timing, use the conversation date for reference." "questions based on the provided context."
"Provide the shortest possible answer." "If the question involves timing, use the conversation date for reference."
"Use words directly from the conversation when possible." "Provide the shortest possible answer."
"Avoid using subjects in your answer."}, "Use words directly from the conversation when possible."
{"role": "user", "content": prompt} "Avoid using subjects in your answer.",
},
{"role": "user", "content": prompt},
], ],
temperature=0 temperature=0,
) )
t2 = time.time() 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: except Exception as e:
retries += 1 retries += 1
if retries > max_retries: if retries > max_retries:
@@ -69,21 +68,16 @@ class RAGManager:
def clean_chat_history(self, chat_history): def clean_chat_history(self, chat_history):
cleaned_chat_history = "" cleaned_chat_history = ""
for c in chat_history: for c in chat_history:
cleaned_chat_history += (f"{c['timestamp']} | {c['speaker']}: " cleaned_chat_history += f"{c['timestamp']} | {c['speaker']}: " f"{c['text']}\n"
f"{c['text']}\n")
return cleaned_chat_history return cleaned_chat_history
def calculate_embedding(self, document): def calculate_embedding(self, document):
response = self.client.embeddings.create( response = self.client.embeddings.create(model=os.getenv("EMBEDDING_MODEL"), input=document)
model=os.getenv("EMBEDDING_MODEL"),
input=document
)
return response.data[0].embedding return response.data[0].embedding
def calculate_similarity(self, embedding1, embedding2): def calculate_similarity(self, embedding1, embedding2):
return np.dot(embedding1, embedding2) / ( return np.dot(embedding1, embedding2) / (np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
def search(self, query, chunks, embeddings, k=1): def search(self, query, chunks, embeddings, k=1):
""" """
@@ -101,10 +95,7 @@ class RAGManager:
""" """
t1 = time.time() t1 = time.time()
query_embedding = self.calculate_embedding(query) query_embedding = self.calculate_embedding(query)
similarities = [ similarities = [self.calculate_similarity(query_embedding, embedding) for embedding in embeddings]
self.calculate_similarity(query_embedding, embedding)
for embedding in embeddings
]
# Get indices of top-k most similar chunks # Get indices of top-k most similar chunks
if k == 1: if k == 1:
@@ -118,7 +109,7 @@ class RAGManager:
combined_chunks = "\n<->\n".join([chunks[i] for i in top_indices]) combined_chunks = "\n<->\n".join([chunks[i] for i in top_indices])
t2 = time.time() t2 = time.time()
return combined_chunks, t2-t1 return combined_chunks, t2 - t1
def create_chunks(self, chat_history, chunk_size=500): def create_chunks(self, chat_history, chunk_size=500):
""" """
@@ -139,7 +130,7 @@ class RAGManager:
# Split into chunks based on token count # Split into chunks based on token count
for i in range(0, len(tokens), chunk_size): 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) chunk = encoding.decode(chunk_tokens)
chunks.append(chunk) chunks.append(chunk)
@@ -159,13 +150,9 @@ class RAGManager:
chat_history = value["conversation"] chat_history = value["conversation"]
questions = value["question"] questions = value["question"]
chunks, embeddings = self.create_chunks( chunks, embeddings = self.create_chunks(chat_history, self.chunk_size)
chat_history, self.chunk_size
)
for item in tqdm( for item in tqdm(questions, desc="Answering questions", leave=False):
questions, desc="Answering questions", leave=False
):
question = item["question"] question = item["question"]
answer = item.get("answer", "") answer = item.get("answer", "")
category = item["category"] category = item["category"]
@@ -174,22 +161,20 @@ class RAGManager:
context = chunks[0] context = chunks[0]
search_time = 0 search_time = 0
else: else:
context, search_time = self.search( context, search_time = self.search(question, chunks, embeddings, k=self.k)
question, chunks, embeddings, k=self.k response, response_time = self.generate_response(question, context)
)
response, response_time = self.generate_response(
question, context
)
FINAL_RESULTS[key].append({ FINAL_RESULTS[key].append(
"question": question, {
"answer": answer, "question": question,
"category": category, "answer": answer,
"context": context, "category": category,
"response": response, "context": context,
"search_time": search_time, "response": response,
"response_time": response_time, "search_time": search_time,
}) "response_time": response_time,
}
)
with open(output_file_path, "w+") as f: with open(output_file_path, "w+") as f:
json.dump(FINAL_RESULTS, f, indent=4) json.dump(FINAL_RESULTS, f, indent=4)
+2 -11
View File
@@ -1,12 +1,3 @@
TECHNIQUES = [ TECHNIQUES = ["mem0", "rag", "langmem", "zep", "openai"]
"mem0",
"rag",
"langmem",
"zep",
"openai"
]
METHODS = [ METHODS = ["add", "search"]
"add",
"search"
]
+10 -8
View File
@@ -19,12 +19,12 @@ class ZepAdd:
self.load_data() self.load_data()
def load_data(self): 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) self.data = json.load(f)
return self.data return self.data
def process_conversation(self, run_id, item, idx): def process_conversation(self, run_id, item, idx):
conversation = item['conversation'] conversation = item["conversation"]
user_id = f"run_id_{run_id}_experiment_user_{idx}" user_id = f"run_id_{run_id}_experiment_user_{idx}"
session_id = f"run_id_{run_id}_experiment_session_{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) print("Starting to add memories... for user", user_id)
for key in tqdm(conversation.keys(), desc=f"Processing 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 continue
date_time_key = key + "_date_time" 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): for chat in tqdm(chats, desc=f"Adding chats for {key}", leave=False):
self.zep_client.memory.add( self.zep_client.memory.add(
session_id=session_id, session_id=session_id,
messages=[Message( messages=[
role=chat['speaker'], Message(
role_type="user", role=chat["speaker"],
content=f"{timestamp}: {chat['text']}", role_type="user",
)] content=f"{timestamp}: {chat['text']}",
)
],
) )
def process_all_conversations(self, run_id): def process_all_conversations(self, 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')}" 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: 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] facts = [f" - {edge.fact} ({self.format_edge_date_range(edge)})" for edge in edges]
entities = [f' - {node.name}: {node.summary}' for node in nodes] entities = [f" - {node.name}: {node.summary}" for node in nodes]
return TEMPLATE.format(facts='\n'.join(facts), entities='\n'.join(entities)) 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): def search_memory(self, run_id, idx, query, max_retries=3, retry_delay=1):
start_time = time.time() start_time = time.time()
@@ -52,8 +52,14 @@ class ZepSearch:
while retries < max_retries: while retries < max_retries:
try: try:
user_id = f"run_id_{run_id}_experiment_user_{idx}" 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 edges_results = (
node_results = (self.zep_client.graph.search(user_id=user_id, reranker='rrf', query=query, scope='nodes', limit=20)).nodes 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) context = self.compose_search_context(edges_results, node_results)
break break
except Exception as e: except Exception as e:
@@ -68,17 +74,13 @@ class ZepSearch:
return context, end_time - start_time return context, end_time - start_time
def process_question(self, run_id, val, idx): def process_question(self, run_id, val, idx):
question = val.get('question', '') question = val.get("question", "")
answer = val.get('answer', '') answer = val.get("answer", "")
category = val.get('category', -1) category = val.get("category", -1)
evidence = val.get('evidence', []) evidence = val.get("evidence", [])
adversarial_answer = val.get('adversarial_answer', '') adversarial_answer = val.get("adversarial_answer", "")
response, search_memory_time, response_time, context = self.answer_question( response, search_memory_time, response_time, context = self.answer_question(run_id, idx, question)
run_id,
idx,
question
)
result = { result = {
"question": question, "question": question,
@@ -89,7 +91,7 @@ class ZepSearch:
"adversarial_answer": adversarial_answer, "adversarial_answer": adversarial_answer,
"search_memory_time": search_memory_time, "search_memory_time": search_memory_time,
"response_time": response_time, "response_time": response_time,
"context": context "context": context,
} }
return result return result
@@ -98,44 +100,35 @@ class ZepSearch:
context, search_memory_time = self.search_memory(run_id, idx, question) context, search_memory_time = self.search_memory(run_id, idx, question)
template = Template(ANSWER_PROMPT_ZEP) template = Template(ANSWER_PROMPT_ZEP)
answer_prompt = template.render( answer_prompt = template.render(memories=context, question=question)
memories=context,
question=question
)
t1 = time.time() t1 = time.time()
response = self.openai_client.chat.completions.create( response = self.openai_client.chat.completions.create(
model=os.getenv("MODEL"), model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
messages=[
{"role": "system", "content": answer_prompt}
],
temperature=0.0
) )
t2 = time.time() t2 = time.time()
response_time = t2 - t1 response_time = t2 - t1
return response.choices[0].message.content, search_memory_time, response_time, context return response.choices[0].message.content, search_memory_time, response_time, context
def process_data_file(self, file_path, run_id, output_file_path): 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) data = json.load(f)
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"): 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): for question_item in tqdm(
result = self.process_question( qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
run_id, ):
question_item, result = self.process_question(run_id, question_item, idx)
idx
)
self.results[idx].append(result) self.results[idx].append(result)
# Save results after each question is processed # 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) json.dump(self.results, f, indent=4)
# Final save at the end # 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) json.dump(self.results, f, indent=4)
@@ -56,9 +56,7 @@
"\n", "\n",
"import os\n", "import os\n",
"\n", "\n",
"os.environ[\"OPENAI_API_KEY\"] = (\n", "os.environ[\"OPENAI_API_KEY\"] = \"\""
" \"\"\n",
")"
] ]
}, },
{ {
@@ -149,7 +147,7 @@
" \"role\": \"assistant\",\n", " \"role\": \"assistant\",\n",
" \"content\": \"Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future.\",\n", " \"content\": \"Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future.\",\n",
" },\n", " },\n",
"]\n" "]"
] ]
}, },
{ {
@@ -166,9 +164,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"# Store inferred memories (default behavior)\n", "# Store inferred memories (default behavior)\n",
"result = m.add(\n", "result = m.add(messages, user_id=\"alice\", metadata={\"category\": \"movie_recommendations\"})"
" messages, user_id=\"alice\", metadata={\"category\": \"movie_recommendations\"}\n",
")"
] ]
}, },
{ {
+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", name="Fitness Agent",
model=OpenAIChat(id="gpt-4o"), 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.", 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 # Store user preferences as memory
def store_user_preferences(conversation: list, user_id: str = USER_ID): def store_user_preferences(conversation: list, user_id: str = USER_ID):
"""Store user preferences from conversation history""" """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 # Memory-aware assistant function
def fitness_coach(user_input: str, user_id: str = USER_ID): 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) 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. 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) memory_client.add(f"User: {user_input}\nAssistant: {response.content}", user_id=user_id)
return response.content return response.content
# -------------------------------------------------- # --------------------------------------------------
# Store user preferences and memories # Store user preferences and memories
messages = [ messages = [
{ {
"role": "user", "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", "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", "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", "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", "role": "user",
"content": "After push days, I usually eat high-protein and moderate-carb meals to recover." "content": "I often feel sore after leg days, so I use turmeric milk and magnesium to help with recovery.",
},
{
"role": "assistant",
"content": "Noted — high-protein, moderate-carb meals after push workouts."
}, },
{"role": "assistant", "content": "I'll remember turmeric milk and magnesium as part of your leg day recovery."},
{ {
"role": "user", "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", "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", "role": "user",
"content": "On leg days, I make sure to have complex carbs like rice or oats." "content": "I have mild lactose intolerance, so I avoid dairy. I use almond milk or lactose-free whey.",
},
{
"role": "assistant",
"content": "Noted — complex carbs like rice and oats are part of your leg day meals."
}, },
{"role": "assistant", "content": "Understood — avoiding regular dairy, using almond milk and lactose-free whey."},
{ {
"role": "user", "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", "role": "assistant",
"content": "I'll remember turmeric milk and magnesium as part of your leg day recovery." "content": "Noted — due to knee discomfort, you substitute deep squats with curls and glute bridges.",
},
{
"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."
}, },
{"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) store_user_preferences(messages)
@@ -1,9 +1,11 @@
import asyncio import asyncio
import warnings import warnings
from google.adk.agents import Agent from google.adk.agents import Agent
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types from google.genai import types
from mem0 import MemoryClient from mem0 import MemoryClient
warnings.filterwarnings("ignore", category=DeprecationWarning) warnings.filterwarnings("ignore", category=DeprecationWarning)
@@ -19,14 +21,14 @@ def save_patient_info(information: str) -> dict:
print(f"Storing patient information: {information[:30]}...") print(f"Storing patient information: {information[:30]}...")
# Get user_id from session state or use default # Get user_id from session state or use default
user_id = getattr(save_patient_info, 'user_id', 'default_user') user_id = getattr(save_patient_info, "user_id", "default_user")
# Store in Mem0 # Store in Mem0
response = mem0_client.add( mem0_client.add(
[{"role": "user", "content": information}], [{"role": "user", "content": information}],
user_id=user_id, user_id=user_id,
run_id="healthcare_session", run_id="healthcare_session",
metadata={"type": "patient_information"} metadata={"type": "patient_information"},
) )
return {"status": "success", "message": "Information saved"} return {"status": "success", "message": "Information saved"}
@@ -37,7 +39,7 @@ def retrieve_patient_info(query: str) -> str:
print(f"Searching for patient information: {query}") print(f"Searching for patient information: {query}")
# Get user_id from session state or use default # Get user_id from session state or use default
user_id = getattr(retrieve_patient_info, 'user_id', 'default_user') user_id = getattr(retrieve_patient_info, "user_id", "default_user")
# Search Mem0 # Search Mem0
results = mem0_client.search( results = mem0_client.search(
@@ -45,7 +47,7 @@ def retrieve_patient_info(query: str) -> str:
user_id=user_id, user_id=user_id,
run_id="healthcare_session", run_id="healthcare_session",
limit=5, limit=5,
threshold=0.7 # Higher threshold for more relevant results threshold=0.7, # Higher threshold for more relevant results
) )
if not results: if not results:
@@ -65,7 +67,7 @@ def schedule_appointment(date: str, time: str, reason: str) -> dict:
"status": "success", "status": "success",
"appointment_id": appointment_id, "appointment_id": appointment_id,
"confirmation": f"Appointment scheduled for {date} at {time} for {reason}", "confirmation": f"Appointment scheduled for {date} at {time} for {reason}",
"message": "Please arrive 15 minutes early to complete paperwork." "message": "Please arrive 15 minutes early to complete paperwork.",
} }
@@ -89,7 +91,7 @@ IMPORTANT GUIDELINES:
- For serious symptoms, always recommend consulting a healthcare professional. - For serious symptoms, always recommend consulting a healthcare professional.
- Keep all patient information confidential. - Keep all patient information confidential.
""", """,
tools=[save_patient_info, retrieve_patient_info, schedule_appointment] tools=[save_patient_info, retrieve_patient_info, schedule_appointment],
) )
# Set Up Session and Runner # Set Up Session and Runner
@@ -101,18 +103,10 @@ USER_ID = "Alex"
SESSION_ID = "session_001" SESSION_ID = "session_001"
# Create a session # Create a session
session = session_service.create_session( session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
app_name=APP_NAME,
user_id=USER_ID,
session_id=SESSION_ID
)
# Create the runner # Create the runner
runner = Runner( runner = Runner(agent=healthcare_agent, app_name=APP_NAME, session_service=session_service)
agent=healthcare_agent,
app_name=APP_NAME,
session_service=session_service
)
# Interact with the Healthcare Assistant # Interact with the Healthcare Assistant
@@ -121,21 +115,14 @@ async def call_agent_async(query, runner, user_id, session_id):
print(f"\n>>> Patient: {query}") print(f"\n>>> Patient: {query}")
# Format the user's message # Format the user's message
content = types.Content( content = types.Content(role="user", parts=[types.Part(text=query)])
role='user',
parts=[types.Part(text=query)]
)
# Set user_id for tools to access # Set user_id for tools to access
save_patient_info.user_id = user_id save_patient_info.user_id = user_id
retrieve_patient_info.user_id = user_id retrieve_patient_info.user_id = user_id
# Run the agent # Run the agent
async for event in runner.run_async( async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=content):
user_id=user_id,
session_id=session_id,
new_message=content
):
if event.is_final_response(): if event.is_final_response():
if event.content and event.content.parts: if event.content and event.content.parts:
response = event.content.parts[0].text response = event.content.parts[0].text
@@ -152,7 +139,7 @@ async def run_conversation():
"Hi, I'm Alex. I've been having headaches for the past week, and I have a penicillin allergy.", "Hi, I'm Alex. I've been having headaches for the past week, and I have a penicillin allergy.",
runner=runner, runner=runner,
user_id=USER_ID, user_id=USER_ID,
session_id=SESSION_ID session_id=SESSION_ID,
) )
# Request for health information # Request for health information
@@ -160,7 +147,7 @@ async def run_conversation():
"Can you tell me more about what might be causing my headaches?", "Can you tell me more about what might be causing my headaches?",
runner=runner, runner=runner,
user_id=USER_ID, user_id=USER_ID,
session_id=SESSION_ID session_id=SESSION_ID,
) )
# Schedule an appointment # Schedule an appointment
@@ -168,15 +155,12 @@ async def run_conversation():
"I think I should see a doctor. Can you help me schedule an appointment for next Monday at 2pm?", "I think I should see a doctor. Can you help me schedule an appointment for next Monday at 2pm?",
runner=runner, runner=runner,
user_id=USER_ID, user_id=USER_ID,
session_id=SESSION_ID session_id=SESSION_ID,
) )
# Test memory - should remember patient name, symptoms, and allergy # Test memory - should remember patient name, symptoms, and allergy
await call_agent_async( await call_agent_async(
"What medications should I avoid for my headaches?", "What medications should I avoid for my headaches?", runner=runner, user_id=USER_ID, session_id=SESSION_ID
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID
) )
@@ -191,37 +175,28 @@ async def interactive_mode():
session_id = f"session_{hash(patient_id) % 1000:03d}" session_id = f"session_{hash(patient_id) % 1000:03d}"
# Create session for this user # Create session for this user
session = session_service.create_session( session_service.create_session(app_name=APP_NAME, user_id=patient_id, session_id=session_id)
app_name=APP_NAME,
user_id=patient_id,
session_id=session_id
)
print(f"\nStarting conversation with patient ID: {patient_id}") print(f"\nStarting conversation with patient ID: {patient_id}")
print("Type your message and press Enter.") print("Type your message and press Enter.")
while True: while True:
user_input = input("\n>>> Patient: ").strip() user_input = input("\n>>> Patient: ").strip()
if user_input.lower() in ['exit', 'quit', 'bye']: if user_input.lower() in ["exit", "quit", "bye"]:
print("Ending conversation. Thank you!") print("Ending conversation. Thank you!")
break break
await call_agent_async( await call_agent_async(user_input, runner=runner, user_id=patient_id, session_id=session_id)
user_input,
runner=runner,
user_id=patient_id,
session_id=session_id
)
# Main execution # Main execution
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse
parser = argparse.ArgumentParser(description='Healthcare Assistant with Memory') parser = argparse.ArgumentParser(description="Healthcare Assistant with Memory")
parser.add_argument('--demo', action='store_true', help='Run the demo conversation') 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("--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') parser.add_argument("--patient-id", type=str, default=USER_ID, help="Patient ID for the conversation")
args = parser.parse_args() args = parser.parse_args()
if args.demo: if args.demo:
@@ -231,5 +206,3 @@ if __name__ == "__main__":
else: else:
# Default to demo mode if no arguments provided # Default to demo mode if no arguments provided
asyncio.run(run_conversation()) asyncio.run(run_conversation())
+10 -20
View File
@@ -16,26 +16,21 @@ from mem0 import Memory
# Configure Mem0 with Grok 3 and Qdrant # Configure Mem0 with Grok 3 and Qdrant
config = { config = {
"vector_store": { "vector_store": {"provider": "qdrant", "config": {"embedding_model_dims": 384}},
"provider": "qdrant",
"config": {
"embedding_model_dims": 384
}
},
"llm": { "llm": {
"provider": "xai", "provider": "xai",
"config": { "config": {
"model": "grok-3-beta", "model": "grok-3-beta",
"temperature": 0.1, "temperature": 0.1,
"max_tokens": 2000, "max_tokens": 2000,
} },
}, },
"embedder": { "embedder": {
"provider": "huggingface", "provider": "huggingface",
"config": { "config": {
"model": "all-MiniLM-L6-v2" # open embedding model "model": "all-MiniLM-L6-v2" # open embedding model
} },
} },
} }
# Instantiate memory layer # 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}" prompt += f"\nPreviously, the user mentioned: {past_memories}"
# Generate movie recommendation using Grok 3 # Generate movie recommendation using Grok 3
response = grok_client.chat.completions.create( response = grok_client.chat.completions.create(model="grok-3-beta", messages=[{"role": "user", "content": prompt}])
model="grok-3-beta",
messages=[
{"role": "user", "content": prompt}
]
)
recommendation = response.choices[0].message.content recommendation = response.choices[0].message.content
# Store conversation in memory # Store conversation in memory
memory.add( memory.add(
[{"role": "user", "content": user_query}, [{"role": "user", "content": user_query}, {"role": "assistant", "content": recommendation}],
{"role": "assistant", "content": recommendation}],
user_id=user_id, user_id=user_id,
metadata={"category": "movie"} metadata={"category": "movie"},
) )
return recommendation return recommendation
@@ -81,10 +70,11 @@ if __name__ == "__main__":
user_id = "arshi" user_id = "arshi"
recommend_movie_with_memory(user_id, "I'm looking for a movie to watch tonight. Any suggestions?") 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. # 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. # 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?") 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. # 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?") 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! # 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", name="Personal Agent",
model=OpenAIChat(id="gpt-4o"), model=OpenAIChat(id="gpt-4o"),
description="You are a helpful personal agent that helps me with day to day activities." description="You are a helpful personal agent that helps me with day to day activities."
"You can process both text and images.", "You can process both text and images.",
markdown=True 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") base64_image = base64.b64encode(image_file.read()).decode("utf-8")
# First: the text message # First: the text message
text_msg = { text_msg = {"role": "user", "content": user_input}
"role": "user",
"content": user_input
}
# Second: the image message # Second: the image message
image_msg = { image_msg = {
"role": "user", "role": "user",
"content": { "content": {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}},
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
} }
# Send both as separate message objects # 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.") print("✅ Image uploaded and stored in memory.")
if user_input: 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! # 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. # 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", print(
image_path="travel_items.jpeg", # this will be added to Mem0 memory chat_user(
user_id=user_id)) "This is the picture of what I brought with me in the trip to Bahamas",
print(chat_user("hey can you quickly tell me if brought my sunglasses to my trip, not able to find", image_path="travel_items.jpeg", # this will be added to Mem0 memory
user_id=user_id)) 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.. # 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. # 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 OPENAI_API_KEY="your_openai_api_key"
export MEM0_API_KEY="your_mem0_api_key" export MEM0_API_KEY="your_mem0_api_key"
""" """
import asyncio import asyncio
from agents import Agent, Runner 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") - 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 - Help with spaced repetition by suggesting topics to revisit based on last review time
- Personalize answers using stored memories - 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 # Upload and store PDF to Mem0
def upload_pdf(pdf_url: str, user_id: str): def upload_pdf(pdf_url: str, user_id: str):
pdf_message = { pdf_message = {"role": "user", "content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}}}
"role": "user",
"content": {
"type": "pdf_url",
"pdf_url": {"url": pdf_url}
}
}
client.add([pdf_message], user_id=user_id) client.add([pdf_message], user_id=user_id)
print("✅ PDF uploaded and processed into memory.") print("✅ PDF uploaded and processed into memory.")
# Main interaction loop with your personal study buddy # Main interaction loop with your personal study buddy
async def study_buddy(user_id: str, topic: str, user_input: str): async def study_buddy(user_id: str, topic: str, user_input: str):
memories = client.search(f"{topic}", user_id=user_id) memories = client.search(f"{topic}", user_id=user_id)
memory_context = "n".join(f"- {m['memory']}" for m in memories) 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) result = await Runner.run(study_agent, prompt)
response = result.final_output response = result.final_output
client.add([ client.add(
{"role": "user", "content": f'''Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}'''} [{"role": "user", "content": f"""Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}"""}],
], user_id=user_id, metadata={"topic": topic}) user_id=user_id,
metadata={"topic": topic},
)
return response return response
@@ -78,7 +75,12 @@ async def main():
# Demonstrate spaced repetition prompting # Demonstrate spaced repetition prompting
topic = "Momentum Conservation" 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__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
+18 -29
View File
@@ -57,7 +57,7 @@ def initialize_memory():
}, },
{ {
"role": "user", "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", "role": "assistant",
@@ -65,7 +65,7 @@ def initialize_memory():
}, },
{ {
"role": "user", "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", "role": "assistant",
@@ -73,7 +73,7 @@ def initialize_memory():
}, },
{ {
"role": "user", "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", "role": "assistant",
@@ -81,7 +81,7 @@ def initialize_memory():
}, },
{ {
"role": "user", "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", "role": "assistant",
@@ -89,7 +89,7 @@ def initialize_memory():
}, },
{ {
"role": "user", "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", "role": "assistant",
@@ -135,11 +135,11 @@ def record_audio(filename="input.wav", record_seconds=5):
stream.close() stream.close()
p.terminate() p.terminate()
with wave.open(filename, 'wb') as wf: with wave.open(filename, "wb") as wf:
wf.setnchannels(channels) wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(fmt)) wf.setsampwidth(p.get_sample_size(fmt))
wf.setframerate(rate) wf.setframerate(rate)
wf.writeframes(b''.join(frames)) wf.writeframes(b"".join(frames))
# ------------------ STT USING WHISPER ------------------ # ------------------ STT USING WHISPER ------------------
@@ -147,10 +147,7 @@ def transcribe_whisper(audio_path):
print("🔎 Transcribing with Whisper...") print("🔎 Transcribing with Whisper...")
try: try:
with open(audio_path, "rb") as audio_file: with open(audio_path, "rb") as audio_file:
transcript = openai_client.audio.transcriptions.create( transcript = openai_client.audio.transcriptions.create(model="whisper-1", file=audio_file)
model="whisper-1",
file=audio_file
)
print(f"🗣️ You said: {transcript.text}") print(f"🗣️ You said: {transcript.text}")
return transcript.text return transcript.text
except Exception as e: except Exception as e:
@@ -165,9 +162,7 @@ def get_agent_response(user_input):
try: try:
task = Task( task = Task(
description=f"Respond to: {user_input}", description=f"Respond to: {user_input}", expected_output="A short and relevant reply.", agent=voice_agent
expected_output="A short and relevant reply.",
agent=voice_agent
) )
crew = Crew( crew = Crew(
agents=[voice_agent], agents=[voice_agent],
@@ -175,22 +170,19 @@ def get_agent_response(user_input):
process=Process.sequential, process=Process.sequential,
verbose=True, verbose=True,
memory=True, memory=True,
memory_config={ memory_config={"provider": "mem0", "config": {"user_id": USER_ID}},
"provider": "mem0",
"config": {"user_id": USER_ID}
}
) )
result = crew.kickoff() result = crew.kickoff()
# Extract the text response from the complex result object # Extract the text response from the complex result object
if hasattr(result, 'raw'): if hasattr(result, "raw"):
return result.raw return result.raw
elif isinstance(result, dict) and 'raw' in result: elif isinstance(result, dict) and "raw" in result:
return result['raw'] return result["raw"]
elif isinstance(result, dict) and 'tasks_output' in result: elif isinstance(result, dict) and "tasks_output" in result:
outputs = result['tasks_output'] outputs = result["tasks_output"]
if outputs and isinstance(outputs, list) and len(outputs) > 0: 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 # Fallback to string representation if we can't extract the raw response
return str(result) return str(result)
@@ -204,10 +196,7 @@ def get_agent_response(user_input):
def speak_response(text): def speak_response(text):
print(f"🤖 Agent: {text}") print(f"🤖 Agent: {text}")
audio = tts_client.text_to_speech.convert( audio = tts_client.text_to_speech.convert(
text=text, text=text, voice_id="JBFqnCBsd6RMkjVDRZzb", model_id="eleven_multilingual_v2", output_format="mp3_44100_128"
voice_id="JBFqnCBsd6RMkjVDRZzb",
model_id="eleven_multilingual_v2",
output_format="mp3_44100_128"
) )
play(audio) play(audio)
@@ -220,7 +209,7 @@ def run_voice_agent():
record_audio(tmp_audio.name) record_audio(tmp_audio.name)
try: try:
user_text = transcribe_whisper(tmp_audio.name) 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.") print("👋 Exiting.")
break break
response = get_agent_response(user_text) response = get_agent_response(user_text)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "mem0ai", "name": "mem0ai",
"version": "2.1.25", "version": "2.1.29",
"description": "The Memory Layer For Your AI Apps", "description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.mjs", "module": "./dist/index.mjs",
@@ -98,7 +98,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@anthropic-ai/sdk": "^0.40.1", "@anthropic-ai/sdk": "^0.40.1",
"@google/genai": "^0.7.0", "@google/genai": "^1.2.0",
"@mistralai/mistralai": "^1.5.2", "@mistralai/mistralai": "^1.5.2",
"@qdrant/js-client-rest": "1.13.0", "@qdrant/js-client-rest": "1.13.0",
"@supabase/supabase-js": "^2.49.1", "@supabase/supabase-js": "^2.49.1",
+672 -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; return jsonResponse;
} }
_preparePayload( _preparePayload(messages: Array<Message>, options: MemoryOptions): object {
messages: string | Array<Message>,
options: MemoryOptions,
): object {
const payload: any = {}; const payload: any = {};
if (typeof messages === "string") { payload.messages = messages;
payload.messages = [{ role: "user", content: messages }];
} else if (Array.isArray(messages)) {
payload.messages = messages;
}
return { ...payload, ...options }; return { ...payload, ...options };
} }
@@ -217,7 +210,7 @@ export default class MemoryClient {
} }
async add( async add(
messages: string | Array<Message>, messages: Array<Message>,
options: MemoryOptions = {}, options: MemoryOptions = {},
): Promise<Array<Memory>> { ): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
+2 -1
View File
@@ -23,6 +23,7 @@ export interface MemoryOptions {
custom_instructions?: string; custom_instructions?: string;
timestamp?: number; timestamp?: number;
output_format?: string | OutputFormat; output_format?: string | OutputFormat;
async_mode?: boolean;
} }
export interface ProjectOptions { export interface ProjectOptions {
@@ -53,7 +54,7 @@ export interface MultiModalMessages {
} }
export interface Messages { export interface Messages {
role: string; role: "user" | "assistant";
content: string | MultiModalMessages; content: string | MultiModalMessages;
} }
+1 -1
View File
@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types"; 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 // Safely check for process.env in different environments
let MEM0_TELEMETRY = true; let MEM0_TELEMETRY = true;
+1
View File
@@ -20,6 +20,7 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
llm: { llm: {
provider: "openai", provider: "openai",
config: { config: {
baseURL: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY || "", apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview", model: "gpt-4-turbo-preview",
modelProperties: undefined, modelProperties: undefined,
+1
View File
@@ -80,6 +80,7 @@ export class ConfigManager {
} }
return { return {
baseURL: userConf?.baseURL || defaultConf.baseURL,
apiKey: apiKey:
userConf?.apiKey !== undefined userConf?.apiKey !== undefined
? userConf.apiKey ? userConf.apiKey
+4 -1
View File
@@ -7,7 +7,10 @@ export class OpenAILLM implements LLM {
private model: string; private model: string;
constructor(config: LLMConfig) { 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"; this.model = config.model || "gpt-4o-mini";
} }
+1
View File
@@ -39,6 +39,7 @@ export interface HistoryStoreConfig {
export interface LLMConfig { export interface LLMConfig {
provider?: string; provider?: string;
baseURL?: string;
config?: Record<string, any>; config?: Record<string, any>;
apiKey?: string; apiKey?: string;
model?: string | any; model?: string | any;
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
TelemetryEventData, TelemetryEventData,
} from "./telemetry.types"; } from "./telemetry.types";
let version = "2.1.16"; let version = "2.1.26";
// Safely check for process.env in different environments // Safely check for process.env in different environments
let MEM0_TELEMETRY = true; let MEM0_TELEMETRY = true;
+74 -44
View File
@@ -3,7 +3,7 @@ import logging
import os import os
import warnings import warnings
from functools import wraps from functools import wraps
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional
import httpx import httpx
import requests import requests
@@ -95,10 +95,7 @@ class MemoryClient:
self.client = client self.client = client
# Ensure the client has the correct base_url and headers # Ensure the client has the correct base_url and headers
self.client.base_url = httpx.URL(self.host) self.client.base_url = httpx.URL(self.host)
self.client.headers.update({ self.client.headers.update({"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id})
"Authorization": f"Token {self.api_key}",
"Mem0-User-ID": self.user_id
})
else: else:
self.client = httpx.Client( self.client = httpx.Client(
base_url=self.host, base_url=self.host,
@@ -132,11 +129,11 @@ class MemoryClient:
raise ValueError(f"Error: {error_message}") raise ValueError(f"Error: {error_message}")
@api_error_handler @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. """Add a new memory.
Args: 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. **kwargs: Additional parameters such as user_id, agent_id, app_id, metadata, filters.
Returns: Returns:
@@ -237,7 +234,9 @@ class MemoryClient:
response.raise_for_status() response.raise_for_status()
if "metadata" in kwargs: if "metadata" in kwargs:
del kwargs["metadata"] 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() return response.json()
@api_error_handler @api_error_handler
@@ -357,10 +356,7 @@ class MemoryClient:
else: else:
entities = self.users() entities = self.users()
# Filter entities based on provided IDs using list comprehension # Filter entities based on provided IDs using list comprehension
to_delete = [ to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
{"type": entity["type"], "name": entity["name"]}
for entity in entities["results"]
]
params = self._prepare_params() params = self._prepare_params()
@@ -373,7 +369,9 @@ class MemoryClient:
response.raise_for_status() response.raise_for_status()
capture_client_event( 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 { return {
"message": "Entity deleted successfully." "message": "Entity deleted successfully."
@@ -454,7 +452,9 @@ class MemoryClient:
""" """
response = self.client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)}) response = self.client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
response.raise_for_status() 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() return response.json()
@api_error_handler @api_error_handler
@@ -504,12 +504,17 @@ class MemoryClient:
custom_instructions: Optional[str] = None, custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None, custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None, retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
enable_graph: Optional[bool] = None,
version: Optional[str] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Update the project settings. """Update the project settings.
Args: Args:
custom_instructions: New instructions for the project custom_instructions: New instructions for the project
custom_categories: New categories 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: Returns:
Dictionary containing the API response. Dictionary containing the API response.
@@ -521,13 +526,19 @@ class MemoryClient:
if not (self.org_id and self.project_id): if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories") 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( raise ValueError(
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them" "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( 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( response = self.client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/", f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
@@ -537,7 +548,14 @@ class MemoryClient:
capture_client_event( capture_client_event(
"client.update_project", "client.update_project",
self, 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() return response.json()
@@ -658,7 +676,7 @@ class MemoryClient:
return response.json() return response.json()
def _prepare_payload( def _prepare_payload(
self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any] self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Prepare the payload for API requests. """Prepare the payload for API requests.
@@ -670,10 +688,7 @@ class MemoryClient:
A dictionary containing the prepared payload. A dictionary containing the prepared payload.
""" """
payload = {} payload = {}
if isinstance(messages, str): payload["messages"] = messages
payload["messages"] = [{"role": "user", "content": messages}]
elif isinstance(messages, list):
payload["messages"] = messages
payload.update({k: v for k, v in kwargs.items() if v is not None}) payload.update({k: v for k, v in kwargs.items() if v is not None})
return payload return payload
@@ -750,10 +765,7 @@ class AsyncMemoryClient:
self.async_client = client self.async_client = client
# Ensure the client has the correct base_url and headers # Ensure the client has the correct base_url and headers
self.async_client.base_url = httpx.URL(self.host) self.async_client.base_url = httpx.URL(self.host)
self.async_client.headers.update({ self.async_client.headers.update({"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id})
"Authorization": f"Token {self.api_key}",
"Mem0-User-ID": self.user_id
})
else: else:
self.async_client = httpx.AsyncClient( self.async_client = httpx.AsyncClient(
base_url=self.host, base_url=self.host,
@@ -768,7 +780,11 @@ class AsyncMemoryClient:
"""Validate the API key by making a test request.""" """Validate the API key by making a test request."""
try: try:
params = self._prepare_params() 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() data = response.json()
response.raise_for_status() response.raise_for_status()
@@ -788,7 +804,7 @@ class AsyncMemoryClient:
raise ValueError(f"Error: {error_message}") raise ValueError(f"Error: {error_message}")
def _prepare_payload( def _prepare_payload(
self, messages: Union[str, List[Dict[str, str]], None], kwargs: Dict[str, Any] self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Prepare the payload for API requests. """Prepare the payload for API requests.
@@ -800,10 +816,7 @@ class AsyncMemoryClient:
A dictionary containing the prepared payload. A dictionary containing the prepared payload.
""" """
payload = {} payload = {}
if isinstance(messages, str): payload["messages"] = messages
payload["messages"] = [{"role": "user", "content": messages}]
elif isinstance(messages, list):
payload["messages"] = messages
payload.update({k: v for k, v in kwargs.items() if v is not None}) payload.update({k: v for k, v in kwargs.items() if v is not None})
return payload return payload
@@ -840,7 +853,7 @@ class AsyncMemoryClient:
await self.async_client.aclose() await self.async_client.aclose()
@api_error_handler @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) kwargs = self._prepare_params(kwargs)
if kwargs.get("output_format") != "v1.1": if kwargs.get("output_format") != "v1.1":
kwargs["output_format"] = "v1.1" kwargs["output_format"] = "v1.1"
@@ -973,10 +986,7 @@ class AsyncMemoryClient:
else: else:
entities = await self.users() entities = await self.users()
# Filter entities based on provided IDs using list comprehension # Filter entities based on provided IDs using list comprehension
to_delete = [ to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
{"type": entity["type"], "name": entity["name"]}
for entity in entities["results"]
]
params = self._prepare_params() params = self._prepare_params()
@@ -988,7 +998,11 @@ class AsyncMemoryClient:
response = await self.async_client.delete(f"/v2/entities/{entity['type']}/{entity['name']}/", params=params) response = await self.async_client.delete(f"/v2/entities/{entity['type']}/{entity['name']}/", params=params)
response.raise_for_status() 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 { return {
"message": "Entity deleted successfully." "message": "Entity deleted successfully."
if (user_id or agent_id or app_id or run_id) if (user_id or agent_id or app_id or run_id)
@@ -1091,19 +1105,29 @@ class AsyncMemoryClient:
@api_error_handler @api_error_handler
async def update_project( async def update_project(
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None, self,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None 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]: ) -> Dict[str, Any]:
if not (self.org_id and self.project_id): if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories") 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( raise ValueError(
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them" "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( 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( response = await self.async_client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/", f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
@@ -1113,7 +1137,14 @@ class AsyncMemoryClient:
capture_client_event( capture_client_event(
"client.update_project", "client.update_project",
self, 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() return response.json()
@@ -1174,4 +1205,3 @@ class AsyncMemoryClient:
response.raise_for_status() response.raise_for_status()
capture_client_event("client.feedback", self, data, {"sync_type": "async"}) capture_client_event("client.feedback", self, data, {"sync_type": "async"})
return response.json() return response.json()
+7
View File
@@ -39,6 +39,8 @@ class BaseLlmConfig(ABC):
deepseek_base_url: Optional[str] = None, deepseek_base_url: Optional[str] = None,
# XAI specific # XAI specific
xai_base_url: Optional[str] = None, xai_base_url: Optional[str] = None,
# Sarvam specific
sarvam_base_url: Optional[str] = "https://api.sarvam.ai/v1",
# LM Studio specific # LM Studio specific
lmstudio_base_url: Optional[str] = "http://localhost:1234/v1", lmstudio_base_url: Optional[str] = "http://localhost:1234/v1",
# AWS Bedrock specific # AWS Bedrock specific
@@ -89,6 +91,8 @@ class BaseLlmConfig(ABC):
:type deepseek_base_url: Optional[str], optional :type deepseek_base_url: Optional[str], optional
:param xai_base_url: XAI base URL to be use, defaults to None :param xai_base_url: XAI base URL to be use, defaults to None
:type xai_base_url: Optional[str], optional :type xai_base_url: Optional[str], optional
:param 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" :param lmstudio_base_url: LM Studio base URL to be use, defaults to "http://localhost:1234/v1"
:type lmstudio_base_url: Optional[str], optional :type lmstudio_base_url: Optional[str], optional
""" """
@@ -125,6 +129,9 @@ class BaseLlmConfig(ABC):
# XAI specific # XAI specific
self.xai_base_url = xai_base_url self.xai_base_url = xai_base_url
# Sarvam specific
self.sarvam_base_url = sarvam_base_url
# LM Studio specific # LM Studio specific
self.lmstudio_base_url = lmstudio_base_url self.lmstudio_base_url = lmstudio_base_url
+10 -27
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 from pydantic import BaseModel, Field, model_validator
@@ -7,33 +7,17 @@ class OpenSearchConfig(BaseModel):
collection_name: str = Field("mem0", description="Name of the index") collection_name: str = Field("mem0", description="Name of the index")
host: str = Field("localhost", description="OpenSearch host") host: str = Field("localhost", description="OpenSearch host")
port: int = Field(9200, description="OpenSearch port") port: int = Field(9200, description="OpenSearch port")
user: Optional[str] = Field( user: Optional[str] = Field(None, description="Username for authentication")
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)")
password: Optional[str] = Field( embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
None, description="Password for authentication" 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)")
api_key: Optional[str] = Field( http_auth: Optional[object] = Field(None, description="HTTP authentication method / AWS SigV4")
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( connection_class: Optional[Union[str, Type]] = Field(
"RequestsHttpConnection", description="Connection class for OpenSearch" "RequestsHttpConnection", description="Connection class for OpenSearch"
) )
pool_maxsize: int = Field( pool_maxsize: int = Field(20, description="Maximum number of connections in the pool")
20, description="Maximum number of connections in the pool"
)
@model_validator(mode="before") @model_validator(mode="before")
@classmethod @classmethod
@@ -52,7 +36,6 @@ class OpenSearchConfig(BaseModel):
extra_fields = input_fields - allowed_fields extra_fields = input_fields - allowed_fields
if extra_fields: if extra_fields:
raise ValueError( raise ValueError(
f"Extra fields not allowed: {', '.join(extra_fields)}. " f"Extra fields not allowed: {', '.join(extra_fields)}. " f"Allowed fields: {', '.join(allowed_fields)}"
f"Allowed fields: {', '.join(allowed_fields)}"
) )
return values return values
+1
View File
@@ -11,6 +11,7 @@ logging.getLogger("transformers").setLevel(logging.WARNING)
logging.getLogger("sentence_transformers").setLevel(logging.WARNING) logging.getLogger("sentence_transformers").setLevel(logging.WARNING)
logging.getLogger("huggingface_hub").setLevel(logging.WARNING) logging.getLogger("huggingface_hub").setLevel(logging.WARNING)
class HuggingFaceEmbedding(EmbeddingBase): class HuggingFaceEmbedding(EmbeddingBase):
def __init__(self, config: Optional[BaseEmbedderConfig] = None): def __init__(self, config: Optional[BaseEmbedderConfig] = None):
super().__init__(config) super().__init__(config)
+1
View File
@@ -23,6 +23,7 @@ class Neo4jConfig(BaseModel):
raise ValueError("Please provide 'url', 'username' and 'password'.") raise ValueError("Please provide 'url', 'username' and 'password'.")
return values return values
class MemgraphConfig(BaseModel): class MemgraphConfig(BaseModel):
url: Optional[str] = Field(None, description="Host address for the graph database") url: Optional[str] = Field(None, description="Host address for the graph database")
username: Optional[str] = Field(None, description="Username for the graph database") username: Optional[str] = Field(None, description="Username for the graph database")
+6 -10
View File
@@ -20,6 +20,7 @@ def extract_provider(model: str) -> str:
return provider return provider
raise ValueError(f"Unknown provider in model: {model}") raise ValueError(f"Unknown provider in model: {model}")
class AWSBedrockLLM(LLMBase): class AWSBedrockLLM(LLMBase):
def __init__(self, config: Optional[BaseLlmConfig] = None): def __init__(self, config: Optional[BaseLlmConfig] = None):
super().__init__(config) super().__init__(config)
@@ -145,7 +146,9 @@ class AWSBedrockLLM(LLMBase):
input_body = { input_body = {
"inputText": prompt, "inputText": prompt,
"textGenerationConfig": { "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, "topP": self.model_kwargs["top_p"] or 0.9,
"temperature": self.model_kwargs["temperature"] or 0.1, "temperature": self.model_kwargs["temperature"] or 0.1,
}, },
@@ -243,14 +246,8 @@ class AWSBedrockLLM(LLMBase):
body = json.dumps(input_body) body = json.dumps(input_body)
if provider == "anthropic" or provider == "deepseek": if provider == "anthropic" or provider == "deepseek":
input_body = { input_body = {
"messages": [ "messages": [{"role": "user", "content": [{"type": "text", "text": prompt}]}],
{
"role": "user",
"content": [{"type": "text", "text": prompt}]
}
],
"max_tokens": self.model_kwargs["max_tokens_to_sample"] or self.model_kwargs["max_tokens"] or 5000, "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, "temperature": self.model_kwargs["temperature"] or 0.1,
"top_p": self.model_kwargs["top_p"] or 0.9, "top_p": self.model_kwargs["top_p"] or 0.9,
@@ -259,7 +256,6 @@ class AWSBedrockLLM(LLMBase):
body = json.dumps(input_body) body = json.dumps(input_body)
response = self.client.invoke_model( response = self.client.invoke_model(
body=body, body=body,
modelId=self.config.model, modelId=self.config.model,
@@ -272,6 +268,6 @@ class AWSBedrockLLM(LLMBase):
modelId=self.config.model, modelId=self.config.model,
accept="application/json", accept="application/json",
contentType="application/json", contentType="application/json",
) )
return self._parse_response(response, tools) return self._parse_response(response, tools)
+1
View File
@@ -24,6 +24,7 @@ class LlmConfig(BaseModel):
"gemini", "gemini",
"deepseek", "deepseek",
"xai", "xai",
"sarvam",
"lmstudio", "lmstudio",
"langchain", "langchain",
): ):
+100
View File
@@ -0,0 +1,100 @@
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}")
+154 -149
View File
@@ -34,11 +34,11 @@ from mem0.utils.factory import EmbedderFactory, LlmFactory, VectorStoreFactory
def _build_filters_and_metadata( def _build_filters_and_metadata(
*, # Enforce keyword-only arguments *, # Enforce keyword-only arguments
user_id: Optional[str] = None, user_id: Optional[str] = None,
agent_id: Optional[str] = None, agent_id: Optional[str] = None,
run_id: Optional[str] = None, run_id: Optional[str] = None,
actor_id: Optional[str] = None, # For query-time filtering actor_id: Optional[str] = None, # For query-time filtering
input_metadata: Optional[Dict[str, Any]] = None, input_metadata: Optional[Dict[str, Any]] = None,
input_filters: Optional[Dict[str, Any]] = None, input_filters: Optional[Dict[str, Any]] = None,
) -> tuple[Dict[str, Any], Dict[str, Any]]: ) -> tuple[Dict[str, Any], Dict[str, Any]]:
@@ -278,20 +278,20 @@ class Memory(MemoryBase):
if not infer: if not infer:
returned_memories = [] returned_memories = []
for message_dict in messages: for message_dict in messages:
if not isinstance(message_dict, dict) or \ if (
message_dict.get("role") is None or \ not isinstance(message_dict, dict)
message_dict.get("content") is None: or message_dict.get("role") is None
or message_dict.get("content") is None
):
logger.warning(f"Skipping invalid message format: {message_dict}") logger.warning(f"Skipping invalid message format: {message_dict}")
continue continue
if message_dict["role"] == "system": if message_dict["role"] == "system":
continue continue
per_msg_meta = deepcopy(metadata) per_msg_meta = deepcopy(metadata)
per_msg_meta["role"] = message_dict["role"] per_msg_meta["role"] = message_dict["role"]
actor_name = message_dict.get("name") actor_name = message_dict.get("name")
if actor_name: if actor_name:
per_msg_meta["actor_id"] = actor_name per_msg_meta["actor_id"] = actor_name
@@ -334,6 +334,10 @@ class Memory(MemoryBase):
logging.error(f"Error in new_retrieved_facts: {e}") logging.error(f"Error in new_retrieved_facts: {e}")
new_retrieved_facts = [] new_retrieved_facts = []
if not new_retrieved_facts:
logger.debug("No new facts retrieved from input. Skipping memory update LLM call.")
return []
retrieved_old_memory = [] retrieved_old_memory = []
new_message_embeddings = {} new_message_embeddings = {}
for new_mem in new_retrieved_facts: for new_mem in new_retrieved_facts:
@@ -405,16 +409,23 @@ class Memory(MemoryBase):
existing_embeddings=new_message_embeddings, existing_embeddings=new_message_embeddings,
metadata=deepcopy(metadata), metadata=deepcopy(metadata),
) )
returned_memories.append({ returned_memories.append(
"id": temp_uuid_mapping[resp.get("id")], "memory": action_text, {
"event": event_type, "previous_memory": resp.get("old_memory"), "id": temp_uuid_mapping[resp.get("id")],
}) "memory": action_text,
"event": event_type,
"previous_memory": resp.get("old_memory"),
}
)
elif event_type == "DELETE": elif event_type == "DELETE":
self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")]) self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")])
returned_memories.append({ returned_memories.append(
"id": temp_uuid_mapping[resp.get("id")], "memory": action_text, {
"event": event_type, "id": temp_uuid_mapping[resp.get("id")],
}) "memory": action_text,
"event": event_type,
}
)
elif event_type == "NONE": elif event_type == "NONE":
logging.info("NOOP for Memory.") logging.info("NOOP for Memory.")
except Exception as e: except Exception as e:
@@ -463,10 +474,7 @@ class Memory(MemoryBase):
"role", "role",
] ]
core_and_promoted_keys = { core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
"data", "hash", "created_at", "updated_at", "id",
*promoted_payload_keys
}
result_item = MemoryItem( result_item = MemoryItem(
id=memory.id, id=memory.id,
@@ -480,9 +488,7 @@ class Memory(MemoryBase):
if key in memory.payload: if key in memory.payload:
result_item[key] = memory.payload[key] result_item[key] = memory.payload[key]
additional_metadata = { additional_metadata = {k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys}
k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys
}
if additional_metadata: if additional_metadata:
result_item["metadata"] = additional_metadata result_item["metadata"] = additional_metadata
@@ -517,19 +523,14 @@ class Memory(MemoryBase):
""" """
_, effective_filters = _build_filters_and_metadata( _, effective_filters = _build_filters_and_metadata(
user_id=user_id, user_id=user_id, agent_id=agent_id, run_id=run_id, input_filters=filters
agent_id=agent_id,
run_id=run_id,
input_filters=filters
) )
if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")): if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")):
raise ValueError("At least one of 'user_id', 'agent_id', or 'run_id' must be specified.") raise ValueError("At least one of 'user_id', 'agent_id', or 'run_id' must be specified.")
capture_event( capture_event(
"mem0.get_all", "mem0.get_all", self, {"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "sync"}
self,
{"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "sync"}
) )
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
@@ -562,17 +563,18 @@ class Memory(MemoryBase):
def _get_all_from_vector_store(self, filters, limit): def _get_all_from_vector_store(self, filters, limit):
memories_result = self.vector_store.list(filters=filters, limit=limit) memories_result = self.vector_store.list(filters=filters, limit=limit)
actual_memories = memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result actual_memories = (
memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result
)
promoted_payload_keys = [ promoted_payload_keys = [
"user_id", "agent_id", "run_id", "user_id",
"agent_id",
"run_id",
"actor_id", "actor_id",
"role", "role",
] ]
core_and_promoted_keys = { core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
"data", "hash", "created_at", "updated_at", "id",
*promoted_payload_keys
}
formatted_memories = [] formatted_memories = []
for mem in actual_memories: for mem in actual_memories:
@@ -588,9 +590,7 @@ class Memory(MemoryBase):
if key in mem.payload: if key in mem.payload:
memory_item_dict[key] = mem.payload[key] memory_item_dict[key] = mem.payload[key]
additional_metadata = { additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
}
if additional_metadata: if additional_metadata:
memory_item_dict["metadata"] = additional_metadata memory_item_dict["metadata"] = additional_metadata
@@ -624,10 +624,7 @@ class Memory(MemoryBase):
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", "score": 0.8, ...}]}` Example for v1.1+: `{"results": [{"id": "...", "memory": "...", "score": 0.8, ...}]}`
""" """
_, effective_filters = _build_filters_and_metadata( _, effective_filters = _build_filters_and_metadata(
user_id=user_id, user_id=user_id, agent_id=agent_id, run_id=run_id, input_filters=filters
agent_id=agent_id,
run_id=run_id,
input_filters=filters
) )
if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")): if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")):
@@ -679,10 +676,7 @@ class Memory(MemoryBase):
"role", "role",
] ]
core_and_promoted_keys = { core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
"data", "hash", "created_at", "updated_at", "id",
*promoted_payload_keys
}
original_memories = [] original_memories = []
for mem in memories: for mem in memories:
@@ -699,9 +693,7 @@ class Memory(MemoryBase):
if key in mem.payload: if key in mem.payload:
memory_item_dict[key] = mem.payload[key] memory_item_dict[key] = mem.payload[key]
additional_metadata = { additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
}
if additional_metadata: if additional_metadata:
memory_item_dict["metadata"] = additional_metadata memory_item_dict["metadata"] = additional_metadata
@@ -738,7 +730,7 @@ class Memory(MemoryBase):
self._delete_memory(memory_id) self._delete_memory(memory_id)
return {"message": "Memory deleted successfully!"} return {"message": "Memory deleted successfully!"}
def delete_all(self, user_id:Optional[str]=None, agent_id:Optional[str]=None, run_id:Optional[str]=None): def delete_all(self, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None):
""" """
Delete all memories. Delete all memories.
@@ -1037,10 +1029,7 @@ class AsyncMemory(MemoryBase):
dict: A dictionary containing the result of the memory addition operation. dict: A dictionary containing the result of the memory addition operation.
""" """
processed_metadata, effective_filters = _build_filters_and_metadata( processed_metadata, effective_filters = _build_filters_and_metadata(
user_id=user_id, user_id=user_id, agent_id=agent_id, run_id=run_id, input_metadata=metadata
agent_id=agent_id,
run_id=run_id,
input_metadata=metadata
) )
if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value: if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
@@ -1058,7 +1047,9 @@ class AsyncMemory(MemoryBase):
raise ValueError("messages must be str, dict, or list[dict]") raise ValueError("messages must be str, dict, or list[dict]")
if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value: if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
results = await self._create_procedural_memory(messages, metadata=processed_metadata, prompt=prompt, llm=llm) results = await self._create_procedural_memory(
messages, metadata=processed_metadata, prompt=prompt, llm=llm
)
return results return results
if self.config.llm.config.get("enable_vision"): if self.config.llm.config.get("enable_vision"):
@@ -1066,7 +1057,9 @@ class AsyncMemory(MemoryBase):
else: else:
messages = parse_vision_messages(messages) messages = parse_vision_messages(messages)
vector_store_task = asyncio.create_task(self._add_to_vector_store(messages, processed_metadata, effective_filters, infer)) vector_store_task = asyncio.create_task(
self._add_to_vector_store(messages, processed_metadata, effective_filters, infer)
)
graph_task = asyncio.create_task(self._add_to_graph(messages, effective_filters)) graph_task = asyncio.create_task(self._add_to_graph(messages, effective_filters))
vector_store_result, graph_result = await asyncio.gather(vector_store_task, graph_task) vector_store_result, graph_result = await asyncio.gather(vector_store_task, graph_task)
@@ -1099,9 +1092,11 @@ class AsyncMemory(MemoryBase):
if not infer: if not infer:
returned_memories = [] returned_memories = []
for message_dict in messages: for message_dict in messages:
if not isinstance(message_dict, dict) or \ if (
message_dict.get("role") is None or \ not isinstance(message_dict, dict)
message_dict.get("content") is None: or message_dict.get("role") is None
or message_dict.get("content") is None
):
logger.warning(f"Skipping invalid message format (async): {message_dict}") logger.warning(f"Skipping invalid message format (async): {message_dict}")
continue continue
@@ -1119,11 +1114,15 @@ class AsyncMemory(MemoryBase):
msg_embeddings = await asyncio.to_thread(self.embedding_model.embed, msg_content, "add") msg_embeddings = await asyncio.to_thread(self.embedding_model.embed, msg_content, "add")
mem_id = await self._create_memory(msg_content, msg_embeddings, per_msg_meta) mem_id = await self._create_memory(msg_content, msg_embeddings, per_msg_meta)
returned_memories.append({ returned_memories.append(
"id": mem_id, "memory": msg_content, "event": "ADD", {
"actor_id": actor_name if actor_name else None, "id": mem_id,
"role": message_dict["role"], "memory": msg_content,
}) "event": "ADD",
"actor_id": actor_name if actor_name else None,
"role": message_dict["role"],
}
)
return returned_memories return returned_memories
parsed_messages = parse_messages(messages) parsed_messages = parse_messages(messages)
@@ -1142,7 +1141,13 @@ class AsyncMemory(MemoryBase):
response = remove_code_blocks(response) response = remove_code_blocks(response)
new_retrieved_facts = json.loads(response)["facts"] new_retrieved_facts = json.loads(response)["facts"]
except Exception as e: except Exception as e:
logging.error(f"Error in new_retrieved_facts: {e}"); new_retrieved_facts = [] new_retrieved_facts = []
if not new_retrieved_facts:
logger.info("No new facts retrieved from input. Skipping memory update LLM call.")
return []
logging.error(f"Error in new_retrieved_facts: {e}")
new_retrieved_facts = []
retrieved_old_memory = [] retrieved_old_memory = []
new_message_embeddings = {} new_message_embeddings = {}
@@ -1151,8 +1156,11 @@ class AsyncMemory(MemoryBase):
embeddings = await asyncio.to_thread(self.embedding_model.embed, new_mem_content, "add") embeddings = await asyncio.to_thread(self.embedding_model.embed, new_mem_content, "add")
new_message_embeddings[new_mem_content] = embeddings new_message_embeddings[new_mem_content] = embeddings
existing_mems = await asyncio.to_thread( existing_mems = await asyncio.to_thread(
self.vector_store.search, query=new_mem_content, vectors=embeddings, self.vector_store.search,
limit=5, filters=filters, # 'filters' is query_filters_for_inference query=new_mem_content,
vectors=embeddings,
limit=5,
filters=filters, # 'filters' is query_filters_for_inference
) )
return [{"id": mem.id, "text": mem.payload["data"]} for mem in existing_mems] return [{"id": mem.id, "text": mem.payload["data"]} for mem in existing_mems]
@@ -1162,7 +1170,8 @@ class AsyncMemory(MemoryBase):
retrieved_old_memory.extend(result_group) retrieved_old_memory.extend(result_group)
unique_data = {} unique_data = {}
for item in retrieved_old_memory: unique_data[item["id"]] = item for item in retrieved_old_memory:
unique_data[item["id"]] = item
retrieved_old_memory = list(unique_data.values()) retrieved_old_memory = list(unique_data.values())
logging.info(f"Total existing memories: {len(retrieved_old_memory)}") logging.info(f"Total existing memories: {len(retrieved_old_memory)}")
temp_uuid_mapping = {} temp_uuid_mapping = {}
@@ -1180,13 +1189,24 @@ class AsyncMemory(MemoryBase):
response_format={"type": "json_object"}, response_format={"type": "json_object"},
) )
except Exception as e: except Exception as e:
logging.error(f"Error in new memory actions response: {e}"); response = ""
response = ""
logging.error(f"Error in new memory actions response: {e}")
response = ""
try: try:
response = remove_code_blocks(response) response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response) new_memories_with_actions = json.loads(response)
except Exception as e: except Exception as e:
logging.error(f"Invalid JSON response: {e}"); new_memories_with_actions = {}
new_memories_with_actions = {}
if not new_memories_with_actions:
logger.info("No new facts retrieved from input (async). Skipping memory update LLM call.")
return []
logging.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
returned_memories = [] returned_memories = []
try: try:
@@ -1195,20 +1215,28 @@ class AsyncMemory(MemoryBase):
logging.info(resp) logging.info(resp)
try: try:
action_text = resp.get("text") action_text = resp.get("text")
if not action_text: continue if not action_text:
continue
event_type = resp.get("event") event_type = resp.get("event")
if event_type == "ADD": if event_type == "ADD":
task = asyncio.create_task(self._create_memory( task = asyncio.create_task(
data=action_text, existing_embeddings=new_message_embeddings, self._create_memory(
metadata=deepcopy(metadata) data=action_text,
)) existing_embeddings=new_message_embeddings,
metadata=deepcopy(metadata),
)
)
memory_tasks.append((task, resp, "ADD", None)) memory_tasks.append((task, resp, "ADD", None))
elif event_type == "UPDATE": elif event_type == "UPDATE":
task = asyncio.create_task(self._update_memory( task = asyncio.create_task(
memory_id=temp_uuid_mapping[resp["id"]], data=action_text, self._update_memory(
existing_embeddings=new_message_embeddings, metadata=deepcopy(metadata) memory_id=temp_uuid_mapping[resp["id"]],
)) data=action_text,
existing_embeddings=new_message_embeddings,
metadata=deepcopy(metadata),
)
)
memory_tasks.append((task, resp, "UPDATE", temp_uuid_mapping[resp["id"]])) memory_tasks.append((task, resp, "UPDATE", temp_uuid_mapping[resp["id"]]))
elif event_type == "DELETE": elif event_type == "DELETE":
task = asyncio.create_task(self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")])) task = asyncio.create_task(self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")]))
@@ -1222,26 +1250,25 @@ class AsyncMemory(MemoryBase):
try: try:
result_id = await task result_id = await task
if event_type == "ADD": if event_type == "ADD":
returned_memories.append({ returned_memories.append({"id": result_id, "memory": resp.get("text"), "event": event_type})
"id": result_id, "memory": resp.get("text"), "event": event_type
})
elif event_type == "UPDATE": elif event_type == "UPDATE":
returned_memories.append({ returned_memories.append(
"id": mem_id, "memory": resp.get("text"), {
"event": event_type, "previous_memory": resp.get("old_memory") "id": mem_id,
}) "memory": resp.get("text"),
"event": event_type,
"previous_memory": resp.get("old_memory"),
}
)
elif event_type == "DELETE": elif event_type == "DELETE":
returned_memories.append({ returned_memories.append({"id": mem_id, "memory": resp.get("text"), "event": event_type})
"id": mem_id, "memory": resp.get("text"), "event": event_type
})
except Exception as e: except Exception as e:
logging.error(f"Error awaiting memory task (async): {e}") logging.error(f"Error awaiting memory task (async): {e}")
except Exception as e: except Exception as e:
logging.error(f"Error in memory processing loop (async): {e}") logging.error(f"Error in memory processing loop (async): {e}")
capture_event( capture_event(
"mem0.add", self, "mem0.add", self, {"version": self.api_version, "keys": list(filters.keys()), "sync_type": "async"}
{"version": self.api_version, "keys": list(filters.keys()), "sync_type": "async"}
) )
return returned_memories return returned_memories
@@ -1279,10 +1306,7 @@ class AsyncMemory(MemoryBase):
"role", "role",
] ]
core_and_promoted_keys = { core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
"data", "hash", "created_at", "updated_at", "id",
*promoted_payload_keys
}
result_item = MemoryItem( result_item = MemoryItem(
id=memory.id, id=memory.id,
@@ -1296,9 +1320,7 @@ class AsyncMemory(MemoryBase):
if key in memory.payload: if key in memory.payload:
result_item[key] = memory.payload[key] result_item[key] = memory.payload[key]
additional_metadata = { additional_metadata = {k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys}
k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys
}
if additional_metadata: if additional_metadata:
result_item["metadata"] = additional_metadata result_item["metadata"] = additional_metadata
@@ -1314,41 +1336,36 @@ class AsyncMemory(MemoryBase):
limit: int = 100, limit: int = 100,
): ):
""" """
List all memories. List all memories.
Args: Args:
user_id (str, optional): user id user_id (str, optional): user id
agent_id (str, optional): agent id agent_id (str, optional): agent id
run_id (str, optional): run id run_id (str, optional): run id
filters (dict, optional): Additional custom key-value filters to apply to the search. filters (dict, optional): Additional custom key-value filters to apply to the search.
These are merged with the ID-based scoping filters. For example, These are merged with the ID-based scoping filters. For example,
`filters={"actor_id": "some_user"}`. `filters={"actor_id": "some_user"}`.
limit (int, optional): The maximum number of memories to return. Defaults to 100. limit (int, optional): The maximum number of memories to return. Defaults to 100.
Returns: Returns:
dict: A dictionary containing a list of memories under the "results" key, dict: A dictionary containing a list of memories under the "results" key,
and potentially "relations" if graph store is enabled. For API v1.0, and potentially "relations" if graph store is enabled. For API v1.0,
it might return a direct list (see deprecation warning). it might return a direct list (see deprecation warning).
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", ...}]}` Example for v1.1+: `{"results": [{"id": "...", "memory": "...", ...}]}`
""" """
_, effective_filters = _build_filters_and_metadata( _, effective_filters = _build_filters_and_metadata(
user_id=user_id, user_id=user_id, agent_id=agent_id, run_id=run_id, input_filters=filters
agent_id=agent_id,
run_id=run_id,
input_filters=filters
) )
if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")): if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")):
raise ValueError( raise ValueError(
"When 'conversation_id' is not provided (classic mode), " "When 'conversation_id' is not provided (classic mode), "
"at least one of 'user_id', 'agent_id', or 'run_id' must be specified for get_all." "at least one of 'user_id', 'agent_id', or 'run_id' must be specified for get_all."
) )
capture_event( capture_event(
"mem0.get_all", "mem0.get_all", self, {"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "async"}
self,
{"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "async"}
) )
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
@@ -1381,17 +1398,18 @@ class AsyncMemory(MemoryBase):
async def _get_all_from_vector_store(self, filters, limit): async def _get_all_from_vector_store(self, filters, limit):
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit) memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit)
actual_memories = memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result actual_memories = (
memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result
)
promoted_payload_keys = [ promoted_payload_keys = [
"user_id", "agent_id", "run_id", "user_id",
"agent_id",
"run_id",
"actor_id", "actor_id",
"role", "role",
] ]
core_and_promoted_keys = { core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
"data", "hash", "created_at", "updated_at", "id",
*promoted_payload_keys
}
formatted_memories = [] formatted_memories = []
for mem in actual_memories: for mem in actual_memories:
@@ -1407,9 +1425,7 @@ class AsyncMemory(MemoryBase):
if key in mem.payload: if key in mem.payload:
memory_item_dict[key] = mem.payload[key] memory_item_dict[key] = mem.payload[key]
additional_metadata = { additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
}
if additional_metadata: if additional_metadata:
memory_item_dict["metadata"] = additional_metadata memory_item_dict["metadata"] = additional_metadata
@@ -1444,14 +1460,11 @@ class AsyncMemory(MemoryBase):
""" """
_, effective_filters = _build_filters_and_metadata( _, effective_filters = _build_filters_and_metadata(
user_id=user_id, user_id=user_id, agent_id=agent_id, run_id=run_id, input_filters=filters
agent_id=agent_id,
run_id=run_id,
input_filters=filters
) )
if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")): if not any(key in effective_filters for key in ("user_id", "agent_id", "run_id")):
raise ValueError("at least one of 'user_id', 'agent_id', or 'run_id' must be specified ") raise ValueError("at least one of 'user_id', 'agent_id', or 'run_id' must be specified ")
capture_event( capture_event(
"mem0.search", "mem0.search",
@@ -1466,9 +1479,7 @@ class AsyncMemory(MemoryBase):
if hasattr(self.graph.search, "__await__"): # Check if graph search is async if hasattr(self.graph.search, "__await__"): # Check if graph search is async
graph_task = asyncio.create_task(self.graph.search(query, effective_filters, limit)) graph_task = asyncio.create_task(self.graph.search(query, effective_filters, limit))
else: else:
graph_task = asyncio.create_task( graph_task = asyncio.create_task(asyncio.to_thread(self.graph.search, query, effective_filters, limit))
asyncio.to_thread(self.graph.search, query, effective_filters, limit)
)
if graph_task: if graph_task:
original_memories, graph_entities = await asyncio.gather(vector_store_task, graph_task) original_memories, graph_entities = await asyncio.gather(vector_store_task, graph_task)
@@ -1505,10 +1516,7 @@ class AsyncMemory(MemoryBase):
"role", "role",
] ]
core_and_promoted_keys = { core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
"data", "hash", "created_at", "updated_at", "id",
*promoted_payload_keys
}
original_memories = [] original_memories = []
for mem in memories: for mem in memories:
@@ -1525,9 +1533,7 @@ class AsyncMemory(MemoryBase):
if key in mem.payload: if key in mem.payload:
memory_item_dict[key] = mem.payload[key] memory_item_dict[key] = mem.payload[key]
additional_metadata = { additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
}
if additional_metadata: if additional_metadata:
memory_item_dict["metadata"] = additional_metadata memory_item_dict["metadata"] = additional_metadata
@@ -1650,7 +1656,7 @@ class AsyncMemory(MemoryBase):
capture_event("mem0._create_memory", self, {"memory_id": memory_id, "sync_type": "async"}) capture_event("mem0._create_memory", self, {"memory_id": memory_id, "sync_type": "async"})
return memory_id return memory_id
async def _create_procedural_memory(self, messages, metadata=None,llm=None ,prompt=None): async def _create_procedural_memory(self, messages, metadata=None, llm=None, prompt=None):
""" """
Create a procedural memory asynchronously Create a procedural memory asynchronously
@@ -1726,7 +1732,6 @@ class AsyncMemory(MemoryBase):
if "run_id" in existing_memory.payload: if "run_id" in existing_memory.payload:
new_metadata["run_id"] = existing_memory.payload["run_id"] new_metadata["run_id"] = existing_memory.payload["run_id"]
if "actor_id" in existing_memory.payload: if "actor_id" in existing_memory.payload:
new_metadata["actor_id"] = existing_memory.payload["actor_id"] new_metadata["actor_id"] = existing_memory.payload["actor_id"]
if "role" in existing_memory.payload: if "role" in existing_memory.payload:
+19 -57
View File
@@ -5,16 +5,12 @@ from mem0.memory.utils import format_entities
try: try:
from langchain_memgraph import Memgraph from langchain_memgraph import Memgraph
except ImportError: except ImportError:
raise ImportError( raise ImportError("langchain_memgraph is not installed. Please install it using pip install langchain-memgraph")
"langchain_memgraph is not installed. Please install it using pip install langchain-memgraph"
)
try: try:
from rank_bm25 import BM25Okapi from rank_bm25 import BM25Okapi
except ImportError: except ImportError:
raise ImportError( raise ImportError("rank_bm25 is not installed. Please install it using pip install rank-bm25")
"rank_bm25 is not installed. Please install it using pip install rank-bm25"
)
from mem0.graphs.tools import ( from mem0.graphs.tools import (
DELETE_MEMORY_STRUCT_TOOL_GRAPH, DELETE_MEMORY_STRUCT_TOOL_GRAPH,
@@ -74,22 +70,14 @@ class MemoryGraph:
filters (dict): A dictionary containing filters to be applied during the addition. filters (dict): A dictionary containing filters to be applied during the addition.
""" """
entity_type_map = self._retrieve_nodes_from_data(data, filters) entity_type_map = self._retrieve_nodes_from_data(data, filters)
to_be_added = self._establish_nodes_relations_from_data( to_be_added = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
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)
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: Batch queries with APOC plugin
# TODO: Add more filter support # TODO: Add more filter support
deleted_entities = self._delete_entities(to_be_deleted, filters["user_id"]) deleted_entities = self._delete_entities(to_be_deleted, filters["user_id"])
added_entities = self._add_entities( added_entities = self._add_entities(to_be_added, filters["user_id"], entity_type_map)
to_be_added, filters["user_id"], entity_type_map
)
return {"deleted_entities": deleted_entities, "added_entities": added_entities} 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. - "entities": List of related graph data based on the query.
""" """
entity_type_map = self._retrieve_nodes_from_data(query, filters) entity_type_map = self._retrieve_nodes_from_data(query, filters)
search_output = self._search_graph_db( search_output = self._search_graph_db(node_list=list(entity_type_map.keys()), filters=filters)
node_list=list(entity_type_map.keys()), filters=filters
)
if not search_output: if not search_output:
return [] return []
search_outputs_sequence = [ search_outputs_sequence = [
[item["source"], item["relationship"], item["destination"]] [item["source"], item["relationship"], item["destination"]] for item in search_output
for item in search_output
] ]
bm25 = BM25Okapi(search_outputs_sequence) bm25 = BM25Okapi(search_outputs_sequence)
@@ -126,9 +111,7 @@ class MemoryGraph:
search_results = [] search_results = []
for item in reranked_results: for item in reranked_results:
search_results.append( search_results.append({"source": item[0], "relationship": item[1], "destination": item[2]})
{"source": item[0], "relationship": item[1], "destination": item[2]}
)
logger.info(f"Returned {len(search_results)} search results") 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 RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT $limit LIMIT $limit
""" """
results = self.graph.query( results = self.graph.query(query, params={"user_id": filters["user_id"], "limit": limit})
query, params={"user_id": filters["user_id"], "limit": limit}
)
final_results = [] final_results = []
for result in 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}" f"Error in search tool: {e}, llm_provider={self.llm_provider}, search_results={search_results}"
) )
entity_type_map = { entity_type_map = {k.lower().replace(" ", "_"): v.lower().replace(" ", "_") for k, v in entity_type_map.items()}
k.lower().replace(" ", "_"): v.lower().replace(" ", "_") logger.debug(f"Entity type map: {entity_type_map}\n search_results={search_results}")
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 return entity_type_map
def _establish_nodes_relations_from_data(self, data, filters, entity_type_map): def _establish_nodes_relations_from_data(self, data, filters, entity_type_map):
@@ -223,9 +199,7 @@ class MemoryGraph:
messages = [ messages = [
{ {
"role": "system", "role": "system",
"content": EXTRACT_RELATIONS_PROMPT.replace( "content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]).replace(
"USER_ID", filters["user_id"]
).replace(
"CUSTOM_PROMPT", f"4. {self.config.graph_store.custom_prompt}" "CUSTOM_PROMPT", f"4. {self.config.graph_store.custom_prompt}"
), ),
}, },
@@ -235,9 +209,7 @@ class MemoryGraph:
messages = [ messages = [
{ {
"role": "system", "role": "system",
"content": EXTRACT_RELATIONS_PROMPT.replace( "content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]),
"USER_ID", filters["user_id"]
),
}, },
{ {
"role": "user", "role": "user",
@@ -304,9 +276,7 @@ class MemoryGraph:
def _get_delete_entities_from_search_output(self, search_output, data, filters): def _get_delete_entities_from_search_output(self, search_output, data, filters):
"""Get the entities to be deleted from the search output.""" """Get the entities to be deleted from the search output."""
search_output_string = format_entities(search_output) search_output_string = format_entities(search_output)
system_prompt, user_prompt = get_delete_messages( system_prompt, user_prompt = get_delete_messages(search_output_string, data, filters["user_id"])
search_output_string, data, filters["user_id"]
)
_tools = [DELETE_MEMORY_TOOL_GRAPH] _tools = [DELETE_MEMORY_TOOL_GRAPH]
if self.llm_provider in ["azure_openai_structured", "openai_structured"]: 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 # search for the nodes with the closest embeddings; this is basically
# comparison of one embedding to all embeddings in a graph -> vector # comparison of one embedding to all embeddings in a graph -> vector
# search with cosine similarity metric # search with cosine similarity metric
source_node_search_result = self._search_source_node( source_node_search_result = self._search_source_node(source_embedding, user_id, threshold=0.9)
source_embedding, user_id, threshold=0.9 destination_node_search_result = self._search_destination_node(dest_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 # TODO: Create a cypher query and common params for all the cases
if not destination_node_search_result and source_node_search_result: if not destination_node_search_result and source_node_search_result:
@@ -424,9 +390,7 @@ class MemoryGraph:
""" """
params = { params = {
"destination_id": destination_node_search_result[0][ "destination_id": destination_node_search_result[0]["id(destination_candidate)"],
"id(destination_candidate)"
],
"source_name": source, "source_name": source,
"source_embedding": source_embedding, "source_embedding": source_embedding,
"user_id": user_id, "user_id": user_id,
@@ -445,9 +409,7 @@ class MemoryGraph:
""" """
params = { params = {
"source_id": source_node_search_result[0]["id(source_candidate)"], "source_id": source_node_search_result[0]["id(source_candidate)"],
"destination_id": destination_node_search_result[0][ "destination_id": destination_node_search_result[0]["id(destination_candidate)"],
"id(destination_candidate)"
],
"user_id": user_id, "user_id": user_id,
} }
else: else:
+4 -8
View File
@@ -1,8 +1,8 @@
import logging
import sqlite3 import sqlite3
import threading import threading
import uuid import uuid
import logging from typing import Any, Dict, List, Optional
from typing import List, Dict, Any, Optional
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,9 +23,7 @@ class SQLiteManager:
""" """
with self._lock, self.connection: with self._lock, self.connection:
cur = self.connection.cursor() cur = self.connection.cursor()
cur.execute( cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='history'")
"SELECT name FROM sqlite_master WHERE type='table' AND name='history'"
)
if cur.fetchone() is None: if cur.fetchone() is None:
return # nothing to migrate return # nothing to migrate
@@ -55,9 +53,7 @@ class SQLiteManager:
intersecting = list(expected_cols & old_cols) intersecting = list(expected_cols & old_cols)
cols_csv = ", ".join(intersecting) cols_csv = ", ".join(intersecting)
cur.execute( cur.execute(f"INSERT INTO history ({cols_csv}) SELECT {cols_csv} FROM history_old")
f"INSERT INTO history ({cols_csv}) SELECT {cols_csv} FROM history_old"
)
cur.execute("DROP TABLE history_old") cur.execute("DROP TABLE history_old")
def _create_history_table(self) -> None: def _create_history_table(self) -> None:
+2 -2
View File
@@ -9,8 +9,8 @@ import mem0
from mem0.memory.setup import get_or_create_user_id from mem0.memory.setup import get_or_create_user_id
MEM0_TELEMETRY = os.environ.get("MEM0_TELEMETRY", "True") MEM0_TELEMETRY = os.environ.get("MEM0_TELEMETRY", "True")
PROJECT_API_KEY="phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX" PROJECT_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
HOST="https://us.i.posthog.com" HOST = "https://us.i.posthog.com"
if isinstance(MEM0_TELEMETRY, str): if isinstance(MEM0_TELEMETRY, str):
MEM0_TELEMETRY = MEM0_TELEMETRY.lower() in ("true", "1", "yes") MEM0_TELEMETRY = MEM0_TELEMETRY.lower() in ("true", "1", "yes")
+1 -1
View File
@@ -27,6 +27,7 @@ class LlmFactory:
"gemini": "mem0.llms.gemini.GeminiLLM", "gemini": "mem0.llms.gemini.GeminiLLM",
"deepseek": "mem0.llms.deepseek.DeepSeekLLM", "deepseek": "mem0.llms.deepseek.DeepSeekLLM",
"xai": "mem0.llms.xai.XAILLM", "xai": "mem0.llms.xai.XAILLM",
"sarvam": "mem0.llms.sarvam.SarvamLLM",
"lmstudio": "mem0.llms.lmstudio.LMStudioLLM", "lmstudio": "mem0.llms.lmstudio.LMStudioLLM",
"langchain": "mem0.llms.langchain.LangchainLLM", "langchain": "mem0.llms.langchain.LangchainLLM",
} }
@@ -103,4 +104,3 @@ class VectorStoreFactory:
def reset(cls, instance): def reset(cls, instance):
instance.reset() instance.reset()
return instance return instance
-1
View File
@@ -377,4 +377,3 @@ class AzureAISearch(VectorStoreBase):
except Exception as e: except Exception as e:
logger.error(f"Error resetting index {self.index_name}: {e}") logger.error(f"Error resetting index {self.index_name}: {e}")
raise raise
+6 -1
View File
@@ -58,7 +58,12 @@ class ElasticsearchDB(VectorStoreBase):
"mappings": { "mappings": {
"properties": { "properties": {
"text": {"type": "text"}, "text": {"type": "text"},
"vector": {"type": "dense_vector", "dims": self.embedding_model_dims, "index": True, "similarity": "cosine"}, "vector": {
"type": "dense_vector",
"dims": self.embedding_model_dims,
"index": True,
"similarity": "cosine",
},
"metadata": {"type": "object", "properties": {"user_id": {"type": "keyword"}}}, "metadata": {"type": "object", "properties": {"user_id": {"type": "keyword"}}},
} }
}, },
+2 -4
View File
@@ -14,6 +14,7 @@ from mem0.vector_stores.base import VectorStoreBase
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class OutputData(BaseModel): class OutputData(BaseModel):
id: Optional[str] # memory id id: Optional[str] # memory id
score: Optional[float] # distance score: Optional[float] # distance
@@ -162,10 +163,7 @@ class Langchain(VectorStoreBase):
if filters and "user_id" in filters: if filters and "user_id" in filters:
where_clause = {"user_id": filters["user_id"]} where_clause = {"user_id": filters["user_id"]}
result = self.client._collection.get( result = self.client._collection.get(where=where_clause, limit=limit)
where=where_clause,
limit=limit
)
# Convert the result to the expected format # Convert the result to the expected format
if result and isinstance(result, dict): if result and isinstance(result, dict):
+22 -78
View File
@@ -1,6 +1,6 @@
import logging import logging
from typing import Any, Dict, List, Optional
import time import time
from typing import Any, Dict, List, Optional
try: try:
from opensearchpy import OpenSearch, RequestsHttpConnection from opensearchpy import OpenSearch, RequestsHttpConnection
@@ -34,7 +34,7 @@ class OpenSearchDB(VectorStoreBase):
use_ssl=config.use_ssl, use_ssl=config.use_ssl,
verify_certs=config.verify_certs, verify_certs=config.verify_certs,
connection_class=RequestsHttpConnection, connection_class=RequestsHttpConnection,
pool_maxsize=20 pool_maxsize=20,
) )
self.collection_name = config.collection_name self.collection_name = config.collection_name
@@ -69,9 +69,7 @@ class OpenSearchDB(VectorStoreBase):
def create_col(self, name: str, vector_size: int) -> None: def create_col(self, name: str, vector_size: int) -> None:
"""Create a new collection (index in OpenSearch).""" """Create a new collection (index in OpenSearch)."""
index_settings = { index_settings = {
"settings": { "settings": {"index.knn": True},
"index.knn": True
},
"mappings": { "mappings": {
"properties": { "properties": {
"vector_field": { "vector_field": {
@@ -82,7 +80,7 @@ class OpenSearchDB(VectorStoreBase):
"payload": {"type": "object"}, "payload": {"type": "object"},
"id": {"type": "keyword"}, "id": {"type": "keyword"},
} }
} },
} }
if not self.client.indices.exists(index=name): if not self.client.indices.exists(index=name):
@@ -102,9 +100,7 @@ class OpenSearchDB(VectorStoreBase):
except Exception: except Exception:
retry_count += 1 retry_count += 1
if retry_count == max_retries: if retry_count == max_retries:
raise TimeoutError( raise TimeoutError(f"Index {name} creation timed out after {max_retries} seconds")
f"Index {name} creation timed out after {max_retries} seconds"
)
time.sleep(0.5) time.sleep(0.5)
def insert( def insert(
@@ -145,10 +141,7 @@ class OpenSearchDB(VectorStoreBase):
} }
# Start building the full query # Start building the full query
query_body = { query_body = {"size": limit * 2, "query": None}
"size": limit * 2,
"query": None
}
# Prepare filter conditions if applicable # Prepare filter conditions if applicable
filter_clauses = [] filter_clauses = []
@@ -156,18 +149,11 @@ class OpenSearchDB(VectorStoreBase):
for key in ["user_id", "run_id", "agent_id"]: for key in ["user_id", "run_id", "agent_id"]:
value = filters.get(key) value = filters.get(key)
if value: if value:
filter_clauses.append({ filter_clauses.append({"term": {f"payload.{key}.keyword": value}})
"term": {f"payload.{key}.keyword": value}
})
# Combine knn with filters if needed # Combine knn with filters if needed
if filter_clauses: if filter_clauses:
query_body["query"] = { query_body["query"] = {"bool": {"must": knn_query, "filter": filter_clauses}}
"bool": {
"must": knn_query,
"filter": filter_clauses
}
}
else: else:
query_body["query"] = knn_query query_body["query"] = knn_query
@@ -176,11 +162,7 @@ class OpenSearchDB(VectorStoreBase):
hits = response["hits"]["hits"] hits = response["hits"]["hits"]
results = [ results = [
OutputData( OutputData(id=hit["_source"].get("id"), score=hit["_score"], payload=hit["_source"].get("payload", {}))
id=hit["_source"].get("id"),
score=hit["_score"],
payload=hit["_source"].get("payload", {})
)
for hit in hits for hit in hits
] ]
return results return results
@@ -188,13 +170,7 @@ class OpenSearchDB(VectorStoreBase):
def delete(self, vector_id: str) -> None: def delete(self, vector_id: str) -> None:
"""Delete a vector by custom ID.""" """Delete a vector by custom ID."""
# First, find the document by custom ID # First, find the document by custom ID
search_query = { search_query = {"query": {"term": {"id": vector_id}}}
"query": {
"term": {
"id": vector_id
}
}
}
response = self.client.search(index=self.collection_name, body=search_query) response = self.client.search(index=self.collection_name, body=search_query)
hits = response.get("hits", {}).get("hits", []) hits = response.get("hits", {}).get("hits", [])
@@ -207,18 +183,11 @@ class OpenSearchDB(VectorStoreBase):
# Delete using the actual document ID # Delete using the actual document ID
self.client.delete(index=self.collection_name, id=opensearch_id) self.client.delete(index=self.collection_name, id=opensearch_id)
def update(self, vector_id: str, vector: Optional[List[float]] = None, payload: Optional[Dict] = None) -> None: def update(self, vector_id: str, vector: Optional[List[float]] = None, payload: Optional[Dict] = None) -> None:
"""Update a vector and its payload using the custom 'id' field.""" """Update a vector and its payload using the custom 'id' field."""
# First, find the document by custom ID # First, find the document by custom ID
search_query = { search_query = {"query": {"term": {"id": vector_id}}}
"query": {
"term": {
"id": vector_id
}
}
}
response = self.client.search(index=self.collection_name, body=search_query) response = self.client.search(index=self.collection_name, body=search_query)
hits = response.get("hits", {}).get("hits", []) hits = response.get("hits", {}).get("hits", [])
@@ -241,7 +210,6 @@ class OpenSearchDB(VectorStoreBase):
except Exception: except Exception:
pass pass
def get(self, vector_id: str) -> Optional[OutputData]: def get(self, vector_id: str) -> Optional[OutputData]:
"""Retrieve a vector by ID.""" """Retrieve a vector by ID."""
try: try:
@@ -251,13 +219,7 @@ class OpenSearchDB(VectorStoreBase):
self.create_col(self.collection_name, self.embedding_model_dims) self.create_col(self.collection_name, self.embedding_model_dims)
return None return None
search_query = { search_query = {"query": {"term": {"id": vector_id}}}
"query": {
"term": {
"id": vector_id
}
}
}
response = self.client.search(index=self.collection_name, body=search_query) response = self.client.search(index=self.collection_name, body=search_query)
hits = response["hits"]["hits"] hits = response["hits"]["hits"]
@@ -265,11 +227,7 @@ class OpenSearchDB(VectorStoreBase):
if not hits: if not hits:
return None return None
return OutputData( return OutputData(id=hits[0]["_source"].get("id"), score=1.0, payload=hits[0]["_source"].get("payload", {}))
id=hits[0]["_source"].get("id"),
score=1.0,
payload=hits[0]["_source"].get("payload", {})
)
except Exception as e: except Exception as e:
logger.error(f"Error retrieving vector {vector_id}: {str(e)}") logger.error(f"Error retrieving vector {vector_id}: {str(e)}")
return None return None
@@ -287,30 +245,19 @@ class OpenSearchDB(VectorStoreBase):
return self.client.indices.get(index=name) return self.client.indices.get(index=name)
def list(self, filters: Optional[Dict] = None, limit: Optional[int] = None) -> List[OutputData]: def list(self, filters: Optional[Dict] = None, limit: Optional[int] = None) -> List[OutputData]:
try: try:
"""List all memories with optional filters.""" """List all memories with optional filters."""
query: Dict = { query: Dict = {"query": {"match_all": {}}}
"query": {
"match_all": {}
}
}
filter_clauses = [] filter_clauses = []
if filters: if filters:
for key in ["user_id", "run_id", "agent_id"]: for key in ["user_id", "run_id", "agent_id"]:
value = filters.get(key) value = filters.get(key)
if value: if value:
filter_clauses.append({ filter_clauses.append({"term": {f"payload.{key}.keyword": value}})
"term": {f"payload.{key}.keyword": value}
})
if filter_clauses: if filter_clauses:
query["query"] = { query["query"] = {"bool": {"filter": filter_clauses}}
"bool": {
"filter": filter_clauses
}
}
if limit: if limit:
query["size"] = limit query["size"] = limit
@@ -318,18 +265,15 @@ class OpenSearchDB(VectorStoreBase):
response = self.client.search(index=self.collection_name, body=query) response = self.client.search(index=self.collection_name, body=query)
hits = response["hits"]["hits"] hits = response["hits"]["hits"]
return [[ return [
OutputData( [
id=hit["_source"].get("id"), OutputData(id=hit["_source"].get("id"), score=1.0, payload=hit["_source"].get("payload", {}))
score=1.0, for hit in hits
payload=hit["_source"].get("payload", {}) ]
) ]
for hit in hits
]]
except Exception: except Exception:
return [] return []
def reset(self): def reset(self):
"""Reset the index by deleting and recreating it.""" """Reset the index by deleting and recreating it."""
logger.warning(f"Resetting index {self.collection_name}...") logger.warning(f"Resetting index {self.collection_name}...")
+4 -5
View File
@@ -88,7 +88,7 @@ class RedisDB(VectorStoreBase):
The created index object. The created index object.
""" """
# Use provided parameters or fall back to instance attributes # Use provided parameters or fall back to instance attributes
collection_name = name or self.schema['index']['name'] collection_name = name or self.schema["index"]["name"]
embedding_dims = vector_size or self.embedding_model_dims embedding_dims = vector_size or self.embedding_model_dims
distance_metric = distance or "cosine" distance_metric = distance or "cosine"
@@ -237,7 +237,7 @@ class RedisDB(VectorStoreBase):
""" """
Reset the index by deleting and recreating it. Reset the index by deleting and recreating it.
""" """
collection_name = self.schema['index']['name'] collection_name = self.schema["index"]["name"]
logger.warning(f"Resetting index {collection_name}...") logger.warning(f"Resetting index {collection_name}...")
self.delete_col() self.delete_col()
@@ -245,9 +245,8 @@ class RedisDB(VectorStoreBase):
self.index.set_client(self.client) self.index.set_client(self.client)
self.index.create(overwrite=True) self.index.create(overwrite=True)
#or use # or use
#self.create_col(collection_name, self.embedding_model_dims) # self.create_col(collection_name, self.embedding_model_dims)
# Recreate the index with the same parameters # Recreate the index with the same parameters
self.create_col(collection_name, self.embedding_model_dims) self.create_col(collection_name, self.embedding_model_dims)
-1
View File
@@ -291,4 +291,3 @@ class UpstashVector(VectorStoreBase):
Reset the Upstash Vector index. Reset the Upstash Vector index.
""" """
self.delete_col() self.delete_col()
+1
View File
@@ -7,6 +7,7 @@ WORKDIR /usr/src/openmemory
COPY requirements.txt . COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY config.json .
COPY . . COPY . .
EXPOSE 8765 EXPOSE 8765
@@ -0,0 +1,40 @@
"""add_config_table
Revision ID: add_config_table
Revises: 0b53c747049a
Create Date: 2023-06-01 10:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
import uuid
# revision identifiers, used by Alembic.
revision = 'add_config_table'
down_revision = '0b53c747049a'
branch_labels = None
depends_on = None
def upgrade():
# Create configs table if it doesn't exist
op.create_table(
'configs',
sa.Column('id', sa.UUID(), nullable=False, default=lambda: uuid.uuid4()),
sa.Column('key', sa.String(), nullable=False),
sa.Column('value', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('key')
)
# Create index for key lookups
op.create_index('idx_configs_key', 'configs', ['key'])
def downgrade():
# Drop the configs table
op.drop_index('idx_configs_key', 'configs')
op.drop_table('configs')
+57 -8
View File
@@ -1,3 +1,20 @@
"""
MCP Server for OpenMemory with resilient memory client handling.
This module implements an MCP (Model Context Protocol) server that provides
memory operations for OpenMemory. The memory client is initialized lazily
to prevent server crashes when external dependencies (like Ollama) are
unavailable. If the memory client cannot be initialized, the server will
continue running with limited functionality and appropriate error messages.
Key features:
- Lazy memory client initialization
- Graceful error handling for unavailable dependencies
- Fallback to database-only mode when vector store is unavailable
- Proper logging for debugging connection issues
- Environment variable parsing for API keys
"""
import logging import logging
import json import json
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP
@@ -19,14 +36,17 @@ from qdrant_client import models as qdrant_models
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
# Initialize MCP and memory client # Initialize MCP
mcp = FastMCP("mem0-mcp-server") mcp = FastMCP("mem0-mcp-server")
# Check if OpenAI API key is set # Don't initialize memory client at import time - do it lazily when needed
if not os.getenv("OPENAI_API_KEY"): def get_memory_client_safe():
raise Exception("OPENAI_API_KEY is not set in .env file") """Get memory client with error handling. Returns None if client cannot be initialized."""
try:
memory_client = get_memory_client() return get_memory_client()
except Exception as e:
logging.warning(f"Failed to get memory client: {e}")
return None
# Context variables for user_id and client_name # Context variables for user_id and client_name
user_id_var: contextvars.ContextVar[str] = contextvars.ContextVar("user_id") user_id_var: contextvars.ContextVar[str] = contextvars.ContextVar("user_id")
@@ -38,7 +58,7 @@ mcp_router = APIRouter(prefix="/mcp")
# Initialize SSE transport # Initialize SSE transport
sse = SseServerTransport("/mcp/messages/") sse = SseServerTransport("/mcp/messages/")
@mcp.tool(description="Add a new memory. This method is called everytime the user informs anything about themselves, their preferences, or anything that has any relevent information whcih can be useful in the future conversation. This can also be called when the user asks you to remember something.") @mcp.tool(description="Add a new memory. This method is called everytime the user informs anything about themselves, their preferences, or anything that has any relevant information which can be useful in the future conversation. This can also be called when the user asks you to remember something.")
async def add_memories(text: str) -> str: async def add_memories(text: str) -> str:
uid = user_id_var.get(None) uid = user_id_var.get(None)
client_name = client_name_var.get(None) client_name = client_name_var.get(None)
@@ -48,6 +68,11 @@ async def add_memories(text: str) -> str:
if not client_name: if not client_name:
return "Error: client_name not provided" return "Error: client_name not provided"
# Get memory client safely
memory_client = get_memory_client_safe()
if not memory_client:
return "Error: Memory system is currently unavailable. Please try again later."
try: try:
db = SessionLocal() db = SessionLocal()
try: try:
@@ -113,6 +138,7 @@ async def add_memories(text: str) -> str:
finally: finally:
db.close() db.close()
except Exception as e: except Exception as e:
logging.exception(f"Error adding to memory: {e}")
return f"Error adding to memory: {e}" return f"Error adding to memory: {e}"
@@ -124,6 +150,12 @@ async def search_memory(query: str) -> str:
return "Error: user_id not provided" return "Error: user_id not provided"
if not client_name: if not client_name:
return "Error: client_name not provided" return "Error: client_name not provided"
# Get memory client safely
memory_client = get_memory_client_safe()
if not memory_client:
return "Error: Memory system is currently unavailable. Please try again later."
try: try:
db = SessionLocal() db = SessionLocal()
try: try:
@@ -216,6 +248,12 @@ async def list_memories() -> str:
return "Error: user_id not provided" return "Error: user_id not provided"
if not client_name: if not client_name:
return "Error: client_name not provided" return "Error: client_name not provided"
# Get memory client safely
memory_client = get_memory_client_safe()
if not memory_client:
return "Error: Memory system is currently unavailable. Please try again later."
try: try:
db = SessionLocal() db = SessionLocal()
try: try:
@@ -267,6 +305,7 @@ async def list_memories() -> str:
finally: finally:
db.close() db.close()
except Exception as e: except Exception as e:
logging.exception(f"Error getting memories: {e}")
return f"Error getting memories: {e}" return f"Error getting memories: {e}"
@@ -278,6 +317,12 @@ async def delete_all_memories() -> str:
return "Error: user_id not provided" return "Error: user_id not provided"
if not client_name: if not client_name:
return "Error: client_name not provided" return "Error: client_name not provided"
# Get memory client safely
memory_client = get_memory_client_safe()
if not memory_client:
return "Error: Memory system is currently unavailable. Please try again later."
try: try:
db = SessionLocal() db = SessionLocal()
try: try:
@@ -289,7 +334,10 @@ async def delete_all_memories() -> str:
# delete the accessible memories only # delete the accessible memories only
for memory_id in accessible_memory_ids: for memory_id in accessible_memory_ids:
memory_client.delete(memory_id) try:
memory_client.delete(memory_id)
except Exception as delete_error:
logging.warning(f"Failed to delete memory {memory_id} from vector store: {delete_error}")
# Update each memory's state and create history entries # Update each memory's state and create history entries
now = datetime.datetime.now(datetime.UTC) now = datetime.datetime.now(datetime.UTC)
@@ -322,6 +370,7 @@ async def delete_all_memories() -> str:
finally: finally:
db.close() db.close()
except Exception as e: except Exception as e:
logging.exception(f"Error deleting memories: {e}")
return f"Error deleting memories: {e}" return f"Error deleting memories: {e}"
+11
View File
@@ -56,6 +56,17 @@ class App(Base):
memories = relationship("Memory", back_populates="app") memories = relationship("Memory", back_populates="app")
class Config(Base):
__tablename__ = "configs"
id = Column(UUID, primary_key=True, default=lambda: uuid.uuid4())
key = Column(String, unique=True, nullable=False, index=True)
value = Column(JSON, nullable=False)
created_at = Column(DateTime, default=get_current_utc_time)
updated_at = Column(DateTime,
default=get_current_utc_time,
onupdate=get_current_utc_time)
class Memory(Base): class Memory(Base):
__tablename__ = "memories" __tablename__ = "memories"
id = Column(UUID, primary_key=True, default=lambda: uuid.uuid4()) id = Column(UUID, primary_key=True, default=lambda: uuid.uuid4())
+2 -1
View File
@@ -1,5 +1,6 @@
from .memories import router as memories_router from .memories import router as memories_router
from .apps import router as apps_router from .apps import router as apps_router
from .stats import router as stats_router from .stats import router as stats_router
from .config import router as config_router
__all__ = ["memories_router", "apps_router", "stats_router"] __all__ = ["memories_router", "apps_router", "stats_router", "config_router"]
+240
View File
@@ -0,0 +1,240 @@
import os
import json
from typing import Dict, Any, Optional
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import Config as ConfigModel
from app.utils.memory import reset_memory_client
router = APIRouter(prefix="/api/v1/config", tags=["config"])
class LLMConfig(BaseModel):
model: str = Field(..., description="LLM model name")
temperature: float = Field(..., description="Temperature setting for the model")
max_tokens: int = Field(..., description="Maximum tokens to generate")
api_key: Optional[str] = Field(None, description="API key or 'env:API_KEY' to use environment variable")
ollama_base_url: Optional[str] = Field(None, description="Base URL for Ollama server (e.g., http://host.docker.internal:11434)")
class LLMProvider(BaseModel):
provider: str = Field(..., description="LLM provider name")
config: LLMConfig
class EmbedderConfig(BaseModel):
model: str = Field(..., description="Embedder model name")
api_key: Optional[str] = Field(None, description="API key or 'env:API_KEY' to use environment variable")
ollama_base_url: Optional[str] = Field(None, description="Base URL for Ollama server (e.g., http://host.docker.internal:11434)")
class EmbedderProvider(BaseModel):
provider: str = Field(..., description="Embedder provider name")
config: EmbedderConfig
class OpenMemoryConfig(BaseModel):
custom_instructions: Optional[str] = Field(None, description="Custom instructions for memory management and fact extraction")
class Mem0Config(BaseModel):
llm: Optional[LLMProvider] = None
embedder: Optional[EmbedderProvider] = None
class ConfigSchema(BaseModel):
openmemory: Optional[OpenMemoryConfig] = None
mem0: Mem0Config
def get_default_configuration():
"""Get the default configuration with sensible defaults for LLM and embedder."""
return {
"openmemory": {
"custom_instructions": None
},
"mem0": {
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini",
"temperature": 0.1,
"max_tokens": 2000,
"api_key": "env:OPENAI_API_KEY"
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small",
"api_key": "env:OPENAI_API_KEY"
}
}
}
}
def get_config_from_db(db: Session, key: str = "main"):
"""Get configuration from database."""
config = db.query(ConfigModel).filter(ConfigModel.key == key).first()
if not config:
# Create default config with proper provider configurations
default_config = get_default_configuration()
db_config = ConfigModel(key=key, value=default_config)
db.add(db_config)
db.commit()
db.refresh(db_config)
return default_config
# Ensure the config has all required sections with defaults
config_value = config.value
default_config = get_default_configuration()
# Merge with defaults to ensure all required fields exist
if "openmemory" not in config_value:
config_value["openmemory"] = default_config["openmemory"]
if "mem0" not in config_value:
config_value["mem0"] = default_config["mem0"]
else:
# Ensure LLM config exists with defaults
if "llm" not in config_value["mem0"] or config_value["mem0"]["llm"] is None:
config_value["mem0"]["llm"] = default_config["mem0"]["llm"]
# Ensure embedder config exists with defaults
if "embedder" not in config_value["mem0"] or config_value["mem0"]["embedder"] is None:
config_value["mem0"]["embedder"] = default_config["mem0"]["embedder"]
# Save the updated config back to database if it was modified
if config_value != config.value:
config.value = config_value
db.commit()
db.refresh(config)
return config_value
def save_config_to_db(db: Session, config: Dict[str, Any], key: str = "main"):
"""Save configuration to database."""
db_config = db.query(ConfigModel).filter(ConfigModel.key == key).first()
if db_config:
db_config.value = config
db_config.updated_at = None # Will trigger the onupdate to set current time
else:
db_config = ConfigModel(key=key, value=config)
db.add(db_config)
db.commit()
db.refresh(db_config)
return db_config.value
@router.get("/", response_model=ConfigSchema)
async def get_configuration(db: Session = Depends(get_db)):
"""Get the current configuration."""
config = get_config_from_db(db)
return config
@router.put("/", response_model=ConfigSchema)
async def update_configuration(config: ConfigSchema, db: Session = Depends(get_db)):
"""Update the configuration."""
current_config = get_config_from_db(db)
# Convert to dict for processing
updated_config = current_config.copy()
# Update openmemory settings if provided
if config.openmemory is not None:
if "openmemory" not in updated_config:
updated_config["openmemory"] = {}
updated_config["openmemory"].update(config.openmemory.dict(exclude_none=True))
# Update mem0 settings
updated_config["mem0"] = config.mem0.dict(exclude_none=True)
# Save the configuration to database
save_config_to_db(db, updated_config)
reset_memory_client()
return updated_config
@router.post("/reset", response_model=ConfigSchema)
async def reset_configuration(db: Session = Depends(get_db)):
"""Reset the configuration to default values."""
try:
# Get the default configuration with proper provider setups
default_config = get_default_configuration()
# Save it as the current configuration in the database
save_config_to_db(db, default_config)
reset_memory_client()
return default_config
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to reset configuration: {str(e)}"
)
@router.get("/mem0/llm", response_model=LLMProvider)
async def get_llm_configuration(db: Session = Depends(get_db)):
"""Get only the LLM configuration."""
config = get_config_from_db(db)
llm_config = config.get("mem0", {}).get("llm", {})
return llm_config
@router.put("/mem0/llm", response_model=LLMProvider)
async def update_llm_configuration(llm_config: LLMProvider, db: Session = Depends(get_db)):
"""Update only the LLM configuration."""
current_config = get_config_from_db(db)
# Ensure mem0 key exists
if "mem0" not in current_config:
current_config["mem0"] = {}
# Update the LLM configuration
current_config["mem0"]["llm"] = llm_config.dict(exclude_none=True)
# Save the configuration to database
save_config_to_db(db, current_config)
reset_memory_client()
return current_config["mem0"]["llm"]
@router.get("/mem0/embedder", response_model=EmbedderProvider)
async def get_embedder_configuration(db: Session = Depends(get_db)):
"""Get only the Embedder configuration."""
config = get_config_from_db(db)
embedder_config = config.get("mem0", {}).get("embedder", {})
return embedder_config
@router.put("/mem0/embedder", response_model=EmbedderProvider)
async def update_embedder_configuration(embedder_config: EmbedderProvider, db: Session = Depends(get_db)):
"""Update only the Embedder configuration."""
current_config = get_config_from_db(db)
# Ensure mem0 key exists
if "mem0" not in current_config:
current_config["mem0"] = {}
# Update the Embedder configuration
current_config["mem0"]["embedder"] = embedder_config.dict(exclude_none=True)
# Save the configuration to database
save_config_to_db(db, current_config)
reset_memory_client()
return current_config["mem0"]["embedder"]
@router.get("/openmemory", response_model=OpenMemoryConfig)
async def get_openmemory_configuration(db: Session = Depends(get_db)):
"""Get only the OpenMemory configuration."""
config = get_config_from_db(db)
openmemory_config = config.get("openmemory", {})
return openmemory_config
@router.put("/openmemory", response_model=OpenMemoryConfig)
async def update_openmemory_configuration(openmemory_config: OpenMemoryConfig, db: Session = Depends(get_db)):
"""Update only the OpenMemory configuration."""
current_config = get_config_from_db(db)
# Ensure openmemory key exists
if "openmemory" not in current_config:
current_config["openmemory"] = {}
# Update the OpenMemory configuration
current_config["openmemory"].update(openmemory_config.dict(exclude_none=True))
# Save the configuration to database
save_config_to_db(db, current_config)
reset_memory_client()
return current_config["openmemory"]
+67 -89
View File
@@ -2,6 +2,7 @@ from datetime import datetime, UTC
from typing import List, Optional, Set from typing import List, Optional, Set
from uuid import UUID, uuid4 from uuid import UUID, uuid4
import logging import logging
import os
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from fastapi_pagination import Page, Params from fastapi_pagination import Page, Params
@@ -13,13 +14,11 @@ from app.utils.memory import get_memory_client
from app.database import get_db from app.database import get_db
from app.models import ( from app.models import (
Memory, MemoryState, MemoryAccessLog, App, Memory, MemoryState, MemoryAccessLog, App,
MemoryStatusHistory, User, Category, AccessControl MemoryStatusHistory, User, Category, AccessControl, Config as ConfigModel
) )
from app.schemas import MemoryResponse, PaginatedMemoryResponse from app.schemas import MemoryResponse, PaginatedMemoryResponse
from app.utils.permissions import check_memory_access_permissions from app.utils.permissions import check_memory_access_permissions
memory_client = get_memory_client()
router = APIRouter(prefix="/api/v1/memories", tags=["memories"]) router = APIRouter(prefix="/api/v1/memories", tags=["memories"])
@@ -227,100 +226,79 @@ async def create_memory(
# Log what we're about to do # Log what we're about to do
logging.info(f"Creating memory for user_id: {request.user_id} with app: {request.app}") logging.info(f"Creating memory for user_id: {request.user_id} with app: {request.app}")
# Save to Qdrant via memory_client # Try to get memory client safely
qdrant_response = memory_client.add(
request.text,
user_id=request.user_id, # Use string user_id to match search
metadata={
"source_app": "openmemory",
"mcp_client": request.app,
}
)
# Log the response for debugging
logging.info(f"Qdrant response: {qdrant_response}")
# Process Qdrant response
if isinstance(qdrant_response, dict) and 'results' in qdrant_response:
for result in qdrant_response['results']:
if result['event'] == 'ADD':
# Get the Qdrant-generated ID
memory_id = UUID(result['id'])
# Check if memory already exists
existing_memory = db.query(Memory).filter(Memory.id == memory_id).first()
if existing_memory:
# Update existing memory
existing_memory.state = MemoryState.active
existing_memory.content = result['memory']
memory = existing_memory
else:
# Create memory with the EXACT SAME ID from Qdrant
memory = Memory(
id=memory_id, # Use the same ID that Qdrant generated
user_id=user.id,
app_id=app_obj.id,
content=result['memory'],
metadata_=request.metadata,
state=MemoryState.active
)
db.add(memory)
# Create history entry
history = MemoryStatusHistory(
memory_id=memory_id,
changed_by=user.id,
old_state=MemoryState.deleted if existing_memory else MemoryState.deleted,
new_state=MemoryState.active
)
db.add(history)
db.commit()
db.refresh(memory)
return memory
# Fallback to traditional DB-only approach if Qdrant integration fails
# Generate a random UUID for the memory
memory_id = uuid4()
memory = Memory(
id=memory_id,
user_id=user.id,
app_id=app_obj.id,
content=request.text,
metadata_=request.metadata
)
db.add(memory)
# Create history entry
history = MemoryStatusHistory(
memory_id=memory_id,
changed_by=user.id,
old_state=MemoryState.deleted,
new_state=MemoryState.active
)
db.add(history)
db.commit()
db.refresh(memory)
# Attempt to add to Qdrant with the same ID we just created
try: try:
# Try to add with our specific ID memory_client = get_memory_client()
memory_client.add( if not memory_client:
raise Exception("Memory client is not available")
except Exception as client_error:
logging.warning(f"Memory client unavailable: {client_error}. Creating memory in database only.")
# Return a json response with the error
return {
"error": str(client_error)
}
# Try to save to Qdrant via memory_client
try:
qdrant_response = memory_client.add(
request.text, request.text,
memory_id=str(memory_id), # Specify the ID user_id=request.user_id, # Use string user_id to match search
user_id=request.user_id,
metadata={ metadata={
"source_app": "openmemory", "source_app": "openmemory",
"mcp_client": request.app, "mcp_client": request.app,
} }
) )
except Exception as e:
logging.error(f"Failed to add to Qdrant in fallback path: {e}")
# Continue anyway, the DB record is created
return memory # Log the response for debugging
logging.info(f"Qdrant response: {qdrant_response}")
# Process Qdrant response
if isinstance(qdrant_response, dict) and 'results' in qdrant_response:
for result in qdrant_response['results']:
if result['event'] == 'ADD':
# Get the Qdrant-generated ID
memory_id = UUID(result['id'])
# Check if memory already exists
existing_memory = db.query(Memory).filter(Memory.id == memory_id).first()
if existing_memory:
# Update existing memory
existing_memory.state = MemoryState.active
existing_memory.content = result['memory']
memory = existing_memory
else:
# Create memory with the EXACT SAME ID from Qdrant
memory = Memory(
id=memory_id, # Use the same ID that Qdrant generated
user_id=user.id,
app_id=app_obj.id,
content=result['memory'],
metadata_=request.metadata,
state=MemoryState.active
)
db.add(memory)
# Create history entry
history = MemoryStatusHistory(
memory_id=memory_id,
changed_by=user.id,
old_state=MemoryState.deleted if existing_memory else MemoryState.deleted,
new_state=MemoryState.active
)
db.add(history)
db.commit()
db.refresh(memory)
return memory
except Exception as qdrant_error:
logging.warning(f"Qdrant operation failed: {qdrant_error}.")
# Return a json response with the error
return {
"error": str(qdrant_error)
}
# Get memory by ID # Get memory by ID
+265 -24
View File
@@ -1,9 +1,193 @@
"""
Memory client utilities for OpenMemory.
This module provides functionality to initialize and manage the Mem0 memory client
with automatic configuration management and Docker environment support.
Docker Ollama Configuration:
When running inside a Docker container and using Ollama as the LLM or embedder provider,
the system automatically detects the Docker environment and adjusts localhost URLs
to properly reach the host machine where Ollama is running.
Supported Docker host resolution (in order of preference):
1. OLLAMA_HOST environment variable (if set)
2. host.docker.internal (Docker Desktop for Mac/Windows)
3. Docker bridge gateway IP (typically 172.17.0.1 on Linux)
4. Fallback to 172.17.0.1
Example configuration that will be automatically adjusted:
{
"llm": {
"provider": "ollama",
"config": {
"model": "llama3.1:latest",
"ollama_base_url": "http://localhost:11434" # Auto-adjusted in Docker
}
}
}
"""
import os import os
import json
import hashlib
import socket
import platform
from mem0 import Memory from mem0 import Memory
from app.database import SessionLocal
from app.models import Config as ConfigModel
memory_client = None _memory_client = None
_config_hash = None
def _get_config_hash(config_dict):
"""Generate a hash of the config to detect changes."""
config_str = json.dumps(config_dict, sort_keys=True)
return hashlib.md5(config_str.encode()).hexdigest()
def _get_docker_host_url():
"""
Determine the appropriate host URL to reach host machine from inside Docker container.
Returns the best available option for reaching the host from inside a container.
"""
# Check for custom environment variable first
custom_host = os.environ.get('OLLAMA_HOST')
if custom_host:
print(f"Using custom Ollama host from OLLAMA_HOST: {custom_host}")
return custom_host.replace('http://', '').replace('https://', '').split(':')[0]
# Check if we're running inside Docker
if not os.path.exists('/.dockerenv'):
# Not in Docker, return localhost as-is
return "localhost"
print("Detected Docker environment, adjusting host URL for Ollama...")
# Try different host resolution strategies
host_candidates = []
# 1. host.docker.internal (works on Docker Desktop for Mac/Windows)
try:
socket.gethostbyname('host.docker.internal')
host_candidates.append('host.docker.internal')
print("Found host.docker.internal")
except socket.gaierror:
pass
# 2. Docker bridge gateway (typically 172.17.0.1 on Linux)
try:
with open('/proc/net/route', 'r') as f:
for line in f:
fields = line.strip().split()
if fields[1] == '00000000': # Default route
gateway_hex = fields[2]
gateway_ip = socket.inet_ntoa(bytes.fromhex(gateway_hex)[::-1])
host_candidates.append(gateway_ip)
print(f"Found Docker gateway: {gateway_ip}")
break
except (FileNotFoundError, IndexError, ValueError):
pass
# 3. Fallback to common Docker bridge IP
if not host_candidates:
host_candidates.append('172.17.0.1')
print("Using fallback Docker bridge IP: 172.17.0.1")
# Return the first available candidate
return host_candidates[0]
def _fix_ollama_urls(config_section):
"""
Fix Ollama URLs for Docker environment.
Replaces localhost URLs with appropriate Docker host URLs.
Sets default ollama_base_url if not provided.
"""
if not config_section or "config" not in config_section:
return config_section
ollama_config = config_section["config"]
# Set default ollama_base_url if not provided
if "ollama_base_url" not in ollama_config:
ollama_config["ollama_base_url"] = "http://host.docker.internal:11434"
else:
# Check for ollama_base_url and fix if it's localhost
url = ollama_config["ollama_base_url"]
if "localhost" in url or "127.0.0.1" in url:
docker_host = _get_docker_host_url()
if docker_host != "localhost":
new_url = url.replace("localhost", docker_host).replace("127.0.0.1", docker_host)
ollama_config["ollama_base_url"] = new_url
print(f"Adjusted Ollama URL from {url} to {new_url}")
return config_section
def reset_memory_client():
"""Reset the global memory client to force reinitialization with new config."""
global _memory_client, _config_hash
_memory_client = None
_config_hash = None
def get_default_memory_config():
"""Get default memory client configuration with sensible defaults."""
return {
"vector_store": {
"provider": "qdrant",
"config": {
"collection_name": "openmemory",
"host": "mem0_store",
"port": 6333,
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini",
"temperature": 0.1,
"max_tokens": 2000,
"api_key": "env:OPENAI_API_KEY"
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small",
"api_key": "env:OPENAI_API_KEY"
}
},
"version": "v1.1"
}
def _parse_environment_variables(config_dict):
"""
Parse environment variables in config values.
Converts 'env:VARIABLE_NAME' to actual environment variable values.
"""
if isinstance(config_dict, dict):
parsed_config = {}
for key, value in config_dict.items():
if isinstance(value, str) and value.startswith("env:"):
env_var = value.split(":", 1)[1]
env_value = os.environ.get(env_var)
if env_value:
parsed_config[key] = env_value
print(f"Loaded {env_var} from environment for {key}")
else:
print(f"Warning: Environment variable {env_var} not found, keeping original value")
parsed_config[key] = value
elif isinstance(value, dict):
parsed_config[key] = _parse_environment_variables(value)
else:
parsed_config[key] = value
return parsed_config
return config_dict
def get_memory_client(custom_instructions: str = None): def get_memory_client(custom_instructions: str = None):
@@ -14,37 +198,94 @@ def get_memory_client(custom_instructions: str = None):
custom_instructions: Optional instructions for the memory project. custom_instructions: Optional instructions for the memory project.
Returns: Returns:
Initialized Mem0 client instance. Initialized Mem0 client instance or None if initialization fails.
Raises: Raises:
Exception: If required API keys are not set. Exception: If required API keys are not set or critical configuration is missing.
""" """
global memory_client global _memory_client, _config_hash
if memory_client is not None:
return memory_client
try: try:
config = { # Start with default configuration
"vector_store": { config = get_default_memory_config()
"provider": "qdrant",
"config": {
"collection_name": "openmemory",
"host": "mem0_store",
"port": 6333,
}
}
}
memory_client = Memory.from_config(config_dict=config) # Variable to track custom instructions
except Exception: db_custom_instructions = None
raise Exception("Exception occurred while initializing memory client")
# Update project with custom instructions if provided # Load configuration from database
if custom_instructions: try:
memory_client.update_project(custom_instructions=custom_instructions) db = SessionLocal()
db_config = db.query(ConfigModel).filter(ConfigModel.key == "main").first()
return memory_client if db_config:
json_config = db_config.value
# Extract custom instructions from openmemory settings
if "openmemory" in json_config and "custom_instructions" in json_config["openmemory"]:
db_custom_instructions = json_config["openmemory"]["custom_instructions"]
# Override defaults with configurations from the database
if "mem0" in json_config:
mem0_config = json_config["mem0"]
# Update LLM configuration if available
if "llm" in mem0_config and mem0_config["llm"] is not None:
config["llm"] = mem0_config["llm"]
# Fix Ollama URLs for Docker if needed
if config["llm"].get("provider") == "ollama":
config["llm"] = _fix_ollama_urls(config["llm"])
# Update Embedder configuration if available
if "embedder" in mem0_config and mem0_config["embedder"] is not None:
config["embedder"] = mem0_config["embedder"]
# Fix Ollama URLs for Docker if needed
if config["embedder"].get("provider") == "ollama":
config["embedder"] = _fix_ollama_urls(config["embedder"])
else:
print("No configuration found in database, using defaults")
db.close()
except Exception as e:
print(f"Warning: Error loading configuration from database: {e}")
print("Using default configuration")
# Continue with default configuration if database config can't be loaded
# Use custom_instructions parameter first, then fall back to database value
instructions_to_use = custom_instructions or db_custom_instructions
if instructions_to_use:
config["custom_fact_extraction_prompt"] = instructions_to_use
# ALWAYS parse environment variables in the final config
# This ensures that even default config values like "env:OPENAI_API_KEY" get parsed
print("Parsing environment variables in final config...")
config = _parse_environment_variables(config)
# Check if config has changed by comparing hashes
current_config_hash = _get_config_hash(config)
# Only reinitialize if config changed or client doesn't exist
if _memory_client is None or _config_hash != current_config_hash:
print(f"Initializing memory client with config hash: {current_config_hash}")
try:
_memory_client = Memory.from_config(config_dict=config)
_config_hash = current_config_hash
print("Memory client initialized successfully")
except Exception as init_error:
print(f"Warning: Failed to initialize memory client: {init_error}")
print("Server will continue running with limited memory functionality")
_memory_client = None
_config_hash = None
return None
return _memory_client
except Exception as e:
print(f"Warning: Exception occurred while initializing memory client: {e}")
print("Server will continue running with limited memory functionality")
return None
def get_default_user_id(): def get_default_user_id():

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