Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06d86996f2 | ||
|
|
bb14cc42a0 | ||
|
|
fbee8d5c20 | ||
|
|
855c322da6 | ||
|
|
240acca3de | ||
|
|
7ef1378304 | ||
|
|
9622ac7dff | ||
|
|
8a280b4a54 | ||
|
|
1ba9c71f54 | ||
|
|
5c6fbcaab0 | ||
|
|
b339cab3c1 | ||
|
|
a952df0953 | ||
|
|
6cebddebbe | ||
|
|
b3d340f59c | ||
|
|
78e2efc0f2 | ||
|
|
d21970efcc | ||
|
|
816039036d | ||
|
|
faf1a34f70 | ||
|
|
6986153c90 | ||
|
|
8048e0b32f | ||
|
|
af1cfd8139 | ||
|
|
f5c3804f79 | ||
|
|
443816365a | ||
|
|
097959d5cc | ||
|
|
bad6e12972 | ||
|
|
d85fcda037 | ||
|
|
dff91154a7 |
@@ -56,6 +56,7 @@ jobs:
|
||||
run: |
|
||||
make install_all
|
||||
pip install -e ".[test]"
|
||||
pip install pinecone pinecone-text
|
||||
if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
|
||||
- name: Run Formatting
|
||||
run: |
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"import anthropic\n",
|
||||
"\n",
|
||||
"# Set up environment variables\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # needed for embedding model\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # needed for embedding model\n",
|
||||
"os.environ[\"ANTHROPIC_API_KEY\"] = \"your_anthropic_api_key\""
|
||||
]
|
||||
},
|
||||
@@ -33,7 +33,7 @@
|
||||
" \"model\": \"claude-3-5-sonnet-latest\",\n",
|
||||
" \"temperature\": 0.1,\n",
|
||||
" \"max_tokens\": 2000,\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" self.client = anthropic.Client(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n",
|
||||
@@ -50,11 +50,7 @@
|
||||
" - Keep track of open issues and follow-ups\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def store_customer_interaction(self,\n",
|
||||
" user_id: str,\n",
|
||||
" message: str,\n",
|
||||
" response: str,\n",
|
||||
" metadata: Dict = None):\n",
|
||||
" def store_customer_interaction(self, user_id: str, message: str, response: str, metadata: Dict = None):\n",
|
||||
" \"\"\"Store customer interaction in memory.\"\"\"\n",
|
||||
" if metadata is None:\n",
|
||||
" metadata = {}\n",
|
||||
@@ -63,24 +59,17 @@
|
||||
" metadata[\"timestamp\"] = datetime.now().isoformat()\n",
|
||||
"\n",
|
||||
" # Format conversation for storage\n",
|
||||
" conversation = [\n",
|
||||
" {\"role\": \"user\", \"content\": message},\n",
|
||||
" {\"role\": \"assistant\", \"content\": response}\n",
|
||||
" ]\n",
|
||||
" conversation = [{\"role\": \"user\", \"content\": message}, {\"role\": \"assistant\", \"content\": response}]\n",
|
||||
"\n",
|
||||
" # Store in Mem0\n",
|
||||
" self.memory.add(\n",
|
||||
" conversation,\n",
|
||||
" user_id=user_id,\n",
|
||||
" metadata=metadata\n",
|
||||
" )\n",
|
||||
" self.memory.add(conversation, user_id=user_id, metadata=metadata)\n",
|
||||
"\n",
|
||||
" def get_relevant_history(self, user_id: str, query: str) -> List[Dict]:\n",
|
||||
" \"\"\"Retrieve relevant past interactions.\"\"\"\n",
|
||||
" return self.memory.search(\n",
|
||||
" query=query,\n",
|
||||
" user_id=user_id,\n",
|
||||
" limit=5 # Adjust based on needs\n",
|
||||
" limit=5, # Adjust based on needs\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" def handle_customer_query(self, user_id: str, query: str) -> str:\n",
|
||||
@@ -112,15 +101,12 @@
|
||||
" model=\"claude-3-5-sonnet-latest\",\n",
|
||||
" messages=[{\"role\": \"user\", \"content\": prompt}],\n",
|
||||
" max_tokens=2000,\n",
|
||||
" temperature=0.1\n",
|
||||
" temperature=0.1,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Store interaction\n",
|
||||
" self.store_customer_interaction(\n",
|
||||
" user_id=user_id,\n",
|
||||
" message=query,\n",
|
||||
" response=response,\n",
|
||||
" metadata={\"type\": \"support_query\"}\n",
|
||||
" user_id=user_id, message=query, response=response, metadata={\"type\": \"support_query\"}\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return response.content[0].text"
|
||||
@@ -203,12 +189,12 @@
|
||||
" # Get user input\n",
|
||||
" query = input()\n",
|
||||
" print(\"Customer:\", query)\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" # Check if user wants to exit\n",
|
||||
" if query.lower() == 'exit':\n",
|
||||
" if query.lower() == \"exit\":\n",
|
||||
" print(\"Thank you for using our support service. Goodbye!\")\n",
|
||||
" break\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" # Handle the query and print the response\n",
|
||||
" response = chatbot.handle_customer_query(user_id, query)\n",
|
||||
" print(\"Support:\", response, \"\\n\\n\")"
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"source": [
|
||||
"# Set up ENV Vars\n",
|
||||
"import os\n",
|
||||
"os.environ['OPENAI_API_KEY'] = \"sk-xxx\"\n"
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"sk-xxx\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -133,11 +134,9 @@
|
||||
"assistant_id = os.environ.get(\"ASSISTANT_ID\", None)\n",
|
||||
"\n",
|
||||
"# LLM Configuration\n",
|
||||
"CACHE_SEED = 42 # choose your poison\n",
|
||||
"CACHE_SEED = 42 # choose your poison\n",
|
||||
"llm_config = {\n",
|
||||
" \"config_list\": [\n",
|
||||
" {\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}\n",
|
||||
" ],\n",
|
||||
" \"config_list\": [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}],\n",
|
||||
" \"cache_seed\": CACHE_SEED,\n",
|
||||
" \"timeout\": 120,\n",
|
||||
" \"temperature\": 0.0,\n",
|
||||
@@ -348,7 +347,7 @@
|
||||
"source": [
|
||||
"# Retrieve the memory\n",
|
||||
"relevant_memories = MEM0_MEMORY_CLIENT.search(user_query, user_id=USER_ID, limit=3)\n",
|
||||
"relevant_memories_text = '\\n'.join(mem['memory'] for mem in relevant_memories)\n",
|
||||
"relevant_memories_text = \"\\n\".join(mem[\"memory\"] for mem in relevant_memories)\n",
|
||||
"print(\"Relevant memories:\")\n",
|
||||
"print(relevant_memories_text)\n",
|
||||
"\n",
|
||||
@@ -389,8 +388,8 @@
|
||||
"# - Enables more context-aware and personalized agent responses.\n",
|
||||
"# - Bridges the gap between human input and AI processing in complex workflows.\n",
|
||||
"\n",
|
||||
"class Mem0ProxyCoderAgent(UserProxyAgent):\n",
|
||||
"\n",
|
||||
"class Mem0ProxyCoderAgent(UserProxyAgent):\n",
|
||||
" def __init__(self, *args, **kwargs):\n",
|
||||
" super().__init__(*args, **kwargs)\n",
|
||||
" self.memory = MEM0_MEMORY_CLIENT\n",
|
||||
@@ -399,15 +398,14 @@
|
||||
" def initiate_chat(self, assistant, message):\n",
|
||||
" # Retrieve memory for the agent\n",
|
||||
" agent_memories = self.memory.search(message, agent_id=self.agent_id, limit=3)\n",
|
||||
" agent_memories_txt = '\\n'.join(mem['memory'] for mem in agent_memories)\n",
|
||||
" agent_memories_txt = \"\\n\".join(mem[\"memory\"] for mem in agent_memories)\n",
|
||||
" prompt = f\"{message}\\n Coding Preferences: \\n{str(agent_memories_txt)}\"\n",
|
||||
" response = super().initiate_chat(assistant, message=prompt)\n",
|
||||
" # Add new memory after processing the message\n",
|
||||
" response_dist = response.__dict__ if not isinstance(response, dict) else response\n",
|
||||
" MEMORY_DATA = [{\"role\": \"user\", \"content\": message}, {\"role\": \"assistant\", \"content\": response_dist}]\n",
|
||||
" self.memory.add(MEMORY_DATA, agent_id=self.agent_id)\n",
|
||||
" return response\n",
|
||||
" "
|
||||
" return response"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -560,12 +558,12 @@
|
||||
"from cookbooks.helper.mem0_teachability import Mem0Teachability\n",
|
||||
"\n",
|
||||
"teachability = Mem0Teachability(\n",
|
||||
" verbosity=2, # for visibility of what's happening\n",
|
||||
" recall_threshold=0.5,\n",
|
||||
" reset_db=False, # Use True to force-reset the memo DB, and False to use an existing DB.\n",
|
||||
" agent_id=AGENT_ID,\n",
|
||||
" memory_client = MEM0_MEMORY_CLIENT,\n",
|
||||
" )\n",
|
||||
" verbosity=2, # for visibility of what's happening\n",
|
||||
" recall_threshold=0.5,\n",
|
||||
" reset_db=False, # Use True to force-reset the memo DB, and False to use an existing DB.\n",
|
||||
" agent_id=AGENT_ID,\n",
|
||||
" memory_client=MEM0_MEMORY_CLIENT,\n",
|
||||
")\n",
|
||||
"teachability.add_to_agent(user_proxy)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ title: 'Get Memories (v2)'
|
||||
openapi: post /v2/memories/
|
||||
---
|
||||
|
||||
The v2 get memories API is powerful and flexible, allowing for more precise memory listing without the need for a search query. It supports complex logical operations (AND, OR) and comparison operators for advanced filtering capabilities. The comparison operators include:
|
||||
The v2 get memories API is powerful and flexible, allowing for more precise memory listing without the need for a search query. It supports complex logical operations (AND, OR, NOT) and comparison operators for advanced filtering capabilities. The comparison operators include:
|
||||
- `in`: Matches any of the values specified
|
||||
- `gte`: Greater than or equal to
|
||||
- `lte`: Less than or equal to
|
||||
|
||||
@@ -3,7 +3,7 @@ title: 'Search Memories (v2)'
|
||||
openapi: post /v2/memories/search/
|
||||
---
|
||||
|
||||
The v2 search API is powerful and flexible, allowing for more precise memory retrieval. It supports complex logical operations (AND, OR) and comparison operators for advanced filtering capabilities. The comparison operators include:
|
||||
The v2 search API is powerful and flexible, allowing for more precise memory retrieval. It supports complex logical operations (AND, OR, NOT) and comparison operators for advanced filtering capabilities. The comparison operators include:
|
||||
- `in`: Matches any of the values specified
|
||||
- `gte`: Greater than or equal to
|
||||
- `lte`: Less than or equal to
|
||||
|
||||
@@ -288,6 +288,26 @@ mode: "wide"
|
||||
|
||||
<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">
|
||||
**Improvements:**
|
||||
- **Client:** Improved error handling in client.
|
||||
@@ -664,6 +684,11 @@ mode: "wide"
|
||||
|
||||
<Tab title="Vercel AI SDK">
|
||||
|
||||
<Update label="2025-05-23" description="v1.0.5">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added support for Google provider.
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-10" description="v1.0.4">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added support for new param `output_format`.
|
||||
|
||||
@@ -110,6 +110,12 @@ Here's a comprehensive list of all parameters that can be used across different
|
||||
| `azure_kwargs` | Azure LLM args for initialization | AzureOpenAI |
|
||||
| `deepseek_base_url` | Base URL for DeepSeek API | DeepSeek |
|
||||
| `xai_base_url` | Base URL for XAI API | XAI |
|
||||
| `sarvam_base_url` | Base URL for Sarvam API | Sarvam |
|
||||
| `reasoning_effort` | Reasoning level (low, medium, high) | Sarvam |
|
||||
| `frequency_penalty` | Penalize frequent tokens (-2.0 to 2.0) | Sarvam |
|
||||
| `presence_penalty` | Penalize existing tokens (-2.0 to 2.0) | Sarvam |
|
||||
| `seed` | Seed for deterministic sampling | Sarvam |
|
||||
| `stop` | Stop sequences (max 4) | Sarvam |
|
||||
| `lmstudio_base_url` | Base URL for LM Studio API | LM Studio |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
|
||||
@@ -20,7 +20,7 @@ config = {
|
||||
"llm": {
|
||||
"provider": "anthropic",
|
||||
"config": {
|
||||
"model": "claude-3-7-sonnet-latest",
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"temperature": 0.1,
|
||||
"max_tokens": 2000,
|
||||
}
|
||||
@@ -45,7 +45,7 @@ const config = {
|
||||
provider: 'anthropic',
|
||||
config: {
|
||||
apiKey: process.env.ANTHROPIC_API_KEY || '',
|
||||
model: 'claude-3-7-sonnet-latest',
|
||||
model: 'claude-sonnet-4-20250514',
|
||||
temperature: 0.1,
|
||||
maxTokens: 2000,
|
||||
},
|
||||
|
||||
@@ -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).
|
||||
@@ -34,6 +34,7 @@ To view all supported llms, visit the [Supported LLMs](./models).
|
||||
<Card title="Gemini" href="/components/llms/models/gemini" />
|
||||
<Card title="DeepSeek" href="/components/llms/models/deepseek" />
|
||||
<Card title="xAI" href="/components/llms/models/xAI" />
|
||||
<Card title="XAI" href="/components/llms/models/sarvam" />
|
||||
<Card title="LM Studio" href="/components/llms/models/lmstudio" />
|
||||
<Card title="Langchain" href="/components/llms/models/langchain" />
|
||||
</CardGroup>
|
||||
|
||||
+24
-21
@@ -45,22 +45,22 @@
|
||||
"group": "Features",
|
||||
"icon": "star",
|
||||
"pages": [
|
||||
"features/platform-overview",
|
||||
"features/advanced-retrieval",
|
||||
"features/criteria-retrieval",
|
||||
"features/contextual-add",
|
||||
"features/multimodal-support",
|
||||
"features/timestamp",
|
||||
"features/selective-memory",
|
||||
"features/custom-categories",
|
||||
"features/custom-instructions",
|
||||
"features/direct-import",
|
||||
"features/async-client",
|
||||
"features/memory-export",
|
||||
"features/webhooks",
|
||||
"features/graph-memory",
|
||||
"features/feedback-mechanism",
|
||||
"features/expiration-date"
|
||||
"platform/features/platform-overview",
|
||||
"platform/features/advanced-retrieval",
|
||||
"platform/features/criteria-retrieval",
|
||||
"platform/features/contextual-add",
|
||||
"platform/features/multimodal-support",
|
||||
"platform/features/timestamp",
|
||||
"platform/features/selective-memory",
|
||||
"platform/features/custom-categories",
|
||||
"platform/features/custom-instructions",
|
||||
"platform/features/direct-import",
|
||||
"platform/features/async-client",
|
||||
"platform/features/memory-export",
|
||||
"platform/features/webhooks",
|
||||
"platform/features/graph-memory",
|
||||
"platform/features/feedback-mechanism",
|
||||
"platform/features/expiration-date"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -77,10 +77,10 @@
|
||||
"icon": "wrench",
|
||||
"pages": [
|
||||
"open-source/features/async-memory",
|
||||
"features/openai_compatibility",
|
||||
"features/custom-fact-extraction-prompt",
|
||||
"features/custom-update-memory-prompt",
|
||||
"open-source/multimodal-support",
|
||||
"open-source/features/openai_compatibility",
|
||||
"open-source/features/custom-fact-extraction-prompt",
|
||||
"open-source/features/custom-update-memory-prompt",
|
||||
"open-source/features/multimodal-support",
|
||||
"open-source/features/rest-api"
|
||||
]
|
||||
},
|
||||
@@ -115,6 +115,7 @@
|
||||
"components/llms/models/gemini",
|
||||
"components/llms/models/deepseek",
|
||||
"components/llms/models/xAI",
|
||||
"components/llms/models/sarvam",
|
||||
"components/llms/models/lmstudio",
|
||||
"components/llms/models/langchain"
|
||||
]
|
||||
@@ -248,7 +249,9 @@
|
||||
"integrations/elevenlabs",
|
||||
"integrations/pipecat",
|
||||
"integrations/agno",
|
||||
"integrations/keywords"
|
||||
"integrations/keywords",
|
||||
"integrations/raycast",
|
||||
"integrations/mastra"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -322,4 +322,60 @@ Here are the available integrations for Mem0:
|
||||
>
|
||||
Build AI applications with persistent memory and comprehensive LLM observability.
|
||||
</Card>
|
||||
<Card
|
||||
title="Raycast"
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M3 12L21 12M12 3L12 21M7.5 7.5L16.5 16.5M16.5 7.5L7.5 16.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
href="/integrations/raycast"
|
||||
>
|
||||
Mem0 Raycast extension for intelligent memory management and retrieval.
|
||||
</Card>
|
||||
<Card
|
||||
title="Mastra"
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M12 2L22 7L12 12L2 7L12 2Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2 17L12 22L22 17"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2 12L12 17L22 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
href="/integrations/mastra"
|
||||
>
|
||||
Build AI agents with persistent memory using Mastra's framework and tools.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -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" />
|
||||
@@ -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
@@ -37,13 +37,13 @@
|
||||
|
||||
## Features
|
||||
|
||||
[Graph Memory](https://docs.mem0.ai/features/graph-memory): Mem0's graph memory system builds relationships between entities in your data, enabling contextually relevant retrieval by analyzing connections between information points - activate it with `enable_graph=True` to enhance search results beyond direct semantic matches, ideal for applications tracking evolving relationships.
|
||||
[Advanced Retrieval](https://docs.mem0.ai/features/advanced-retrieval): Mem0 offers enhanced search capabilities through three advanced retrieval modes: keyword search (improves recall by matching specific terms), reranking (ensures most relevant results appear first using neural networks), and filtering (narrows results by specific criteria) - each can be enabled independently or in combination to optimize search precision and relevance.
|
||||
[Multimodal Support](https://docs.mem0.ai/features/multimodal-support): Mem0 extends beyond text by supporting images and documents (JPG, PNG, MDX, TXT, PDF), allowing users to integrate visual and document content through direct URLs or Base64 encoding, enhancing the memory system's ability to understand and recall information from various media types.
|
||||
[Memory Customization](https://docs.mem0.ai/features/selective-memory): Mem0 enables selective memory storage through inclusion and exclusion rules, allowing users to focus on relevant information (like specific topics) while omitting irrelevant data (such as food preferences), resulting in more efficient, accurate, and privacy-conscious AI interactions.
|
||||
[Custom Categories](https://docs.mem0.ai/features/custom-categories): Mem0 allows setting custom categories at both project level and during individual API calls, overriding default categories (like personal_details, family, sports) with more specific ones to improve memory categorization accuracy - simply provide a list of category dictionaries with descriptive definitions when adding memories.
|
||||
[Async Client](https://docs.mem0.ai/features/async-client): Mem0 provides an AsyncMemoryClient for non-blocking operations, offering the same functionality as the synchronous client (add, search, get_all, delete, etc.) but with async/await support, making it ideal for high-concurrency applications that need to perform memory operations without blocking execution.
|
||||
[Memory Export](https://docs.mem0.ai/features/memory-export): Mem0 enables exporting memories in structured formats using customizable Pydantic schemas, allowing you to transform stored memories into specific data structures by defining schemas, submitting export jobs with optional processing instructions, and retrieving the formatted data with various filtering options.
|
||||
[Graph Memory](https://docs.mem0.ai/platform/features/graph-memory): Mem0's graph memory system builds relationships between entities in your data, enabling contextually relevant retrieval by analyzing connections between information points - activate it with `enable_graph=True` to enhance search results beyond direct semantic matches, ideal for applications tracking evolving relationships.
|
||||
[Advanced Retrieval](https://docs.mem0.ai/platform/features/advanced-retrieval): Mem0 offers enhanced search capabilities through three advanced retrieval modes: keyword search (improves recall by matching specific terms), reranking (ensures most relevant results appear first using neural networks), and filtering (narrows results by specific criteria) - each can be enabled independently or in combination to optimize search precision and relevance.
|
||||
[Multimodal Support](https://docs.mem0.ai/platform/features/multimodal-support): Mem0 extends beyond text by supporting images and documents (JPG, PNG, MDX, TXT, PDF), allowing users to integrate visual and document content through direct URLs or Base64 encoding, enhancing the memory system's ability to understand and recall information from various media types.
|
||||
[Memory Customization](https://docs.mem0.ai/platform/features/selective-memory): Mem0 enables selective memory storage through inclusion and exclusion rules, allowing users to focus on relevant information (like specific topics) while omitting irrelevant data (such as food preferences), resulting in more efficient, accurate, and privacy-conscious AI interactions.
|
||||
[Custom Categories](https://docs.mem0.ai/platform/features/custom-categories): Mem0 allows setting custom categories at both project level and during individual API calls, overriding default categories (like personal_details, family, sports) with more specific ones to improve memory categorization accuracy - simply provide a list of category dictionaries with descriptive definitions when adding memories.
|
||||
[Async Client](https://docs.mem0.ai/platform/features/async-client): Mem0 provides an AsyncMemoryClient for non-blocking operations, offering the same functionality as the synchronous client (add, search, get_all, delete, etc.) but with async/await support, making it ideal for high-concurrency applications that need to perform memory operations without blocking execution.
|
||||
[Memory Export](https://docs.mem0.ai/platform/features/memory-export): Mem0 enables exporting memories in structured formats using customizable Pydantic schemas, allowing you to transform stored memories into specific data structures by defining schemas, submitting export jobs with optional processing instructions, and retrieving the formatted data with various filtering options.
|
||||
|
||||
## OSS
|
||||
|
||||
@@ -72,9 +72,9 @@
|
||||
|
||||
### Features
|
||||
|
||||
[OpenAI Compatibility](https://docs.mem0.ai/features/openai_compatibility): Mem0 offers seamless integration with OpenAI-compatible APIs, allowing developers to enhance conversational agents with structured memory by initializing with a Mem0 API key (or locally without one), supporting various LLM providers, and enabling personalized responses through user context persistence across interactions with parameters like user_id, agent_id, and custom filters.
|
||||
[Custom Fact Extraction Prompt](https://docs.mem0.ai/features/custom-fact-extraction-prompt): Mem0 enables custom fact extraction prompts to tailor information extraction for specific use cases by defining domain-specific examples and formats, allowing precise control over what information is extracted from messages - simply provide a custom prompt with few-shot examples in the config when initializing the Memory client.
|
||||
[Custom Update Memory Prompt](https://docs.mem0.ai/features/custom-update-memory-prompt): Mem0 enables customizing the update memory prompt to control how memories are modified by comparing newly retrieved facts with existing memories and determining appropriate actions (add, update, delete, or no change) based on custom logic and examples provided in the prompt configuration.
|
||||
[OpenAI Compatibility](https://docs.mem0.ai/open-source/features/openai_compatibility): Mem0 offers seamless integration with OpenAI-compatible APIs, allowing developers to enhance conversational agents with structured memory by initializing with a Mem0 API key (or locally without one), supporting various LLM providers, and enabling personalized responses through user context persistence across interactions with parameters like user_id, agent_id, and custom filters.
|
||||
[Custom Fact Extraction Prompt](https://docs.mem0.ai/open-source/features/custom-fact-extraction-prompt): Mem0 enables custom fact extraction prompts to tailor information extraction for specific use cases by defining domain-specific examples and formats, allowing precise control over what information is extracted from messages - simply provide a custom prompt with few-shot examples in the config when initializing the Memory client.
|
||||
[Custom Update Memory Prompt](https://docs.mem0.ai/open-source/features/custom-update-memory-prompt): Mem0 enables customizing the update memory prompt to control how memories are modified by comparing newly retrieved facts with existing memories and determining appropriate actions (add, update, delete, or no change) based on custom logic and examples provided in the prompt configuration.
|
||||
[REST API Server](https://docs.mem0.ai/open-source/features/rest-api): Mem0 provides a FastAPI-based REST API server that supports core operations (create/retrieve/search/update/delete memories) with OpenAPI documentation at /docs, easily deployable via Docker Compose with pre-configured databases (postgres pgvector, neo4j) - just set OPENAI_API_KEY to get started.
|
||||
[Graph Memory](https://docs.mem0.ai/open-source/graph_memory/overview): Mem0's open-source graph memory system enables building and querying relationships between entities by installing with `pip install "mem0ai[graph]"` and configuring a graph store provider (like Neo4j) - this allows for more contextual memory retrieval by combining vector and graph-based approaches to track evolving relationships between information points.
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Multimodal Support
|
||||
description: Integrate images into your interactions with Mem0
|
||||
icon: "image"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
<Snippet file="paper-release.mdx" />
|
||||
|
||||
Mem0 extends its capabilities beyond text by supporting multimodal data. With this feature, users can seamlessly integrate images into their interactions—allowing Mem0 to extract relevant information.
|
||||
|
||||
## How It Works
|
||||
|
||||
When a user submits an image, Mem0 processes it to extract textual information and other pertinent details. These details are then added to the user's memory, enhancing the system's ability to understand and recall multimodal inputs.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
client = Memory()
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hi, my name is Alice."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Nice to meet you, Alice! What do you like to eat?"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "https://www.superhealthykids.com/wp-content/uploads/2021/10/best-veggie-pizza-featured-image-square-2.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
# Calling the add method to ingest messages into the memory system
|
||||
client.add(messages, user_id="alice")
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"memory": "Name is Alice",
|
||||
"event": "ADD",
|
||||
"id": "7ae113a3-3cb5-46e9-b6f7-486c36391847"
|
||||
},
|
||||
{
|
||||
"memory": "Likes large pizza with toppings including cherry tomatoes, black olives, green spinach, yellow bell peppers, diced ham, and sliced mushrooms",
|
||||
"event": "ADD",
|
||||
"id": "56545065-7dee-4acf-8bf2-a5b2535aabb3"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Using these methods, you can seamlessly incorporate various media types into your interactions, further enhancing Mem0's multimodal capabilities.
|
||||
|
||||
If you have any questions, please feel free to reach out to us using one of the following methods:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
@@ -10,13 +10,12 @@ iconType: "solid"
|
||||
Graph Memory is a powerful feature that allows users to create and utilize complex relationships between pieces of information.
|
||||
|
||||
## Graph Memory supports the following features:
|
||||
A list of features provided by Graph Memory.
|
||||
|
||||
### Add Customize Prompt
|
||||
### Using Custom Prompts
|
||||
|
||||
Users can add a customized prompt that will be used to extract specific entities from the given input text.
|
||||
This allows for more targeted and relevant information extraction based on the user's needs.
|
||||
Here's an example of how to add a customized prompt:
|
||||
Users can specify a custom prompt that will be used to extract specific entities from the given input text.
|
||||
This allows for more targeted and relevant information extraction based on the user's needs.
|
||||
Here's an example of how to specify a custom prompt:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
|
||||
+57
-47
@@ -2316,6 +2316,11 @@
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Organization created successfully."
|
||||
},
|
||||
"org_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Unique identifier for the organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2722,13 +2727,13 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"email",
|
||||
"role"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Username of the member whose role is to be updated"
|
||||
"description": "Email of the member whose role is to be updated"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
@@ -2798,27 +2803,27 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2847,13 +2852,13 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"email",
|
||||
"role"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Username of the member to be added"
|
||||
"description": "Email of the member to be added"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
@@ -2923,23 +2928,23 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
@@ -2971,12 +2976,12 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username"
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Username of the member to be removed"
|
||||
"description": "Email of the member to be removed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3020,27 +3025,27 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\"username\": \"<string>\"}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"DELETE\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\npayload = {\"email\": \"<string>\"}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"DELETE\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "const options = {\n method: 'DELETE',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
"source": "const options = {\n method: 'DELETE',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request DELETE \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\"\n}'"
|
||||
"source": "curl --request DELETE \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"DELETE\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"DELETE\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"DELETE\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"DELETE\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "HttpResponse<String> response = Unirest.delete(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
"source": "HttpResponse<String> response = Unirest.delete(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3199,6 +3204,11 @@
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Project created successfully."
|
||||
},
|
||||
"project_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Unique identifier for the project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3753,13 +3763,13 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"email",
|
||||
"role"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Username of the member to be added"
|
||||
"description": "Email of the member to be added"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
@@ -3823,27 +3833,27 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"POST\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
"source": "const options = {\n method: 'POST',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
"source": "curl --request POST \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "HttpResponse<String> response = Unirest.post(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
"source": "HttpResponse<String> response = Unirest.post(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3881,13 +3891,13 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"email",
|
||||
"role"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Username of the member to be updated"
|
||||
"description": "Email of the member to be updated"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
@@ -3951,27 +3961,27 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
"source": "import requests\n\nurl = \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\npayload = {\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}\nheaders = {\n \"Authorization\": \"<api-key>\",\n \"Content-Type\": \"application/json\"\n}\n\nresponse = requests.request(\"PUT\", url, json=payload, headers=headers)\n\nprint(response.text)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"username\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
"source": "const options = {\n method: 'PUT',\n headers: {Authorization: 'Token <api-key>', 'Content-Type': 'application/json'},\n body: '{\"email\":\"<string>\",\"role\":\"<string>\"}'\n};\n\nfetch('https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/', options)\n .then(response => response.json())\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"username\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
"source": "curl --request PUT \\\n --url https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/ \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"email\": \"<string>\",\n \"role\": \"<string>\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\"\n\n\tpayload := strings.NewReader(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"PUT\",\n CURLOPT_POSTFIELDS => \"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"username\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
"source": "HttpResponse<String> response = Unirest.put(\"https://api.mem0.ai/api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(\"{\n \\\"email\\\": \\\"<string>\\\",\n \\\"role\\\": \\\"<string>\\\"\n}\")\n .asString();"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3999,10 +4009,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"name": "email",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"description": "Username of the member to be removed",
|
||||
"description": "Email of the member to be removed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -4809,7 +4819,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"messages": {
|
||||
"description": "An array of message objects representing the content of the memory. Each message object typically contains 'role' and 'content' fields, where 'role' indicates the sender (e.g., 'user', 'assistant', 'system') and 'content' contains the actual message text. This structure allows for the representation of conversations or multi-part memories.",
|
||||
"description": "An array of message objects representing the content of the memory. Each message object typically contains 'role' and 'content' fields, where 'role' indicates the sender either 'user' or 'assistant' and 'content' contains the actual message text. This structure allows for the representation of conversations or multi-part memories.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
@@ -95,9 +95,8 @@ This is just the beginning. The MCP server is the first core layer in the OpenMe
|
||||
|
||||
## Getting Started Today
|
||||
|
||||
- Github Repository: https://github.com/mem0ai/mem0
|
||||
- Read the documentation: [Docs Link]
|
||||
- Join our community: [Discord link]
|
||||
- Repository: [GitHub](https://github.com/mem0ai/mem0/tree/main/openmemory)
|
||||
- Join our community: [Discord](https://discord.gg/6PzXDgEjG5)
|
||||
|
||||
With OpenMemory, your AI memories stay private, portable, and under your control, exactly where they belong.
|
||||
|
||||
|
||||
@@ -66,4 +66,4 @@ Once set up, OpenMemory runs locally on your machine, ensuring all your AI memor
|
||||
|
||||
### Getting Started Today
|
||||
|
||||
- Github Repository: https://github.com/mem0ai/mem0/openmemory
|
||||
- Github Repository: https://github.com/mem0ai/mem0/tree/main/openmemory
|
||||
|
||||
@@ -5,6 +5,10 @@ iconType: "solid"
|
||||
---
|
||||
|
||||
<Snippet file="paper-release.mdx" />
|
||||
<Note type="info">
|
||||
🎉 We're excited to announce that Claude 4 is now available with Mem0! Check it out [here](components/llms/models/anthropic).
|
||||
</Note>
|
||||
|
||||
|
||||
# Introduction
|
||||
|
||||
|
||||
@@ -281,6 +281,67 @@ console.log(memories);
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Setting Graph Memory at Project Level
|
||||
|
||||
Instead of passing `enable_graph=True` to every add call, you can enable it once at the project level:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(
|
||||
api_key="your-api-key",
|
||||
org_id="your-org-id",
|
||||
project_id="your-project-id"
|
||||
)
|
||||
|
||||
# Enable graph memory for all operations in this project
|
||||
client.update_project(enable_graph=True, version="v1")
|
||||
|
||||
# Now all add operations will use graph memory by default
|
||||
messages = [
|
||||
{"role": "user", "content": "My name is Joseph"},
|
||||
{"role": "assistant", "content": "Hello Joseph, it's nice to meet you!"},
|
||||
{"role": "user", "content": "I'm from Seattle and I work as a software engineer"}
|
||||
]
|
||||
|
||||
client.add(
|
||||
messages,
|
||||
user_id="joseph",
|
||||
output_format="v1.1"
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import { MemoryClient } from "mem0";
|
||||
|
||||
const client = new MemoryClient({
|
||||
apiKey: "your-api-key",
|
||||
org_id: "your-org-id",
|
||||
project_id: "your-project-id"
|
||||
});
|
||||
|
||||
# Enable graph memory for all operations in this project
|
||||
await client.updateProject({ enable_graph: true, version: "v1" });
|
||||
|
||||
# Now all add operations will use graph memory by default
|
||||
const messages = [
|
||||
{ role: "user", content: "My name is Joseph" },
|
||||
{ role: "assistant", content: "Hello Joseph, it's nice to meet you!" },
|
||||
{ role: "user", content: "I'm from Seattle and I work as a software engineer" }
|
||||
];
|
||||
|
||||
await client.add({
|
||||
messages,
|
||||
user_id: "joseph",
|
||||
output_format: "v1.1"
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Enable Graph Memory for applications where understanding context and relationships between memories is important
|
||||
@@ -131,12 +131,14 @@ curl -X POST "https://api.mem0.ai/v1/memories/export/" \
|
||||
|
||||
### Retrieve Export
|
||||
|
||||
Once the export job is complete, you can retrieve the structured data:
|
||||
Once the export job is complete, you can retrieve the structured data in two ways:
|
||||
|
||||
#### Using Filters
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Corrected date range (assuming you meant July 10 to July 20)
|
||||
# Retrieve using filters
|
||||
filters = {
|
||||
"AND": [
|
||||
{"created_at": {"gte": "2024-07-10", "lte": "2024-07-20"}},
|
||||
@@ -148,9 +150,27 @@ response = client.get_memory_export(filters=filters)
|
||||
print(response)
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X GET "https://api.mem0.ai/v1/memories/export/?user_id=alice" \
|
||||
-H "Authorization: Token your-api-key"
|
||||
```json Output
|
||||
{
|
||||
"full_name": "John Doe",
|
||||
"current_role": "Senior Software Engineer",
|
||||
"years_experience": 8,
|
||||
"employment_status": "full_time",
|
||||
"education_level": "masters",
|
||||
"skills": ["Python", "AWS", "Machine Learning"]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
#### Using Export ID
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
# Retrieve using export ID
|
||||
response = client.get_memory_export(memory_export_id="550e8400-e29b-41d4-a716-446655440000")
|
||||
print(response)
|
||||
```
|
||||
|
||||
```json Output
|
||||
@@ -59,7 +59,10 @@ five_days_ago = current_time - timedelta(days=5)
|
||||
unix_timestamp = int(five_days_ago.timestamp())
|
||||
|
||||
# Add memory with custom timestamp
|
||||
client.add("I'm travelling to SF", user_id="user1", timestamp=unix_timestamp)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
client.add(messages, user_id="user1", timestamp=unix_timestamp)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -77,7 +80,10 @@ fiveDaysAgo.setDate(currentTime.getDate() - 5);
|
||||
const unixTimestamp = Math.floor(fiveDaysAgo.getTime() / 1000);
|
||||
|
||||
// Add memory with custom timestamp
|
||||
client.add("I'm travelling to SF", { user_id: "user1", timestamp: unixTimestamp })
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
client.add(messages, { user_id: "user1", timestamp: unixTimestamp })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -119,14 +125,20 @@ For example, to create a memory with a timestamp of January 1, 2023:
|
||||
# January 1, 2023 timestamp
|
||||
january_2023_timestamp = 1672531200 # Unix timestamp for 2023-01-01 00:00:00 UTC
|
||||
|
||||
client.add("Important historical information", user_id="user1", timestamp=january_2023_timestamp)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
client.add(messages, user_id="user1", timestamp=january_2023_timestamp)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// January 1, 2023 timestamp
|
||||
const january2023Timestamp = 1672531200; // Unix timestamp for 2023-01-01 00:00:00 UTC
|
||||
|
||||
client.add("Important historical information", { user_id: "user1", timestamp: january2023Timestamp })
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
client.add(messages, { user_id: "user1", timestamp: january2023Timestamp })
|
||||
.then(response => console.log(response))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
@@ -64,7 +64,10 @@ client = AsyncMemoryClient()
|
||||
|
||||
|
||||
async def main():
|
||||
response = await client.add("I'm travelling to SF", user_id="john")
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm travelling to SF"}
|
||||
]
|
||||
response = await client.add(messages, user_id="john")
|
||||
print(response)
|
||||
|
||||
await main()
|
||||
@@ -139,7 +142,20 @@ curl -X POST "https://api.mem0.ai/v1/memories/" \
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
Messages passed along with `user_id`, `run_id`, or `app_id` are stored as user memories, while messages from the assistant are excluded from memory. To store messages for the assistant, use `agent_id` exclusively and avoid including other IDs, such as user_id, alongside it. This ensures the memory is properly attributed to the assistant.
|
||||
When passing `user_id`, memories are primarily created based on user messages, but may be influenced by assistant messages for contextual understanding. For example, in a conversation about food preferences, both the user's stated preferences and their responses to the assistant's questions would form user memories. Similarly, when using `agent_id`, assistant messages are prioritized, but user messages might influence the agent's memories based on context. This approach ensures comprehensive memory creation while maintaining appropriate attribution to either users or agents.
|
||||
|
||||
**Example:**
|
||||
```
|
||||
User: My favorite cuisine is Italian
|
||||
Assistant: Nice! What about Indian cuisine?
|
||||
User: Don't like it much since I cannot eat spicy food
|
||||
|
||||
Resulting user memories:
|
||||
memory1 - Likes Italian food
|
||||
memory2 - Doesn't like Indian food since cannot eat spicy
|
||||
|
||||
(memory2 comes from user's response about Indian cuisine)
|
||||
```
|
||||
</Note>
|
||||
|
||||
<Note>Metadata allows you to store structured information (location, timestamp, user state) with memories. Add it during creation to enable precise filtering and retrieval during searches.</Note>
|
||||
@@ -678,6 +694,77 @@ curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Example 4: Search using NOT filters
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
query = "What do you know about me?"
|
||||
filters = {
|
||||
"NOT": [
|
||||
{
|
||||
"categories": {
|
||||
"contains": "food_preferences"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
client.search(query, version="v2", filters=filters)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
const query = "What do you know about me?";
|
||||
const filters = {
|
||||
"NOT": [
|
||||
{
|
||||
"categories": {
|
||||
"contains": "food_preferences"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
client.search(query, { version: "v2", filters })
|
||||
.then(results => console.log(results))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "https://api.mem0.ai/v1/memories/search/?version=v2" \
|
||||
-H "Authorization: Token your-api-key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "What do you know about me?",
|
||||
"filters": {
|
||||
"NOT": [
|
||||
{
|
||||
"categories": {
|
||||
"contains": "food_preferences"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "123abc-d456-7890-efgh-ijklmnopqrst",
|
||||
"memory": "Lives in San Francisco",
|
||||
"user_id": "alex",
|
||||
"metadata": null,
|
||||
"categories": ["location"],
|
||||
"immutable": false,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-20T01:30:36.275141-07:00",
|
||||
"updated_at": "2024-07-20T01:30:36.275172-07:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
### 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>
|
||||
|
||||
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
|
||||
|
||||
@@ -1547,11 +1749,17 @@ Fun fact: You can also delete the memory using the `add()` method by passing a n
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
client.add("Delete all of my food preferences", user_id="alex")
|
||||
messages = [
|
||||
{"role": "user", "content": "Delete all of my food preferences"}
|
||||
]
|
||||
client.add(messages, user_id="alex")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
client.add("Delete all of my food preferences", { user_id: "alex" })
|
||||
const messages = [
|
||||
{"role": "user", "content": "Delete all of my food preferences"}
|
||||
]
|
||||
client.add(messages, { user_id: "alex" })
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
|
||||
@@ -5,6 +5,9 @@ iconType: "solid"
|
||||
---
|
||||
|
||||
<Snippet file="paper-release.mdx" />
|
||||
<Note type="info">
|
||||
🎉 We're excited to announce that Claude 4 is now available with Mem0! Check it out [here](components/llms/models/anthropic).
|
||||
</Note>
|
||||
|
||||
|
||||
Mem0 offers two powerful ways to leverage our technology: [our managed platform](#mem0-platform-managed-solution) and [our open source solution](#mem0-open-source).
|
||||
|
||||
+28
-29
@@ -14,46 +14,47 @@ def process_item(item_data):
|
||||
local_results = defaultdict(list)
|
||||
|
||||
for item in v:
|
||||
gt_answer = str(item['answer'])
|
||||
pred_answer = str(item['response'])
|
||||
category = str(item['category'])
|
||||
question = str(item['question'])
|
||||
gt_answer = str(item["answer"])
|
||||
pred_answer = str(item["response"])
|
||||
category = str(item["category"])
|
||||
question = str(item["question"])
|
||||
|
||||
# Skip category 5
|
||||
if category == '5':
|
||||
if category == "5":
|
||||
continue
|
||||
|
||||
metrics = calculate_metrics(pred_answer, gt_answer)
|
||||
bleu_scores = calculate_bleu_scores(pred_answer, gt_answer)
|
||||
llm_score = evaluate_llm_judge(question, gt_answer, pred_answer)
|
||||
|
||||
local_results[k].append({
|
||||
"question": question,
|
||||
"answer": gt_answer,
|
||||
"response": pred_answer,
|
||||
"category": category,
|
||||
"bleu_score": bleu_scores["bleu1"],
|
||||
"f1_score": metrics["f1"],
|
||||
"llm_score": llm_score
|
||||
})
|
||||
local_results[k].append(
|
||||
{
|
||||
"question": question,
|
||||
"answer": gt_answer,
|
||||
"response": pred_answer,
|
||||
"category": category,
|
||||
"bleu_score": bleu_scores["bleu1"],
|
||||
"f1_score": metrics["f1"],
|
||||
"llm_score": llm_score,
|
||||
}
|
||||
)
|
||||
|
||||
return local_results
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Evaluate RAG results')
|
||||
parser.add_argument('--input_file', type=str,
|
||||
default="results/rag_results_500_k1.json",
|
||||
help='Path to the input dataset file')
|
||||
parser.add_argument('--output_file', type=str,
|
||||
default="evaluation_metrics.json",
|
||||
help='Path to save the evaluation results')
|
||||
parser.add_argument('--max_workers', type=int, default=10,
|
||||
help='Maximum number of worker threads')
|
||||
parser = argparse.ArgumentParser(description="Evaluate RAG results")
|
||||
parser.add_argument(
|
||||
"--input_file", type=str, default="results/rag_results_500_k1.json", help="Path to the input dataset file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output_file", type=str, default="evaluation_metrics.json", help="Path to save the evaluation results"
|
||||
)
|
||||
parser.add_argument("--max_workers", type=int, default=10, help="Maximum number of worker threads")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.input_file, 'r') as f:
|
||||
with open(args.input_file, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
results = defaultdict(list)
|
||||
@@ -61,18 +62,16 @@ def main():
|
||||
|
||||
# Use ThreadPoolExecutor with specified workers
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=args.max_workers) as executor:
|
||||
futures = [executor.submit(process_item, item_data)
|
||||
for item_data in data.items()]
|
||||
futures = [executor.submit(process_item, item_data) for item_data in data.items()]
|
||||
|
||||
for future in tqdm(concurrent.futures.as_completed(futures),
|
||||
total=len(futures)):
|
||||
for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures)):
|
||||
local_results = future.result()
|
||||
with results_lock:
|
||||
for k, items in local_results.items():
|
||||
results[k].extend(items)
|
||||
|
||||
# Save results to JSON file
|
||||
with open(args.output_file, 'w') as f:
|
||||
with open(args.output_file, "w") as f:
|
||||
json.dump(results, f, indent=4)
|
||||
|
||||
print(f"Results saved to {args.output_file}")
|
||||
|
||||
@@ -3,7 +3,7 @@ import json
|
||||
import pandas as pd
|
||||
|
||||
# Load the evaluation metrics data
|
||||
with open('evaluation_metrics.json', 'r') as f:
|
||||
with open("evaluation_metrics.json", "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Flatten the data into a list of question items
|
||||
@@ -15,28 +15,20 @@ for key in data:
|
||||
df = pd.DataFrame(all_items)
|
||||
|
||||
# Convert category to numeric type
|
||||
df['category'] = pd.to_numeric(df['category'])
|
||||
df["category"] = pd.to_numeric(df["category"])
|
||||
|
||||
# Calculate mean scores by category
|
||||
result = df.groupby('category').agg({
|
||||
'bleu_score': 'mean',
|
||||
'f1_score': 'mean',
|
||||
'llm_score': 'mean'
|
||||
}).round(4)
|
||||
result = df.groupby("category").agg({"bleu_score": "mean", "f1_score": "mean", "llm_score": "mean"}).round(4)
|
||||
|
||||
# Add count of questions per category
|
||||
result['count'] = df.groupby('category').size()
|
||||
result["count"] = df.groupby("category").size()
|
||||
|
||||
# Print the results
|
||||
print("Mean Scores Per Category:")
|
||||
print(result)
|
||||
|
||||
# Calculate overall means
|
||||
overall_means = df.agg({
|
||||
'bleu_score': 'mean',
|
||||
'f1_score': 'mean',
|
||||
'llm_score': 'mean'
|
||||
}).round(4)
|
||||
overall_means = df.agg({"bleu_score": "mean", "f1_score": "mean", "llm_score": "mean"}).round(4)
|
||||
|
||||
print("\nOverall Mean Scores:")
|
||||
print(overall_means)
|
||||
print(overall_means)
|
||||
|
||||
@@ -33,35 +33,34 @@ Do NOT include both CORRECT and WRONG in your response, or it will break the eva
|
||||
Just return the label CORRECT or WRONG in a json format with the key as "label".
|
||||
"""
|
||||
|
||||
|
||||
def evaluate_llm_judge(question, gold_answer, generated_answer):
|
||||
"""Evaluate the generated answer against the gold answer using an LLM judge."""
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": ACCURACY_PROMPT.format(
|
||||
question=question,
|
||||
gold_answer=gold_answer,
|
||||
generated_answer=generated_answer
|
||||
)
|
||||
}],
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": ACCURACY_PROMPT.format(
|
||||
question=question, gold_answer=gold_answer, generated_answer=generated_answer
|
||||
),
|
||||
}
|
||||
],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.0
|
||||
temperature=0.0,
|
||||
)
|
||||
label = json.loads(response.choices[0].message.content)['label']
|
||||
label = json.loads(response.choices[0].message.content)["label"]
|
||||
return 1 if label == "CORRECT" else 0
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to evaluate RAG results using LLM judge."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Evaluate RAG results using LLM judge'
|
||||
)
|
||||
parser = argparse.ArgumentParser(description="Evaluate RAG results using LLM judge")
|
||||
parser.add_argument(
|
||||
'--input_file',
|
||||
"--input_file",
|
||||
type=str,
|
||||
default="results/default_run_v4_k30_new_graph.json",
|
||||
help='Path to the input dataset file'
|
||||
help="Path to the input dataset file",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -78,10 +77,10 @@ def main():
|
||||
index = 0
|
||||
for k, v in data.items():
|
||||
for x in v:
|
||||
question = x['question']
|
||||
gold_answer = x['answer']
|
||||
generated_answer = x['response']
|
||||
category = x['category']
|
||||
question = x["question"]
|
||||
gold_answer = x["answer"]
|
||||
generated_answer = x["response"]
|
||||
category = x["category"]
|
||||
|
||||
# Skip category 5
|
||||
if int(category) == 5:
|
||||
@@ -92,13 +91,15 @@ def main():
|
||||
LLM_JUDGE[category].append(label)
|
||||
|
||||
# Store the results
|
||||
RESULTS[index].append({
|
||||
"question": question,
|
||||
"gt_answer": gold_answer,
|
||||
"response": generated_answer,
|
||||
"category": category,
|
||||
"llm_label": label
|
||||
})
|
||||
RESULTS[index].append(
|
||||
{
|
||||
"question": question,
|
||||
"gt_answer": gold_answer,
|
||||
"response": generated_answer,
|
||||
"category": category,
|
||||
"llm_label": label,
|
||||
}
|
||||
)
|
||||
|
||||
# Save intermediate results
|
||||
with open(output_path, "w") as f:
|
||||
@@ -108,8 +109,7 @@ def main():
|
||||
print("All categories accuracy:")
|
||||
for cat, results in LLM_JUDGE.items():
|
||||
if results: # Only print if there are results for this category
|
||||
print(f" Category {cat}: {np.mean(results):.4f} "
|
||||
f"({sum(results)}/{len(results)})")
|
||||
print(f" Category {cat}: {np.mean(results):.4f} " f"({sum(results)}/{len(results)})")
|
||||
print("------------------------------------------")
|
||||
index += 1
|
||||
|
||||
|
||||
+54
-54
@@ -3,7 +3,7 @@ Borrowed from https://github.com/WujiangXu/AgenticMemory/blob/main/utils.py
|
||||
|
||||
@article{xu2025mem,
|
||||
title={A-mem: Agentic memory for llm agents},
|
||||
author={Xu, Wujiang and Liang, Zujie and Mei, Kai and Gao, Hang and Tan, Juntao
|
||||
author={Xu, Wujiang and Liang, Zujie and Mei, Kai and Gao, Hang and Tan, Juntao
|
||||
and Zhang, Yongfeng},
|
||||
journal={arXiv preprint arXiv:2502.12110},
|
||||
year={2025}
|
||||
@@ -26,42 +26,45 @@ from sentence_transformers.util import pytorch_cos_sim
|
||||
|
||||
# Download required NLTK data
|
||||
try:
|
||||
nltk.download('punkt', quiet=True)
|
||||
nltk.download('wordnet', quiet=True)
|
||||
nltk.download("punkt", quiet=True)
|
||||
nltk.download("wordnet", quiet=True)
|
||||
except Exception as e:
|
||||
print(f"Error downloading NLTK data: {e}")
|
||||
|
||||
# Initialize SentenceTransformer model (this will be reused)
|
||||
try:
|
||||
sentence_model = SentenceTransformer('all-MiniLM-L6-v2')
|
||||
sentence_model = SentenceTransformer("all-MiniLM-L6-v2")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load SentenceTransformer model: {e}")
|
||||
sentence_model = None
|
||||
|
||||
|
||||
def simple_tokenize(text):
|
||||
"""Simple tokenization function."""
|
||||
# Convert to string if not already
|
||||
text = str(text)
|
||||
return text.lower().replace('.', ' ').replace(',', ' ').replace('!', ' ').replace('?', ' ').split()
|
||||
return text.lower().replace(".", " ").replace(",", " ").replace("!", " ").replace("?", " ").split()
|
||||
|
||||
|
||||
def calculate_rouge_scores(prediction: str, reference: str) -> Dict[str, float]:
|
||||
"""Calculate ROUGE scores for prediction against reference."""
|
||||
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
|
||||
scorer = rouge_scorer.RougeScorer(["rouge1", "rouge2", "rougeL"], use_stemmer=True)
|
||||
scores = scorer.score(reference, prediction)
|
||||
return {
|
||||
'rouge1_f': scores['rouge1'].fmeasure,
|
||||
'rouge2_f': scores['rouge2'].fmeasure,
|
||||
'rougeL_f': scores['rougeL'].fmeasure
|
||||
"rouge1_f": scores["rouge1"].fmeasure,
|
||||
"rouge2_f": scores["rouge2"].fmeasure,
|
||||
"rougeL_f": scores["rougeL"].fmeasure,
|
||||
}
|
||||
|
||||
|
||||
def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]:
|
||||
"""Calculate BLEU scores with different n-gram settings."""
|
||||
pred_tokens = nltk.word_tokenize(prediction.lower())
|
||||
ref_tokens = [nltk.word_tokenize(reference.lower())]
|
||||
|
||||
|
||||
weights_list = [(1, 0, 0, 0), (0.5, 0.5, 0, 0), (0.33, 0.33, 0.33, 0), (0.25, 0.25, 0.25, 0.25)]
|
||||
smooth = SmoothingFunction().method1
|
||||
|
||||
|
||||
scores = {}
|
||||
for n, weights in enumerate(weights_list, start=1):
|
||||
try:
|
||||
@@ -69,26 +72,20 @@ def calculate_bleu_scores(prediction: str, reference: str) -> Dict[str, float]:
|
||||
except Exception as e:
|
||||
print(f"Error calculating BLEU score: {e}")
|
||||
score = 0.0
|
||||
scores[f'bleu{n}'] = score
|
||||
|
||||
scores[f"bleu{n}"] = score
|
||||
|
||||
return scores
|
||||
|
||||
|
||||
def calculate_bert_scores(prediction: str, reference: str) -> Dict[str, float]:
|
||||
"""Calculate BERTScore for semantic similarity."""
|
||||
try:
|
||||
P, R, F1 = bert_score([prediction], [reference], lang='en', verbose=False)
|
||||
return {
|
||||
'bert_precision': P.item(),
|
||||
'bert_recall': R.item(),
|
||||
'bert_f1': F1.item()
|
||||
}
|
||||
P, R, F1 = bert_score([prediction], [reference], lang="en", verbose=False)
|
||||
return {"bert_precision": P.item(), "bert_recall": R.item(), "bert_f1": F1.item()}
|
||||
except Exception as e:
|
||||
print(f"Error calculating BERTScore: {e}")
|
||||
return {
|
||||
'bert_precision': 0.0,
|
||||
'bert_recall': 0.0,
|
||||
'bert_f1': 0.0
|
||||
}
|
||||
return {"bert_precision": 0.0, "bert_recall": 0.0, "bert_f1": 0.0}
|
||||
|
||||
|
||||
def calculate_meteor_score(prediction: str, reference: str) -> float:
|
||||
"""Calculate METEOR score for the prediction."""
|
||||
@@ -98,6 +95,7 @@ def calculate_meteor_score(prediction: str, reference: str) -> float:
|
||||
print(f"Error calculating METEOR score: {e}")
|
||||
return 0.0
|
||||
|
||||
|
||||
def calculate_sentence_similarity(prediction: str, reference: str) -> float:
|
||||
"""Calculate sentence embedding similarity using SentenceBERT."""
|
||||
if sentence_model is None:
|
||||
@@ -106,7 +104,7 @@ def calculate_sentence_similarity(prediction: str, reference: str) -> float:
|
||||
# Encode sentences
|
||||
embedding1 = sentence_model.encode([prediction], convert_to_tensor=True)
|
||||
embedding2 = sentence_model.encode([reference], convert_to_tensor=True)
|
||||
|
||||
|
||||
# Calculate cosine similarity
|
||||
similarity = pytorch_cos_sim(embedding1, embedding2).item()
|
||||
return float(similarity)
|
||||
@@ -114,6 +112,7 @@ def calculate_sentence_similarity(prediction: str, reference: str) -> float:
|
||||
print(f"Error calculating sentence similarity: {e}")
|
||||
return 0.0
|
||||
|
||||
|
||||
def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
|
||||
"""Calculate comprehensive evaluation metrics for a prediction."""
|
||||
# Handle empty or None values
|
||||
@@ -130,31 +129,31 @@ def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
|
||||
"bleu4": 0.0,
|
||||
"bert_f1": 0.0,
|
||||
"meteor": 0.0,
|
||||
"sbert_similarity": 0.0
|
||||
"sbert_similarity": 0.0,
|
||||
}
|
||||
|
||||
|
||||
# Convert to strings if they're not already
|
||||
prediction = str(prediction).strip()
|
||||
reference = str(reference).strip()
|
||||
|
||||
|
||||
# Calculate exact match
|
||||
exact_match = int(prediction.lower() == reference.lower())
|
||||
|
||||
|
||||
# Calculate token-based F1 score
|
||||
pred_tokens = set(simple_tokenize(prediction))
|
||||
ref_tokens = set(simple_tokenize(reference))
|
||||
common_tokens = pred_tokens & ref_tokens
|
||||
|
||||
|
||||
if not pred_tokens or not ref_tokens:
|
||||
f1 = 0.0
|
||||
else:
|
||||
precision = len(common_tokens) / len(pred_tokens)
|
||||
recall = len(common_tokens) / len(ref_tokens)
|
||||
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
|
||||
|
||||
|
||||
# Calculate all scores
|
||||
bleu_scores = calculate_bleu_scores(prediction, reference)
|
||||
|
||||
|
||||
# Combine all metrics
|
||||
metrics = {
|
||||
"exact_match": exact_match,
|
||||
@@ -164,48 +163,49 @@ def calculate_metrics(prediction: str, reference: str) -> Dict[str, float]:
|
||||
|
||||
return metrics
|
||||
|
||||
def aggregate_metrics(all_metrics: List[Dict[str, float]], all_categories: List[int]) -> Dict[str, Dict[str, Union[float, Dict[str, float]]]]:
|
||||
|
||||
def aggregate_metrics(
|
||||
all_metrics: List[Dict[str, float]], all_categories: List[int]
|
||||
) -> Dict[str, Dict[str, Union[float, Dict[str, float]]]]:
|
||||
"""Calculate aggregate statistics for all metrics, split by category."""
|
||||
if not all_metrics:
|
||||
return {}
|
||||
|
||||
|
||||
# Initialize aggregates for overall and per-category metrics
|
||||
aggregates = defaultdict(list)
|
||||
category_aggregates = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
|
||||
# Collect all values for each metric, both overall and per category
|
||||
for metrics, category in zip(all_metrics, all_categories):
|
||||
for metric_name, value in metrics.items():
|
||||
aggregates[metric_name].append(value)
|
||||
category_aggregates[category][metric_name].append(value)
|
||||
|
||||
|
||||
# Calculate statistics for overall metrics
|
||||
results = {
|
||||
"overall": {}
|
||||
}
|
||||
|
||||
results = {"overall": {}}
|
||||
|
||||
for metric_name, values in aggregates.items():
|
||||
results["overall"][metric_name] = {
|
||||
'mean': statistics.mean(values),
|
||||
'std': statistics.stdev(values) if len(values) > 1 else 0.0,
|
||||
'median': statistics.median(values),
|
||||
'min': min(values),
|
||||
'max': max(values),
|
||||
'count': len(values)
|
||||
"mean": statistics.mean(values),
|
||||
"std": statistics.stdev(values) if len(values) > 1 else 0.0,
|
||||
"median": statistics.median(values),
|
||||
"min": min(values),
|
||||
"max": max(values),
|
||||
"count": len(values),
|
||||
}
|
||||
|
||||
|
||||
# Calculate statistics for each category
|
||||
for category in sorted(category_aggregates.keys()):
|
||||
results[f"category_{category}"] = {}
|
||||
for metric_name, values in category_aggregates[category].items():
|
||||
if values: # Only calculate if we have values for this category
|
||||
results[f"category_{category}"][metric_name] = {
|
||||
'mean': statistics.mean(values),
|
||||
'std': statistics.stdev(values) if len(values) > 1 else 0.0,
|
||||
'median': statistics.median(values),
|
||||
'min': min(values),
|
||||
'max': max(values),
|
||||
'count': len(values)
|
||||
"mean": statistics.mean(values),
|
||||
"std": statistics.stdev(values) if len(values) > 1 else 0.0,
|
||||
"median": statistics.median(values),
|
||||
"min": min(values),
|
||||
"max": max(values),
|
||||
"count": len(values),
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
|
||||
@@ -144,4 +144,4 @@ ANSWER_PROMPT_ZEP = """
|
||||
|
||||
Question: {{question}}
|
||||
Answer:
|
||||
"""
|
||||
"""
|
||||
|
||||
@@ -21,23 +21,15 @@ class Experiment:
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Run memory experiments')
|
||||
parser.add_argument('--technique_type', choices=TECHNIQUES, default='mem0',
|
||||
help='Memory technique to use')
|
||||
parser.add_argument('--method', choices=METHODS, default='add',
|
||||
help='Method to use')
|
||||
parser.add_argument('--chunk_size', type=int, default=1000,
|
||||
help='Chunk size for processing')
|
||||
parser.add_argument('--output_folder', type=str, default='results/',
|
||||
help='Output path for results')
|
||||
parser.add_argument('--top_k', type=int, default=30,
|
||||
help='Number of top memories to retrieve')
|
||||
parser.add_argument('--filter_memories', action='store_true', default=False,
|
||||
help='Whether to filter memories')
|
||||
parser.add_argument('--is_graph', action='store_true', default=False,
|
||||
help='Whether to use graph-based search')
|
||||
parser.add_argument('--num_chunks', type=int, default=1,
|
||||
help='Number of chunks to process')
|
||||
parser = argparse.ArgumentParser(description="Run memory experiments")
|
||||
parser.add_argument("--technique_type", choices=TECHNIQUES, default="mem0", help="Memory technique to use")
|
||||
parser.add_argument("--method", choices=METHODS, default="add", help="Method to use")
|
||||
parser.add_argument("--chunk_size", type=int, default=1000, help="Chunk size for processing")
|
||||
parser.add_argument("--output_folder", type=str, default="results/", help="Output path for results")
|
||||
parser.add_argument("--top_k", type=int, default=30, help="Number of top memories to retrieve")
|
||||
parser.add_argument("--filter_memories", action="store_true", default=False, help="Whether to filter memories")
|
||||
parser.add_argument("--is_graph", action="store_true", default=False, help="Whether to use graph-based search")
|
||||
parser.add_argument("--num_chunks", type=int, default=1, help="Number of chunks to process")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -46,33 +38,18 @@ def main():
|
||||
|
||||
if args.technique_type == "mem0":
|
||||
if args.method == "add":
|
||||
memory_manager = MemoryADD(
|
||||
data_path='dataset/locomo10.json',
|
||||
is_graph=args.is_graph
|
||||
)
|
||||
memory_manager = MemoryADD(data_path="dataset/locomo10.json", is_graph=args.is_graph)
|
||||
memory_manager.process_all_conversations()
|
||||
elif args.method == "search":
|
||||
output_file_path = os.path.join(
|
||||
args.output_folder,
|
||||
f"mem0_results_top_{args.top_k}_filter_{args.filter_memories}_graph_{args.is_graph}.json"
|
||||
f"mem0_results_top_{args.top_k}_filter_{args.filter_memories}_graph_{args.is_graph}.json",
|
||||
)
|
||||
memory_searcher = MemorySearch(
|
||||
output_file_path,
|
||||
args.top_k,
|
||||
args.filter_memories,
|
||||
args.is_graph
|
||||
)
|
||||
memory_searcher.process_data_file('dataset/locomo10.json')
|
||||
memory_searcher = MemorySearch(output_file_path, args.top_k, args.filter_memories, args.is_graph)
|
||||
memory_searcher.process_data_file("dataset/locomo10.json")
|
||||
elif args.technique_type == "rag":
|
||||
output_file_path = os.path.join(
|
||||
args.output_folder,
|
||||
f"rag_results_{args.chunk_size}_k{args.num_chunks}.json"
|
||||
)
|
||||
rag_manager = RAGManager(
|
||||
data_path="dataset/locomo10_rag.json",
|
||||
chunk_size=args.chunk_size,
|
||||
k=args.num_chunks
|
||||
)
|
||||
output_file_path = os.path.join(args.output_folder, f"rag_results_{args.chunk_size}_k{args.num_chunks}.json")
|
||||
rag_manager = RAGManager(data_path="dataset/locomo10_rag.json", chunk_size=args.chunk_size, k=args.num_chunks)
|
||||
rag_manager.process_all_conversations(output_file_path)
|
||||
elif args.technique_type == "langmem":
|
||||
output_file_path = os.path.join(args.output_folder, "langmem_results.json")
|
||||
@@ -85,11 +62,7 @@ def main():
|
||||
elif args.method == "search":
|
||||
output_file_path = os.path.join(args.output_folder, "zep_search_results.json")
|
||||
zep_manager = ZepSearch()
|
||||
zep_manager.process_data_file(
|
||||
"dataset/locomo10.json",
|
||||
"1",
|
||||
output_file_path
|
||||
)
|
||||
zep_manager.process_data_file("dataset/locomo10.json", "1", output_file_path)
|
||||
elif args.technique_type == "openai":
|
||||
output_file_path = os.path.join(args.output_folder, "openai_results.json")
|
||||
openai_manager = OpenAIPredict()
|
||||
|
||||
+36
-40
@@ -28,14 +28,12 @@ def get_answer(question, speaker_1_user_id, speaker_1_memories, speaker_2_user_i
|
||||
speaker_1_user_id=speaker_1_user_id,
|
||||
speaker_1_memories=speaker_1_memories,
|
||||
speaker_2_user_id=speaker_2_user_id,
|
||||
speaker_2_memories=speaker_2_memories
|
||||
speaker_2_memories=speaker_2_memories,
|
||||
)
|
||||
|
||||
t1 = time.time()
|
||||
response = client.chat.completions.create(
|
||||
model=os.getenv("MODEL"),
|
||||
messages=[{"role": "system", "content": prompt}],
|
||||
temperature=0.0
|
||||
model=os.getenv("MODEL"), messages=[{"role": "system", "content": prompt}], temperature=0.0
|
||||
)
|
||||
t2 = time.time()
|
||||
return response.choices[0].message.content, t2 - t1
|
||||
@@ -59,7 +57,9 @@ def prompt(state):
|
||||
|
||||
|
||||
class LangMem:
|
||||
def __init__(self,):
|
||||
def __init__(
|
||||
self,
|
||||
):
|
||||
self.store = InMemoryStore(
|
||||
index={
|
||||
"dims": 1536,
|
||||
@@ -80,18 +80,12 @@ class LangMem:
|
||||
)
|
||||
|
||||
def add_memory(self, message, config):
|
||||
return self.agent.invoke(
|
||||
{"messages": [{"role": "user", "content": message}]},
|
||||
config=config
|
||||
)
|
||||
return self.agent.invoke({"messages": [{"role": "user", "content": message}]}, config=config)
|
||||
|
||||
def search_memory(self, query, config):
|
||||
try:
|
||||
t1 = time.time()
|
||||
response = self.agent.invoke(
|
||||
{"messages": [{"role": "user", "content": query}]},
|
||||
config=config
|
||||
)
|
||||
response = self.agent.invoke({"messages": [{"role": "user", "content": query}]}, config=config)
|
||||
t2 = time.time()
|
||||
return response["messages"][-1].content, t2 - t1
|
||||
except Exception as e:
|
||||
@@ -102,7 +96,7 @@ class LangMem:
|
||||
class LangMemManager:
|
||||
def __init__(self, dataset_path):
|
||||
self.dataset_path = dataset_path
|
||||
with open(self.dataset_path, 'r') as f:
|
||||
with open(self.dataset_path, "r") as f:
|
||||
self.data = json.load(f)
|
||||
|
||||
def process_all_conversations(self, output_file_path):
|
||||
@@ -123,7 +117,7 @@ class LangMemManager:
|
||||
|
||||
# Identify speakers
|
||||
for conv in chat_history:
|
||||
speakers.add(conv['speaker'])
|
||||
speakers.add(conv["speaker"])
|
||||
|
||||
if len(speakers) != 2:
|
||||
raise ValueError(f"Expected 2 speakers, got {len(speakers)}")
|
||||
@@ -134,50 +128,52 @@ class LangMemManager:
|
||||
# Add memories for each message
|
||||
for conv in tqdm(chat_history, desc=f"Processing messages {key}", leave=False):
|
||||
message = f"{conv['timestamp']} | {conv['speaker']}: {conv['text']}"
|
||||
if conv['speaker'] == speaker1:
|
||||
if conv["speaker"] == speaker1:
|
||||
agent1.add_memory(message, config)
|
||||
elif conv['speaker'] == speaker2:
|
||||
elif conv["speaker"] == speaker2:
|
||||
agent2.add_memory(message, config)
|
||||
else:
|
||||
raise ValueError(f"Expected speaker1 or speaker2, got {conv['speaker']}")
|
||||
|
||||
# Process questions
|
||||
for q in tqdm(questions, desc=f"Processing questions {key}", leave=False):
|
||||
category = q['category']
|
||||
category = q["category"]
|
||||
|
||||
if int(category) == 5:
|
||||
continue
|
||||
|
||||
answer = q['answer']
|
||||
question = q['question']
|
||||
answer = q["answer"]
|
||||
question = q["question"]
|
||||
response1, speaker1_memory_time = agent1.search_memory(question, config)
|
||||
response2, speaker2_memory_time = agent2.search_memory(question, config)
|
||||
|
||||
generated_answer, response_time = get_answer(
|
||||
question, speaker1, response1, speaker2, response2
|
||||
)
|
||||
generated_answer, response_time = get_answer(question, speaker1, response1, speaker2, response2)
|
||||
|
||||
result[key].append({
|
||||
"question": question,
|
||||
"answer": answer,
|
||||
"response1": response1,
|
||||
"response2": response2,
|
||||
"category": category,
|
||||
"speaker1_memory_time": speaker1_memory_time,
|
||||
"speaker2_memory_time": speaker2_memory_time,
|
||||
"response_time": response_time,
|
||||
'response': generated_answer
|
||||
})
|
||||
result[key].append(
|
||||
{
|
||||
"question": question,
|
||||
"answer": answer,
|
||||
"response1": response1,
|
||||
"response2": response2,
|
||||
"category": category,
|
||||
"speaker1_memory_time": speaker1_memory_time,
|
||||
"speaker2_memory_time": speaker2_memory_time,
|
||||
"response_time": response_time,
|
||||
"response": generated_answer,
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
# Use multiprocessing to process conversations in parallel
|
||||
with mp.Pool(processes=10) as pool:
|
||||
results = list(tqdm(
|
||||
pool.imap(process_conversation, list(self.data.items())),
|
||||
total=len(self.data),
|
||||
desc="Processing conversations"
|
||||
))
|
||||
results = list(
|
||||
tqdm(
|
||||
pool.imap(process_conversation, list(self.data.items())),
|
||||
total=len(self.data),
|
||||
desc="Processing conversations",
|
||||
)
|
||||
)
|
||||
|
||||
# Combine results from all workers
|
||||
for result in results:
|
||||
@@ -185,5 +181,5 @@ class LangMemManager:
|
||||
OUTPUT[key].extend(items)
|
||||
|
||||
# Save final results
|
||||
with open(output_file_path, 'w') as f:
|
||||
with open(output_file_path, "w") as f:
|
||||
json.dump(OUTPUT, f, indent=4)
|
||||
|
||||
@@ -13,7 +13,7 @@ load_dotenv()
|
||||
|
||||
|
||||
# Update custom instructions
|
||||
custom_instructions ="""
|
||||
custom_instructions = """
|
||||
Generate personal memories that follow these guidelines:
|
||||
|
||||
1. Each memory should be self-contained with complete context, including:
|
||||
@@ -47,7 +47,7 @@ class MemoryADD:
|
||||
self.mem0_client = MemoryClient(
|
||||
api_key=os.getenv("MEM0_API_KEY"),
|
||||
org_id=os.getenv("MEM0_ORGANIZATION_ID"),
|
||||
project_id=os.getenv("MEM0_PROJECT_ID")
|
||||
project_id=os.getenv("MEM0_PROJECT_ID"),
|
||||
)
|
||||
|
||||
self.mem0_client.update_project(custom_instructions=custom_instructions)
|
||||
@@ -59,15 +59,16 @@ class MemoryADD:
|
||||
self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
with open(self.data_path, 'r') as f:
|
||||
with open(self.data_path, "r") as f:
|
||||
self.data = json.load(f)
|
||||
return self.data
|
||||
|
||||
def add_memory(self, user_id, message, metadata, retries=3):
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
_ = self.mem0_client.add(message, user_id=user_id, version="v2",
|
||||
metadata=metadata, enable_graph=self.is_graph)
|
||||
_ = self.mem0_client.add(
|
||||
message, user_id=user_id, version="v2", metadata=metadata, enable_graph=self.is_graph
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
if attempt < retries - 1:
|
||||
@@ -78,13 +79,13 @@ class MemoryADD:
|
||||
|
||||
def add_memories_for_speaker(self, speaker, messages, timestamp, desc):
|
||||
for i in tqdm(range(0, len(messages), self.batch_size), desc=desc):
|
||||
batch_messages = messages[i:i+self.batch_size]
|
||||
batch_messages = messages[i : i + self.batch_size]
|
||||
self.add_memory(speaker, batch_messages, metadata={"timestamp": timestamp})
|
||||
|
||||
def process_conversation(self, item, idx):
|
||||
conversation = item['conversation']
|
||||
speaker_a = conversation['speaker_a']
|
||||
speaker_b = conversation['speaker_b']
|
||||
conversation = item["conversation"]
|
||||
speaker_a = conversation["speaker_a"]
|
||||
speaker_b = conversation["speaker_b"]
|
||||
|
||||
speaker_a_user_id = f"{speaker_a}_{idx}"
|
||||
speaker_b_user_id = f"{speaker_b}_{idx}"
|
||||
@@ -94,7 +95,7 @@ class MemoryADD:
|
||||
self.mem0_client.delete_all(user_id=speaker_b_user_id)
|
||||
|
||||
for key in conversation.keys():
|
||||
if key in ['speaker_a', 'speaker_b'] or "date" in key or "timestamp" in key:
|
||||
if key in ["speaker_a", "speaker_b"] or "date" in key or "timestamp" in key:
|
||||
continue
|
||||
|
||||
date_time_key = key + "_date_time"
|
||||
@@ -104,10 +105,10 @@ class MemoryADD:
|
||||
messages = []
|
||||
messages_reverse = []
|
||||
for chat in chats:
|
||||
if chat['speaker'] == speaker_a:
|
||||
if chat["speaker"] == speaker_a:
|
||||
messages.append({"role": "user", "content": f"{speaker_a}: {chat['text']}"})
|
||||
messages_reverse.append({"role": "assistant", "content": f"{speaker_a}: {chat['text']}"})
|
||||
elif chat['speaker'] == speaker_b:
|
||||
elif chat["speaker"] == speaker_b:
|
||||
messages.append({"role": "assistant", "content": f"{speaker_b}: {chat['text']}"})
|
||||
messages_reverse.append({"role": "user", "content": f"{speaker_b}: {chat['text']}"})
|
||||
else:
|
||||
@@ -116,11 +117,11 @@ class MemoryADD:
|
||||
# add memories for the two users on different threads
|
||||
thread_a = threading.Thread(
|
||||
target=self.add_memories_for_speaker,
|
||||
args=(speaker_a_user_id, messages, timestamp, "Adding Memories for Speaker A")
|
||||
args=(speaker_a_user_id, messages, timestamp, "Adding Memories for Speaker A"),
|
||||
)
|
||||
thread_b = threading.Thread(
|
||||
target=self.add_memories_for_speaker,
|
||||
args=(speaker_b_user_id, messages_reverse, timestamp, "Adding Memories for Speaker B")
|
||||
args=(speaker_b_user_id, messages_reverse, timestamp, "Adding Memories for Speaker B"),
|
||||
)
|
||||
|
||||
thread_a.start()
|
||||
@@ -134,10 +135,7 @@ class MemoryADD:
|
||||
if not self.data:
|
||||
raise ValueError("No data loaded. Please set data_path and call load_data() first.")
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = [
|
||||
executor.submit(self.process_conversation, item, idx)
|
||||
for idx, item in enumerate(self.data)
|
||||
]
|
||||
futures = [executor.submit(self.process_conversation, item, idx) for idx, item in enumerate(self.data)]
|
||||
|
||||
for future in futures:
|
||||
future.result()
|
||||
future.result()
|
||||
|
||||
@@ -16,12 +16,11 @@ load_dotenv()
|
||||
|
||||
|
||||
class MemorySearch:
|
||||
|
||||
def __init__(self, output_path='results.json', top_k=10, filter_memories=False, is_graph=False):
|
||||
def __init__(self, output_path="results.json", top_k=10, filter_memories=False, is_graph=False):
|
||||
self.mem0_client = MemoryClient(
|
||||
api_key=os.getenv("MEM0_API_KEY"),
|
||||
org_id=os.getenv("MEM0_ORGANIZATION_ID"),
|
||||
project_id=os.getenv("MEM0_PROJECT_ID")
|
||||
project_id=os.getenv("MEM0_PROJECT_ID"),
|
||||
)
|
||||
self.top_k = top_k
|
||||
self.openai_client = OpenAI()
|
||||
@@ -42,11 +41,18 @@ class MemorySearch:
|
||||
try:
|
||||
if self.is_graph:
|
||||
print("Searching with graph")
|
||||
memories = self.mem0_client.search(query, user_id=user_id, top_k=self.top_k,
|
||||
filter_memories=self.filter_memories, enable_graph=True, output_format='v1.1')
|
||||
memories = self.mem0_client.search(
|
||||
query,
|
||||
user_id=user_id,
|
||||
top_k=self.top_k,
|
||||
filter_memories=self.filter_memories,
|
||||
enable_graph=True,
|
||||
output_format="v1.1",
|
||||
)
|
||||
else:
|
||||
memories = self.mem0_client.search(query, user_id=user_id, top_k=self.top_k,
|
||||
filter_memories=self.filter_memories)
|
||||
memories = self.mem0_client.search(
|
||||
query, user_id=user_id, top_k=self.top_k, filter_memories=self.filter_memories
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
print("Retrying...")
|
||||
@@ -57,64 +63,86 @@ class MemorySearch:
|
||||
|
||||
end_time = time.time()
|
||||
if not self.is_graph:
|
||||
semantic_memories = [{'memory': memory['memory'],
|
||||
'timestamp': memory['metadata']['timestamp'],
|
||||
'score': round(memory['score'], 2)}
|
||||
for memory in memories]
|
||||
semantic_memories = [
|
||||
{
|
||||
"memory": memory["memory"],
|
||||
"timestamp": memory["metadata"]["timestamp"],
|
||||
"score": round(memory["score"], 2),
|
||||
}
|
||||
for memory in memories
|
||||
]
|
||||
graph_memories = None
|
||||
else:
|
||||
semantic_memories = [{'memory': memory['memory'],
|
||||
'timestamp': memory['metadata']['timestamp'],
|
||||
'score': round(memory['score'], 2)} for memory in memories['results']]
|
||||
graph_memories = [{"source": relation['source'], "relationship": relation['relationship'], "target": relation['target']} for relation in memories['relations']]
|
||||
semantic_memories = [
|
||||
{
|
||||
"memory": memory["memory"],
|
||||
"timestamp": memory["metadata"]["timestamp"],
|
||||
"score": round(memory["score"], 2),
|
||||
}
|
||||
for memory in memories["results"]
|
||||
]
|
||||
graph_memories = [
|
||||
{"source": relation["source"], "relationship": relation["relationship"], "target": relation["target"]}
|
||||
for relation in memories["relations"]
|
||||
]
|
||||
return semantic_memories, graph_memories, end_time - start_time
|
||||
|
||||
def answer_question(self, speaker_1_user_id, speaker_2_user_id, question, answer, category):
|
||||
speaker_1_memories, speaker_1_graph_memories, speaker_1_memory_time = self.search_memory(speaker_1_user_id, question)
|
||||
speaker_2_memories, speaker_2_graph_memories, speaker_2_memory_time = self.search_memory(speaker_2_user_id, question)
|
||||
speaker_1_memories, speaker_1_graph_memories, speaker_1_memory_time = self.search_memory(
|
||||
speaker_1_user_id, question
|
||||
)
|
||||
speaker_2_memories, speaker_2_graph_memories, speaker_2_memory_time = self.search_memory(
|
||||
speaker_2_user_id, question
|
||||
)
|
||||
|
||||
search_1_memory = [f"{item['timestamp']}: {item['memory']}"
|
||||
for item in speaker_1_memories]
|
||||
search_2_memory = [f"{item['timestamp']}: {item['memory']}"
|
||||
for item in speaker_2_memories]
|
||||
search_1_memory = [f"{item['timestamp']}: {item['memory']}" for item in speaker_1_memories]
|
||||
search_2_memory = [f"{item['timestamp']}: {item['memory']}" for item in speaker_2_memories]
|
||||
|
||||
template = Template(self.ANSWER_PROMPT)
|
||||
answer_prompt = template.render(
|
||||
speaker_1_user_id=speaker_1_user_id.split('_')[0],
|
||||
speaker_2_user_id=speaker_2_user_id.split('_')[0],
|
||||
speaker_1_user_id=speaker_1_user_id.split("_")[0],
|
||||
speaker_2_user_id=speaker_2_user_id.split("_")[0],
|
||||
speaker_1_memories=json.dumps(search_1_memory, indent=4),
|
||||
speaker_2_memories=json.dumps(search_2_memory, indent=4),
|
||||
speaker_1_graph_memories=json.dumps(speaker_1_graph_memories, indent=4),
|
||||
speaker_2_graph_memories=json.dumps(speaker_2_graph_memories, indent=4),
|
||||
question=question
|
||||
question=question,
|
||||
)
|
||||
|
||||
t1 = time.time()
|
||||
response = self.openai_client.chat.completions.create(
|
||||
model=os.getenv("MODEL"),
|
||||
messages=[
|
||||
{"role": "system", "content": answer_prompt}
|
||||
],
|
||||
temperature=0.0
|
||||
model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
|
||||
)
|
||||
t2 = time.time()
|
||||
response_time = t2 - t1
|
||||
return response.choices[0].message.content, speaker_1_memories, speaker_2_memories, speaker_1_memory_time, speaker_2_memory_time, speaker_1_graph_memories, speaker_2_graph_memories, response_time
|
||||
return (
|
||||
response.choices[0].message.content,
|
||||
speaker_1_memories,
|
||||
speaker_2_memories,
|
||||
speaker_1_memory_time,
|
||||
speaker_2_memory_time,
|
||||
speaker_1_graph_memories,
|
||||
speaker_2_graph_memories,
|
||||
response_time,
|
||||
)
|
||||
|
||||
def process_question(self, val, speaker_a_user_id, speaker_b_user_id):
|
||||
question = val.get('question', '')
|
||||
answer = val.get('answer', '')
|
||||
category = val.get('category', -1)
|
||||
evidence = val.get('evidence', [])
|
||||
adversarial_answer = val.get('adversarial_answer', '')
|
||||
question = val.get("question", "")
|
||||
answer = val.get("answer", "")
|
||||
category = val.get("category", -1)
|
||||
evidence = val.get("evidence", [])
|
||||
adversarial_answer = val.get("adversarial_answer", "")
|
||||
|
||||
response, speaker_1_memories, speaker_2_memories, speaker_1_memory_time, speaker_2_memory_time, speaker_1_graph_memories, speaker_2_graph_memories, response_time = self.answer_question(
|
||||
speaker_a_user_id,
|
||||
speaker_b_user_id,
|
||||
question,
|
||||
answer,
|
||||
category
|
||||
)
|
||||
(
|
||||
response,
|
||||
speaker_1_memories,
|
||||
speaker_2_memories,
|
||||
speaker_1_memory_time,
|
||||
speaker_2_memory_time,
|
||||
speaker_1_graph_memories,
|
||||
speaker_2_graph_memories,
|
||||
response_time,
|
||||
) = self.answer_question(speaker_a_user_id, speaker_b_user_id, question, answer, category)
|
||||
|
||||
result = {
|
||||
"question": question,
|
||||
@@ -125,67 +153,63 @@ class MemorySearch:
|
||||
"adversarial_answer": adversarial_answer,
|
||||
"speaker_1_memories": speaker_1_memories,
|
||||
"speaker_2_memories": speaker_2_memories,
|
||||
'num_speaker_1_memories': len(speaker_1_memories),
|
||||
'num_speaker_2_memories': len(speaker_2_memories),
|
||||
'speaker_1_memory_time': speaker_1_memory_time,
|
||||
'speaker_2_memory_time': speaker_2_memory_time,
|
||||
"num_speaker_1_memories": len(speaker_1_memories),
|
||||
"num_speaker_2_memories": len(speaker_2_memories),
|
||||
"speaker_1_memory_time": speaker_1_memory_time,
|
||||
"speaker_2_memory_time": speaker_2_memory_time,
|
||||
"speaker_1_graph_memories": speaker_1_graph_memories,
|
||||
"speaker_2_graph_memories": speaker_2_graph_memories,
|
||||
"response_time": response_time
|
||||
"response_time": response_time,
|
||||
}
|
||||
|
||||
# Save results after each question is processed
|
||||
with open(self.output_path, 'w') as f:
|
||||
with open(self.output_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
return result
|
||||
|
||||
def process_data_file(self, file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
|
||||
qa = item['qa']
|
||||
conversation = item['conversation']
|
||||
speaker_a = conversation['speaker_a']
|
||||
speaker_b = conversation['speaker_b']
|
||||
qa = item["qa"]
|
||||
conversation = item["conversation"]
|
||||
speaker_a = conversation["speaker_a"]
|
||||
speaker_b = conversation["speaker_b"]
|
||||
|
||||
speaker_a_user_id = f"{speaker_a}_{idx}"
|
||||
speaker_b_user_id = f"{speaker_b}_{idx}"
|
||||
|
||||
for question_item in tqdm(qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False):
|
||||
result = self.process_question(
|
||||
question_item,
|
||||
speaker_a_user_id,
|
||||
speaker_b_user_id
|
||||
)
|
||||
for question_item in tqdm(
|
||||
qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
|
||||
):
|
||||
result = self.process_question(question_item, speaker_a_user_id, speaker_b_user_id)
|
||||
self.results[idx].append(result)
|
||||
|
||||
# Save results after each question is processed
|
||||
with open(self.output_path, 'w') as f:
|
||||
with open(self.output_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
# Final save at the end
|
||||
with open(self.output_path, 'w') as f:
|
||||
with open(self.output_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
def process_questions_parallel(self, qa_list, speaker_a_user_id, speaker_b_user_id, max_workers=1):
|
||||
def process_single_question(val):
|
||||
result = self.process_question(val, speaker_a_user_id, speaker_b_user_id)
|
||||
# Save results after each question is processed
|
||||
with open(self.output_path, 'w') as f:
|
||||
with open(self.output_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
return result
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
results = list(tqdm(
|
||||
executor.map(process_single_question, qa_list),
|
||||
total=len(qa_list),
|
||||
desc="Answering Questions"
|
||||
))
|
||||
results = list(
|
||||
tqdm(executor.map(process_single_question, qa_list), total=len(qa_list), desc="Answering Questions")
|
||||
)
|
||||
|
||||
# Final save at the end
|
||||
with open(self.output_path, 'w') as f:
|
||||
with open(self.output_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
return results
|
||||
|
||||
@@ -59,23 +59,19 @@ class OpenAIPredict:
|
||||
self.results = defaultdict(list)
|
||||
|
||||
def search_memory(self, idx):
|
||||
|
||||
with open(f'memories/{idx}.txt', 'r') as file:
|
||||
with open(f"memories/{idx}.txt", "r") as file:
|
||||
memories = file.read()
|
||||
|
||||
return memories, 0
|
||||
|
||||
def process_question(self, val, idx):
|
||||
question = val.get('question', '')
|
||||
answer = val.get('answer', '')
|
||||
category = val.get('category', -1)
|
||||
evidence = val.get('evidence', [])
|
||||
adversarial_answer = val.get('adversarial_answer', '')
|
||||
question = val.get("question", "")
|
||||
answer = val.get("answer", "")
|
||||
category = val.get("category", -1)
|
||||
evidence = val.get("evidence", [])
|
||||
adversarial_answer = val.get("adversarial_answer", "")
|
||||
|
||||
response, search_memory_time, response_time, context = self.answer_question(
|
||||
idx,
|
||||
question
|
||||
)
|
||||
response, search_memory_time, response_time, context = self.answer_question(idx, question)
|
||||
|
||||
result = {
|
||||
"question": question,
|
||||
@@ -86,7 +82,7 @@ class OpenAIPredict:
|
||||
"adversarial_answer": adversarial_answer,
|
||||
"search_memory_time": search_memory_time,
|
||||
"response_time": response_time,
|
||||
"context": context
|
||||
"context": context,
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -95,43 +91,35 @@ class OpenAIPredict:
|
||||
memories, search_memory_time = self.search_memory(idx)
|
||||
|
||||
template = Template(ANSWER_PROMPT)
|
||||
answer_prompt = template.render(
|
||||
memories=memories,
|
||||
question=question
|
||||
)
|
||||
answer_prompt = template.render(memories=memories, question=question)
|
||||
|
||||
t1 = time.time()
|
||||
response = self.openai_client.chat.completions.create(
|
||||
model=os.getenv("MODEL"),
|
||||
messages=[
|
||||
{"role": "system", "content": answer_prompt}
|
||||
],
|
||||
temperature=0.0
|
||||
model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
|
||||
)
|
||||
t2 = time.time()
|
||||
response_time = t2 - t1
|
||||
return response.choices[0].message.content, search_memory_time, response_time, memories
|
||||
|
||||
def process_data_file(self, file_path, output_file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
|
||||
qa = item['qa']
|
||||
qa = item["qa"]
|
||||
|
||||
for question_item in tqdm(qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False):
|
||||
result = self.process_question(
|
||||
question_item,
|
||||
idx
|
||||
)
|
||||
for question_item in tqdm(
|
||||
qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
|
||||
):
|
||||
result = self.process_question(question_item, idx)
|
||||
self.results[idx].append(result)
|
||||
|
||||
# Save results after each question is processed
|
||||
with open(output_file_path, 'w') as f:
|
||||
with open(output_file_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
# Final save at the end
|
||||
with open(output_file_path, 'w') as f:
|
||||
with open(output_file_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
|
||||
@@ -141,4 +129,3 @@ if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
openai_predict = OpenAIPredict()
|
||||
openai_predict.process_data_file("../../dataset/locomo10.json", args.output_file_path)
|
||||
|
||||
|
||||
+34
-49
@@ -33,10 +33,7 @@ class RAGManager:
|
||||
|
||||
def generate_response(self, question, context):
|
||||
template = Template(PROMPT)
|
||||
prompt = template.render(
|
||||
CONTEXT=context,
|
||||
QUESTION=question
|
||||
)
|
||||
prompt = template.render(CONTEXT=context, QUESTION=question)
|
||||
|
||||
max_retries = 3
|
||||
retries = 0
|
||||
@@ -47,19 +44,21 @@ class RAGManager:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system",
|
||||
"content": "You are a helpful assistant that can answer "
|
||||
"questions based on the provided context."
|
||||
"If the question involves timing, use the conversation date for reference."
|
||||
"Provide the shortest possible answer."
|
||||
"Use words directly from the conversation when possible."
|
||||
"Avoid using subjects in your answer."},
|
||||
{"role": "user", "content": prompt}
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful assistant that can answer "
|
||||
"questions based on the provided context."
|
||||
"If the question involves timing, use the conversation date for reference."
|
||||
"Provide the shortest possible answer."
|
||||
"Use words directly from the conversation when possible."
|
||||
"Avoid using subjects in your answer.",
|
||||
},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0
|
||||
temperature=0,
|
||||
)
|
||||
t2 = time.time()
|
||||
return response.choices[0].message.content.strip(), t2-t1
|
||||
return response.choices[0].message.content.strip(), t2 - t1
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
if retries > max_retries:
|
||||
@@ -69,21 +68,16 @@ class RAGManager:
|
||||
def clean_chat_history(self, chat_history):
|
||||
cleaned_chat_history = ""
|
||||
for c in chat_history:
|
||||
cleaned_chat_history += (f"{c['timestamp']} | {c['speaker']}: "
|
||||
f"{c['text']}\n")
|
||||
cleaned_chat_history += f"{c['timestamp']} | {c['speaker']}: " f"{c['text']}\n"
|
||||
|
||||
return cleaned_chat_history
|
||||
|
||||
def calculate_embedding(self, document):
|
||||
response = self.client.embeddings.create(
|
||||
model=os.getenv("EMBEDDING_MODEL"),
|
||||
input=document
|
||||
)
|
||||
response = self.client.embeddings.create(model=os.getenv("EMBEDDING_MODEL"), input=document)
|
||||
return response.data[0].embedding
|
||||
|
||||
def calculate_similarity(self, embedding1, embedding2):
|
||||
return np.dot(embedding1, embedding2) / (
|
||||
np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
|
||||
return np.dot(embedding1, embedding2) / (np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
|
||||
|
||||
def search(self, query, chunks, embeddings, k=1):
|
||||
"""
|
||||
@@ -101,10 +95,7 @@ class RAGManager:
|
||||
"""
|
||||
t1 = time.time()
|
||||
query_embedding = self.calculate_embedding(query)
|
||||
similarities = [
|
||||
self.calculate_similarity(query_embedding, embedding)
|
||||
for embedding in embeddings
|
||||
]
|
||||
similarities = [self.calculate_similarity(query_embedding, embedding) for embedding in embeddings]
|
||||
|
||||
# Get indices of top-k most similar chunks
|
||||
if k == 1:
|
||||
@@ -118,7 +109,7 @@ class RAGManager:
|
||||
combined_chunks = "\n<->\n".join([chunks[i] for i in top_indices])
|
||||
|
||||
t2 = time.time()
|
||||
return combined_chunks, t2-t1
|
||||
return combined_chunks, t2 - t1
|
||||
|
||||
def create_chunks(self, chat_history, chunk_size=500):
|
||||
"""
|
||||
@@ -139,7 +130,7 @@ class RAGManager:
|
||||
|
||||
# Split into chunks based on token count
|
||||
for i in range(0, len(tokens), chunk_size):
|
||||
chunk_tokens = tokens[i:i+chunk_size]
|
||||
chunk_tokens = tokens[i : i + chunk_size]
|
||||
chunk = encoding.decode(chunk_tokens)
|
||||
chunks.append(chunk)
|
||||
|
||||
@@ -159,13 +150,9 @@ class RAGManager:
|
||||
chat_history = value["conversation"]
|
||||
questions = value["question"]
|
||||
|
||||
chunks, embeddings = self.create_chunks(
|
||||
chat_history, self.chunk_size
|
||||
)
|
||||
chunks, embeddings = self.create_chunks(chat_history, self.chunk_size)
|
||||
|
||||
for item in tqdm(
|
||||
questions, desc="Answering questions", leave=False
|
||||
):
|
||||
for item in tqdm(questions, desc="Answering questions", leave=False):
|
||||
question = item["question"]
|
||||
answer = item.get("answer", "")
|
||||
category = item["category"]
|
||||
@@ -174,22 +161,20 @@ class RAGManager:
|
||||
context = chunks[0]
|
||||
search_time = 0
|
||||
else:
|
||||
context, search_time = self.search(
|
||||
question, chunks, embeddings, k=self.k
|
||||
)
|
||||
response, response_time = self.generate_response(
|
||||
question, context
|
||||
)
|
||||
context, search_time = self.search(question, chunks, embeddings, k=self.k)
|
||||
response, response_time = self.generate_response(question, context)
|
||||
|
||||
FINAL_RESULTS[key].append({
|
||||
"question": question,
|
||||
"answer": answer,
|
||||
"category": category,
|
||||
"context": context,
|
||||
"response": response,
|
||||
"search_time": search_time,
|
||||
"response_time": response_time,
|
||||
})
|
||||
FINAL_RESULTS[key].append(
|
||||
{
|
||||
"question": question,
|
||||
"answer": answer,
|
||||
"category": category,
|
||||
"context": context,
|
||||
"response": response,
|
||||
"search_time": search_time,
|
||||
"response_time": response_time,
|
||||
}
|
||||
)
|
||||
with open(output_file_path, "w+") as f:
|
||||
json.dump(FINAL_RESULTS, f, indent=4)
|
||||
|
||||
|
||||
+2
-11
@@ -1,12 +1,3 @@
|
||||
TECHNIQUES = [
|
||||
"mem0",
|
||||
"rag",
|
||||
"langmem",
|
||||
"zep",
|
||||
"openai"
|
||||
]
|
||||
TECHNIQUES = ["mem0", "rag", "langmem", "zep", "openai"]
|
||||
|
||||
METHODS = [
|
||||
"add",
|
||||
"search"
|
||||
]
|
||||
METHODS = ["add", "search"]
|
||||
|
||||
@@ -19,12 +19,12 @@ class ZepAdd:
|
||||
self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
with open(self.data_path, 'r') as f:
|
||||
with open(self.data_path, "r") as f:
|
||||
self.data = json.load(f)
|
||||
return self.data
|
||||
|
||||
def process_conversation(self, run_id, item, idx):
|
||||
conversation = item['conversation']
|
||||
conversation = item["conversation"]
|
||||
|
||||
user_id = f"run_id_{run_id}_experiment_user_{idx}"
|
||||
session_id = f"run_id_{run_id}_experiment_session_{idx}"
|
||||
@@ -41,7 +41,7 @@ class ZepAdd:
|
||||
|
||||
print("Starting to add memories... for user", user_id)
|
||||
for key in tqdm(conversation.keys(), desc=f"Processing user {user_id}"):
|
||||
if key in ['speaker_a', 'speaker_b'] or "date" in key:
|
||||
if key in ["speaker_a", "speaker_b"] or "date" in key:
|
||||
continue
|
||||
|
||||
date_time_key = key + "_date_time"
|
||||
@@ -51,11 +51,13 @@ class ZepAdd:
|
||||
for chat in tqdm(chats, desc=f"Adding chats for {key}", leave=False):
|
||||
self.zep_client.memory.add(
|
||||
session_id=session_id,
|
||||
messages=[Message(
|
||||
role=chat['speaker'],
|
||||
role_type="user",
|
||||
content=f"{timestamp}: {chat['text']}",
|
||||
)]
|
||||
messages=[
|
||||
Message(
|
||||
role=chat["speaker"],
|
||||
role_type="user",
|
||||
content=f"{timestamp}: {chat['text']}",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
def process_all_conversations(self, run_id):
|
||||
@@ -71,4 +73,4 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--run_id", type=str, required=True)
|
||||
args = parser.parse_args()
|
||||
zep_add = ZepAdd(data_path="../../dataset/locomo10.json")
|
||||
zep_add.process_all_conversations(args.run_id)
|
||||
zep_add.process_all_conversations(args.run_id)
|
||||
|
||||
@@ -42,9 +42,9 @@ class ZepSearch:
|
||||
return f"{edge.valid_at if edge.valid_at else 'date unknown'} - {(edge.invalid_at if edge.invalid_at else 'present')}"
|
||||
|
||||
def compose_search_context(self, edges: list[EntityEdge], nodes: list[EntityNode]) -> str:
|
||||
facts = [f' - {edge.fact} ({self.format_edge_date_range(edge)})' for edge in edges]
|
||||
entities = [f' - {node.name}: {node.summary}' for node in nodes]
|
||||
return TEMPLATE.format(facts='\n'.join(facts), entities='\n'.join(entities))
|
||||
facts = [f" - {edge.fact} ({self.format_edge_date_range(edge)})" for edge in edges]
|
||||
entities = [f" - {node.name}: {node.summary}" for node in nodes]
|
||||
return TEMPLATE.format(facts="\n".join(facts), entities="\n".join(entities))
|
||||
|
||||
def search_memory(self, run_id, idx, query, max_retries=3, retry_delay=1):
|
||||
start_time = time.time()
|
||||
@@ -52,8 +52,14 @@ class ZepSearch:
|
||||
while retries < max_retries:
|
||||
try:
|
||||
user_id = f"run_id_{run_id}_experiment_user_{idx}"
|
||||
edges_results = (self.zep_client.graph.search(user_id=user_id, reranker='cross_encoder', query=query, scope='edges', limit=20)).edges
|
||||
node_results = (self.zep_client.graph.search(user_id=user_id, reranker='rrf', query=query, scope='nodes', limit=20)).nodes
|
||||
edges_results = (
|
||||
self.zep_client.graph.search(
|
||||
user_id=user_id, reranker="cross_encoder", query=query, scope="edges", limit=20
|
||||
)
|
||||
).edges
|
||||
node_results = (
|
||||
self.zep_client.graph.search(user_id=user_id, reranker="rrf", query=query, scope="nodes", limit=20)
|
||||
).nodes
|
||||
context = self.compose_search_context(edges_results, node_results)
|
||||
break
|
||||
except Exception as e:
|
||||
@@ -68,17 +74,13 @@ class ZepSearch:
|
||||
return context, end_time - start_time
|
||||
|
||||
def process_question(self, run_id, val, idx):
|
||||
question = val.get('question', '')
|
||||
answer = val.get('answer', '')
|
||||
category = val.get('category', -1)
|
||||
evidence = val.get('evidence', [])
|
||||
adversarial_answer = val.get('adversarial_answer', '')
|
||||
question = val.get("question", "")
|
||||
answer = val.get("answer", "")
|
||||
category = val.get("category", -1)
|
||||
evidence = val.get("evidence", [])
|
||||
adversarial_answer = val.get("adversarial_answer", "")
|
||||
|
||||
response, search_memory_time, response_time, context = self.answer_question(
|
||||
run_id,
|
||||
idx,
|
||||
question
|
||||
)
|
||||
response, search_memory_time, response_time, context = self.answer_question(run_id, idx, question)
|
||||
|
||||
result = {
|
||||
"question": question,
|
||||
@@ -89,7 +91,7 @@ class ZepSearch:
|
||||
"adversarial_answer": adversarial_answer,
|
||||
"search_memory_time": search_memory_time,
|
||||
"response_time": response_time,
|
||||
"context": context
|
||||
"context": context,
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -98,44 +100,35 @@ class ZepSearch:
|
||||
context, search_memory_time = self.search_memory(run_id, idx, question)
|
||||
|
||||
template = Template(ANSWER_PROMPT_ZEP)
|
||||
answer_prompt = template.render(
|
||||
memories=context,
|
||||
question=question
|
||||
)
|
||||
answer_prompt = template.render(memories=context, question=question)
|
||||
|
||||
t1 = time.time()
|
||||
response = self.openai_client.chat.completions.create(
|
||||
model=os.getenv("MODEL"),
|
||||
messages=[
|
||||
{"role": "system", "content": answer_prompt}
|
||||
],
|
||||
temperature=0.0
|
||||
model=os.getenv("MODEL"), messages=[{"role": "system", "content": answer_prompt}], temperature=0.0
|
||||
)
|
||||
t2 = time.time()
|
||||
response_time = t2 - t1
|
||||
return response.choices[0].message.content, search_memory_time, response_time, context
|
||||
|
||||
def process_data_file(self, file_path, run_id, output_file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
for idx, item in tqdm(enumerate(data), total=len(data), desc="Processing conversations"):
|
||||
qa = item['qa']
|
||||
qa = item["qa"]
|
||||
|
||||
for question_item in tqdm(qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False):
|
||||
result = self.process_question(
|
||||
run_id,
|
||||
question_item,
|
||||
idx
|
||||
)
|
||||
for question_item in tqdm(
|
||||
qa, total=len(qa), desc=f"Processing questions for conversation {idx}", leave=False
|
||||
):
|
||||
result = self.process_question(run_id, question_item, idx)
|
||||
self.results[idx].append(result)
|
||||
|
||||
# Save results after each question is processed
|
||||
with open(output_file_path, 'w') as f:
|
||||
with open(output_file_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
# Final save at the end
|
||||
with open(output_file_path, 'w') as f:
|
||||
with open(output_file_path, "w") as f:
|
||||
json.dump(self.results, f, indent=4)
|
||||
|
||||
|
||||
|
||||
@@ -56,9 +56,7 @@
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = (\n",
|
||||
" \"\"\n",
|
||||
")"
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -149,7 +147,7 @@
|
||||
" \"role\": \"assistant\",\n",
|
||||
" \"content\": \"Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future.\",\n",
|
||||
" },\n",
|
||||
"]\n"
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -166,9 +164,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Store inferred memories (default behavior)\n",
|
||||
"result = m.add(\n",
|
||||
" messages, user_id=\"alice\", metadata={\"category\": \"movie_recommendations\"}\n",
|
||||
")"
|
||||
"result = m.add(messages, user_id=\"alice\", metadata={\"category\": \"movie_recommendations\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -20,19 +20,19 @@ agent = Agent(
|
||||
name="Fitness Agent",
|
||||
model=OpenAIChat(id="gpt-4o"),
|
||||
description="You are a helpful fitness assistant who remembers past logs and gives personalized suggestions for Anish's training and diet.",
|
||||
markdown=True
|
||||
markdown=True,
|
||||
)
|
||||
|
||||
|
||||
# Store user preferences as memory
|
||||
def store_user_preferences(conversation: list, user_id: str = USER_ID):
|
||||
"""Store user preferences from conversation history"""
|
||||
memory_client.add(conversation, user_id=user_id, output_format='v1.1')
|
||||
memory_client.add(conversation, user_id=user_id, output_format="v1.1")
|
||||
|
||||
|
||||
# Memory-aware assistant function
|
||||
def fitness_coach(user_input: str, user_id: str = USER_ID):
|
||||
memories = memory_client.search(user_input, user_id=user_id) # Search relevant memories bases on user query
|
||||
memories = memory_client.search(user_input, user_id=user_id) # Search relevant memories bases on user query
|
||||
memory_context = "\n".join(f"- {m['memory']}" for m in memories)
|
||||
|
||||
prompt = f"""You are a fitness assistant who helps Anish with his training, recovery, and diet. You have long-term memory of his health, routines, preferences, and past conversations.
|
||||
@@ -48,113 +48,66 @@ User query:
|
||||
memory_client.add(f"User: {user_input}\nAssistant: {response.content}", user_id=user_id)
|
||||
return response.content
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# Store user preferences and memories
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hi, I’m Anish. I'm 26 years old, 5'10\", and weigh 72kg. I started working out 6 months ago with the goal of building lean muscle."
|
||||
"content": "Hi, I’m Anish. I'm 26 years old, 5'10\", and weigh 72kg. I started working out 6 months ago with the goal of building lean muscle.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Got it — you're 26, 5'10\", 72kg, and on a lean muscle journey. Started gym 6 months ago."
|
||||
"content": "Got it — you're 26, 5'10\", 72kg, and on a lean muscle journey. Started gym 6 months ago.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I follow a push-pull-legs routine and train 5 times a week. My rest days are Wednesday and Sunday."
|
||||
"content": "I follow a push-pull-legs routine and train 5 times a week. My rest days are Wednesday and Sunday.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Understood — push-pull-legs split, training 5x/week with rest on Wednesdays and Sundays."
|
||||
"content": "Understood — push-pull-legs split, training 5x/week with rest on Wednesdays and Sundays.",
|
||||
},
|
||||
{"role": "user", "content": "After push days, I usually eat high-protein and moderate-carb meals to recover."},
|
||||
{"role": "assistant", "content": "Noted — high-protein, moderate-carb meals after push workouts."},
|
||||
{"role": "user", "content": "For pull days, I take whey protein and eat a banana after training."},
|
||||
{"role": "assistant", "content": "Logged — whey protein and banana post pull workouts."},
|
||||
{"role": "user", "content": "On leg days, I make sure to have complex carbs like rice or oats."},
|
||||
{"role": "assistant", "content": "Noted — complex carbs like rice and oats are part of your leg day meals."},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "After push days, I usually eat high-protein and moderate-carb meals to recover."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Noted — high-protein, moderate-carb meals after push workouts."
|
||||
"content": "I often feel sore after leg days, so I use turmeric milk and magnesium to help with recovery.",
|
||||
},
|
||||
{"role": "assistant", "content": "I'll remember turmeric milk and magnesium as part of your leg day recovery."},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "For pull days, I take whey protein and eat a banana after training."
|
||||
"content": "Last push day, I did 3x8 bench press at 60kg, 4x12 overhead press, and dips. Felt fatigued after.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Logged — whey protein and banana post pull workouts."
|
||||
"content": "Push day logged — 60kg bench, overhead press, dips. You felt fatigued afterward.",
|
||||
},
|
||||
{"role": "user", "content": "I prefer light dinners post-workout like tofu, soup, and vegetables."},
|
||||
{"role": "assistant", "content": "Got it — light dinners post-workout: tofu, soup, and veggies."},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "On leg days, I make sure to have complex carbs like rice or oats."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Noted — complex carbs like rice and oats are part of your leg day meals."
|
||||
"content": "I have mild lactose intolerance, so I avoid dairy. I use almond milk or lactose-free whey.",
|
||||
},
|
||||
{"role": "assistant", "content": "Understood — avoiding regular dairy, using almond milk and lactose-free whey."},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I often feel sore after leg days, so I use turmeric milk and magnesium to help with recovery."
|
||||
"content": "I get occasional knee pain, so I avoid deep squats and do more hamstring curls and glute bridges on leg days.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I'll remember turmeric milk and magnesium as part of your leg day recovery."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Last push day, I did 3x8 bench press at 60kg, 4x12 overhead press, and dips. Felt fatigued after."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Push day logged — 60kg bench, overhead press, dips. You felt fatigued afterward."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I prefer light dinners post-workout like tofu, soup, and vegetables."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Got it — light dinners post-workout: tofu, soup, and veggies."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I have mild lactose intolerance, so I avoid dairy. I use almond milk or lactose-free whey."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Understood — avoiding regular dairy, using almond milk and lactose-free whey."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I get occasional knee pain, so I avoid deep squats and do more hamstring curls and glute bridges on leg days."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Noted — due to knee discomfort, you substitute deep squats with curls and glute bridges."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I track sleep and notice poor performance when I sleep less than 6 hours."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Logged — performance drops when you get under 6 hours of sleep."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I take magnesium supplements to help with muscle recovery and sleep quality."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Remembered — magnesium helps you with recovery and sleep."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I avoid caffeine after 4 PM because it affects my sleep."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Got it — you avoid caffeine post-4 PM to protect your sleep."
|
||||
"content": "Noted — due to knee discomfort, you substitute deep squats with curls and glute bridges.",
|
||||
},
|
||||
{"role": "user", "content": "I track sleep and notice poor performance when I sleep less than 6 hours."},
|
||||
{"role": "assistant", "content": "Logged — performance drops when you get under 6 hours of sleep."},
|
||||
{"role": "user", "content": "I take magnesium supplements to help with muscle recovery and sleep quality."},
|
||||
{"role": "assistant", "content": "Remembered — magnesium helps you with recovery and sleep."},
|
||||
{"role": "user", "content": "I avoid caffeine after 4 PM because it affects my sleep."},
|
||||
{"role": "assistant", "content": "Got it — you avoid caffeine post-4 PM to protect your sleep."},
|
||||
]
|
||||
store_user_preferences(messages)
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import asyncio
|
||||
import warnings
|
||||
|
||||
from google.adk.agents import Agent
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.genai import types
|
||||
|
||||
from mem0 import MemoryClient
|
||||
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
@@ -19,14 +21,14 @@ def save_patient_info(information: str) -> dict:
|
||||
print(f"Storing patient information: {information[:30]}...")
|
||||
|
||||
# Get user_id from session state or use default
|
||||
user_id = getattr(save_patient_info, 'user_id', 'default_user')
|
||||
user_id = getattr(save_patient_info, "user_id", "default_user")
|
||||
|
||||
# Store in Mem0
|
||||
response = mem0_client.add(
|
||||
mem0_client.add(
|
||||
[{"role": "user", "content": information}],
|
||||
user_id=user_id,
|
||||
run_id="healthcare_session",
|
||||
metadata={"type": "patient_information"}
|
||||
metadata={"type": "patient_information"},
|
||||
)
|
||||
|
||||
return {"status": "success", "message": "Information saved"}
|
||||
@@ -37,7 +39,7 @@ def retrieve_patient_info(query: str) -> str:
|
||||
print(f"Searching for patient information: {query}")
|
||||
|
||||
# Get user_id from session state or use default
|
||||
user_id = getattr(retrieve_patient_info, 'user_id', 'default_user')
|
||||
user_id = getattr(retrieve_patient_info, "user_id", "default_user")
|
||||
|
||||
# Search Mem0
|
||||
results = mem0_client.search(
|
||||
@@ -45,7 +47,7 @@ def retrieve_patient_info(query: str) -> str:
|
||||
user_id=user_id,
|
||||
run_id="healthcare_session",
|
||||
limit=5,
|
||||
threshold=0.7 # Higher threshold for more relevant results
|
||||
threshold=0.7, # Higher threshold for more relevant results
|
||||
)
|
||||
|
||||
if not results:
|
||||
@@ -65,7 +67,7 @@ def schedule_appointment(date: str, time: str, reason: str) -> dict:
|
||||
"status": "success",
|
||||
"appointment_id": appointment_id,
|
||||
"confirmation": f"Appointment scheduled for {date} at {time} for {reason}",
|
||||
"message": "Please arrive 15 minutes early to complete paperwork."
|
||||
"message": "Please arrive 15 minutes early to complete paperwork.",
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +91,7 @@ IMPORTANT GUIDELINES:
|
||||
- For serious symptoms, always recommend consulting a healthcare professional.
|
||||
- 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
|
||||
@@ -101,18 +103,10 @@ USER_ID = "Alex"
|
||||
SESSION_ID = "session_001"
|
||||
|
||||
# Create a session
|
||||
session = session_service.create_session(
|
||||
app_name=APP_NAME,
|
||||
user_id=USER_ID,
|
||||
session_id=SESSION_ID
|
||||
)
|
||||
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
|
||||
|
||||
# Create the runner
|
||||
runner = Runner(
|
||||
agent=healthcare_agent,
|
||||
app_name=APP_NAME,
|
||||
session_service=session_service
|
||||
)
|
||||
runner = Runner(agent=healthcare_agent, app_name=APP_NAME, session_service=session_service)
|
||||
|
||||
|
||||
# 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}")
|
||||
|
||||
# Format the user's message
|
||||
content = types.Content(
|
||||
role='user',
|
||||
parts=[types.Part(text=query)]
|
||||
)
|
||||
content = types.Content(role="user", parts=[types.Part(text=query)])
|
||||
|
||||
# Set user_id for tools to access
|
||||
save_patient_info.user_id = user_id
|
||||
retrieve_patient_info.user_id = user_id
|
||||
|
||||
# Run the agent
|
||||
async for event in runner.run_async(
|
||||
user_id=user_id,
|
||||
session_id=session_id,
|
||||
new_message=content
|
||||
):
|
||||
async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=content):
|
||||
if event.is_final_response():
|
||||
if event.content and event.content.parts:
|
||||
response = event.content.parts[0].text
|
||||
@@ -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.",
|
||||
runner=runner,
|
||||
user_id=USER_ID,
|
||||
session_id=SESSION_ID
|
||||
session_id=SESSION_ID,
|
||||
)
|
||||
|
||||
# Request for health information
|
||||
@@ -160,7 +147,7 @@ async def run_conversation():
|
||||
"Can you tell me more about what might be causing my headaches?",
|
||||
runner=runner,
|
||||
user_id=USER_ID,
|
||||
session_id=SESSION_ID
|
||||
session_id=SESSION_ID,
|
||||
)
|
||||
|
||||
# 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?",
|
||||
runner=runner,
|
||||
user_id=USER_ID,
|
||||
session_id=SESSION_ID
|
||||
session_id=SESSION_ID,
|
||||
)
|
||||
|
||||
# Test memory - should remember patient name, symptoms, and allergy
|
||||
await call_agent_async(
|
||||
"What medications should I avoid for my headaches?",
|
||||
runner=runner,
|
||||
user_id=USER_ID,
|
||||
session_id=SESSION_ID
|
||||
"What medications should I avoid for my headaches?", 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}"
|
||||
|
||||
# Create session for this user
|
||||
session = session_service.create_session(
|
||||
app_name=APP_NAME,
|
||||
user_id=patient_id,
|
||||
session_id=session_id
|
||||
)
|
||||
session_service.create_session(app_name=APP_NAME, user_id=patient_id, session_id=session_id)
|
||||
|
||||
print(f"\nStarting conversation with patient ID: {patient_id}")
|
||||
print("Type your message and press Enter.")
|
||||
|
||||
while True:
|
||||
user_input = input("\n>>> Patient: ").strip()
|
||||
if user_input.lower() in ['exit', 'quit', 'bye']:
|
||||
if user_input.lower() in ["exit", "quit", "bye"]:
|
||||
print("Ending conversation. Thank you!")
|
||||
break
|
||||
|
||||
await call_agent_async(
|
||||
user_input,
|
||||
runner=runner,
|
||||
user_id=patient_id,
|
||||
session_id=session_id
|
||||
)
|
||||
await call_agent_async(user_input, runner=runner, user_id=patient_id, session_id=session_id)
|
||||
|
||||
|
||||
# Main execution
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Healthcare Assistant with Memory')
|
||||
parser.add_argument('--demo', action='store_true', help='Run the demo conversation')
|
||||
parser.add_argument('--interactive', action='store_true', help='Run in interactive mode')
|
||||
parser.add_argument('--patient-id', type=str, default=USER_ID, help='Patient ID for the conversation')
|
||||
parser = argparse.ArgumentParser(description="Healthcare Assistant with Memory")
|
||||
parser.add_argument("--demo", action="store_true", help="Run the demo conversation")
|
||||
parser.add_argument("--interactive", action="store_true", help="Run in interactive mode")
|
||||
parser.add_argument("--patient-id", type=str, default=USER_ID, help="Patient ID for the conversation")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.demo:
|
||||
@@ -231,5 +206,3 @@ if __name__ == "__main__":
|
||||
else:
|
||||
# Default to demo mode if no arguments provided
|
||||
asyncio.run(run_conversation())
|
||||
|
||||
|
||||
|
||||
@@ -16,26 +16,21 @@ from mem0 import Memory
|
||||
|
||||
# Configure Mem0 with Grok 3 and Qdrant
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"embedding_model_dims": 384
|
||||
}
|
||||
},
|
||||
"vector_store": {"provider": "qdrant", "config": {"embedding_model_dims": 384}},
|
||||
"llm": {
|
||||
"provider": "xai",
|
||||
"config": {
|
||||
"model": "grok-3-beta",
|
||||
"temperature": 0.1,
|
||||
"max_tokens": 2000,
|
||||
}
|
||||
},
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "huggingface",
|
||||
"config": {
|
||||
"model": "all-MiniLM-L6-v2" # open embedding model
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Instantiate memory layer
|
||||
@@ -57,20 +52,14 @@ def recommend_movie_with_memory(user_id: str, user_query: str):
|
||||
prompt += f"\nPreviously, the user mentioned: {past_memories}"
|
||||
|
||||
# Generate movie recommendation using Grok 3
|
||||
response = grok_client.chat.completions.create(
|
||||
model="grok-3-beta",
|
||||
messages=[
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
)
|
||||
response = grok_client.chat.completions.create(model="grok-3-beta", messages=[{"role": "user", "content": prompt}])
|
||||
recommendation = response.choices[0].message.content
|
||||
|
||||
# Store conversation in memory
|
||||
memory.add(
|
||||
[{"role": "user", "content": user_query},
|
||||
{"role": "assistant", "content": recommendation}],
|
||||
[{"role": "user", "content": user_query}, {"role": "assistant", "content": recommendation}],
|
||||
user_id=user_id,
|
||||
metadata={"category": "movie"}
|
||||
metadata={"category": "movie"},
|
||||
)
|
||||
|
||||
return recommendation
|
||||
@@ -81,10 +70,11 @@ if __name__ == "__main__":
|
||||
user_id = "arshi"
|
||||
recommend_movie_with_memory(user_id, "I'm looking for a movie to watch tonight. Any suggestions?")
|
||||
# OUTPUT: You have watched Intersteller last weekend and you don't like horror movies, maybe you can watch "Purple Hearts" today.
|
||||
recommend_movie_with_memory(user_id, "Can we skip the tearjerkers? I really enjoyed Notting Hill and Crazy Rich Asians.")
|
||||
recommend_movie_with_memory(
|
||||
user_id, "Can we skip the tearjerkers? I really enjoyed Notting Hill and Crazy Rich Asians."
|
||||
)
|
||||
# OUTPUT: Got it — no sad endings! You might enjoy "The Proposal" or "Love, Rosie". They’re both light-hearted romcoms with happy vibes.
|
||||
recommend_movie_with_memory(user_id, "Any light-hearted movie I can watch after work today?")
|
||||
# OUTPUT: Since you liked Crazy Rich Asians and The Proposal, how about "The Intern" or "Isn’t It Romantic"? Both are upbeat, funny, and perfect for relaxing.
|
||||
recommend_movie_with_memory(user_id, "I’ve already watched The Intern. Something new maybe?")
|
||||
# OUTPUT: No problem! Try "Your Place or Mine" - romcoms that match your taste and are tear-free!
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ agent = Agent(
|
||||
name="Personal Agent",
|
||||
model=OpenAIChat(id="gpt-4o"),
|
||||
description="You are a helpful personal agent that helps me with day to day activities."
|
||||
"You can process both text and images.",
|
||||
markdown=True
|
||||
"You can process both text and images.",
|
||||
markdown=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,24 +35,16 @@ def chat_user(user_input: str = None, user_id: str = "user_123", image_path: str
|
||||
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
|
||||
# First: the text message
|
||||
text_msg = {
|
||||
"role": "user",
|
||||
"content": user_input
|
||||
}
|
||||
text_msg = {"role": "user", "content": user_input}
|
||||
|
||||
# Second: the image message
|
||||
image_msg = {
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||
}
|
||||
}
|
||||
"content": {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}},
|
||||
}
|
||||
|
||||
# Send both as separate message objects
|
||||
client.add([text_msg, image_msg], user_id=user_id, output_format='v1.1')
|
||||
client.add([text_msg, image_msg], user_id=user_id, output_format="v1.1")
|
||||
print("✅ Image uploaded and stored in memory.")
|
||||
|
||||
if user_input:
|
||||
@@ -92,10 +84,13 @@ print(chat_user("When is my test?", user_id=user_id))
|
||||
# OUTPUT: Your pilot's test is on your birthday, which is in five days. You're turning 25!
|
||||
# Good luck with your preparations, and remember to take some time to relax amidst the studying.
|
||||
|
||||
print(chat_user("This is the picture of what I brought with me in the trip to Bahamas",
|
||||
image_path="travel_items.jpeg", # this will be added to Mem0 memory
|
||||
user_id=user_id))
|
||||
print(chat_user("hey can you quickly tell me if brought my sunglasses to my trip, not able to find",
|
||||
user_id=user_id))
|
||||
print(
|
||||
chat_user(
|
||||
"This is the picture of what I brought with me in the trip to Bahamas",
|
||||
image_path="travel_items.jpeg", # this will be added to Mem0 memory
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
print(chat_user("hey can you quickly tell me if brought my sunglasses to my trip, not able to find", user_id=user_id))
|
||||
# OUTPUT: Yes, you did bring your sunglasses on your trip to the Bahamas along with your laptop, face masks and other items..
|
||||
# Since you can't find them now, perhaps check the pockets of jackets you wore or in your luggage compartments.
|
||||
|
||||
@@ -7,6 +7,7 @@ In order to run this file, you need to set up your Mem0 API at Mem0 platform and
|
||||
export OPENAI_API_KEY="your_openai_api_key"
|
||||
export MEM0_API_KEY="your_mem0_api_key"
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner
|
||||
@@ -23,25 +24,19 @@ study_agent = Agent(
|
||||
- Identify topics the user has struggled with (e.g., "I'm confused", "this is hard")
|
||||
- Help with spaced repetition by suggesting topics to revisit based on last review time
|
||||
- Personalize answers using stored memories
|
||||
- Summarize PDFs or notes the user uploads""")
|
||||
- Summarize PDFs or notes the user uploads""",
|
||||
)
|
||||
|
||||
|
||||
# Upload and store PDF to Mem0
|
||||
def upload_pdf(pdf_url: str, user_id: str):
|
||||
pdf_message = {
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "pdf_url",
|
||||
"pdf_url": {"url": pdf_url}
|
||||
}
|
||||
}
|
||||
pdf_message = {"role": "user", "content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}}}
|
||||
client.add([pdf_message], user_id=user_id)
|
||||
print("✅ PDF uploaded and processed into memory.")
|
||||
|
||||
|
||||
# Main interaction loop with your personal study buddy
|
||||
async def study_buddy(user_id: str, topic: str, user_input: str):
|
||||
|
||||
memories = client.search(f"{topic}", user_id=user_id)
|
||||
memory_context = "n".join(f"- {m['memory']}" for m in memories)
|
||||
|
||||
@@ -56,9 +51,11 @@ Now respond to the user's new question or comment:
|
||||
result = await Runner.run(study_agent, prompt)
|
||||
response = result.final_output
|
||||
|
||||
client.add([
|
||||
{"role": "user", "content": f'''Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}'''}
|
||||
], user_id=user_id, metadata={"topic": topic})
|
||||
client.add(
|
||||
[{"role": "user", "content": f"""Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}"""}],
|
||||
user_id=user_id,
|
||||
metadata={"topic": topic},
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -78,7 +75,12 @@ async def main():
|
||||
|
||||
# Demonstrate spaced repetition prompting
|
||||
topic = "Momentum Conservation"
|
||||
print(await study_buddy(user_id, topic, "I think we covered this last week. Is it time to review momentum conservation again?"))
|
||||
print(
|
||||
await study_buddy(
|
||||
user_id, topic, "I think we covered this last week. Is it time to review momentum conservation again?"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -57,7 +57,7 @@ def initialize_memory():
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I prefer brief and concise responses without unnecessary explanations. I get frustrated when assistants are too wordy or repeat information I already know."
|
||||
"content": "I prefer brief and concise responses without unnecessary explanations. I get frustrated when assistants are too wordy or repeat information I already know.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
@@ -65,7 +65,7 @@ def initialize_memory():
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I like to listen to jazz music when I'm working, especially artists like Miles Davis and John Coltrane. I find it helps me focus and be more productive."
|
||||
"content": "I like to listen to jazz music when I'm working, especially artists like Miles Davis and John Coltrane. I find it helps me focus and be more productive.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
@@ -73,7 +73,7 @@ def initialize_memory():
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I usually wake up at 7 AM and prefer reminders for meetings 30 minutes in advance. My most productive hours are between 9 AM and noon, so I try to schedule important tasks during that time."
|
||||
"content": "I usually wake up at 7 AM and prefer reminders for meetings 30 minutes in advance. My most productive hours are between 9 AM and noon, so I try to schedule important tasks during that time.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
@@ -81,7 +81,7 @@ def initialize_memory():
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "My favorite color is navy blue, and I prefer dark mode in all my apps. I'm allergic to peanuts, so please remind me to check ingredients when I ask about recipes or restaurants."
|
||||
"content": "My favorite color is navy blue, and I prefer dark mode in all my apps. I'm allergic to peanuts, so please remind me to check ingredients when I ask about recipes or restaurants.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
@@ -89,7 +89,7 @@ def initialize_memory():
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "My partner's name is Jamie, and we have a golden retriever named Max who is 3 years old. My parents live in Chicago, and I try to visit them once every two months."
|
||||
"content": "My partner's name is Jamie, and we have a golden retriever named Max who is 3 years old. My parents live in Chicago, and I try to visit them once every two months.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
@@ -135,11 +135,11 @@ def record_audio(filename="input.wav", record_seconds=5):
|
||||
stream.close()
|
||||
p.terminate()
|
||||
|
||||
with wave.open(filename, 'wb') as wf:
|
||||
with wave.open(filename, "wb") as wf:
|
||||
wf.setnchannels(channels)
|
||||
wf.setsampwidth(p.get_sample_size(fmt))
|
||||
wf.setframerate(rate)
|
||||
wf.writeframes(b''.join(frames))
|
||||
wf.writeframes(b"".join(frames))
|
||||
|
||||
|
||||
# ------------------ STT USING WHISPER ------------------
|
||||
@@ -147,10 +147,7 @@ def transcribe_whisper(audio_path):
|
||||
print("🔎 Transcribing with Whisper...")
|
||||
try:
|
||||
with open(audio_path, "rb") as audio_file:
|
||||
transcript = openai_client.audio.transcriptions.create(
|
||||
model="whisper-1",
|
||||
file=audio_file
|
||||
)
|
||||
transcript = openai_client.audio.transcriptions.create(model="whisper-1", file=audio_file)
|
||||
print(f"🗣️ You said: {transcript.text}")
|
||||
return transcript.text
|
||||
except Exception as e:
|
||||
@@ -165,9 +162,7 @@ def get_agent_response(user_input):
|
||||
|
||||
try:
|
||||
task = Task(
|
||||
description=f"Respond to: {user_input}",
|
||||
expected_output="A short and relevant reply.",
|
||||
agent=voice_agent
|
||||
description=f"Respond to: {user_input}", expected_output="A short and relevant reply.", agent=voice_agent
|
||||
)
|
||||
crew = Crew(
|
||||
agents=[voice_agent],
|
||||
@@ -175,22 +170,19 @@ def get_agent_response(user_input):
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
memory=True,
|
||||
memory_config={
|
||||
"provider": "mem0",
|
||||
"config": {"user_id": USER_ID}
|
||||
}
|
||||
memory_config={"provider": "mem0", "config": {"user_id": USER_ID}},
|
||||
)
|
||||
result = crew.kickoff()
|
||||
|
||||
# Extract the text response from the complex result object
|
||||
if hasattr(result, 'raw'):
|
||||
if hasattr(result, "raw"):
|
||||
return result.raw
|
||||
elif isinstance(result, dict) and 'raw' in result:
|
||||
return result['raw']
|
||||
elif isinstance(result, dict) and 'tasks_output' in result:
|
||||
outputs = result['tasks_output']
|
||||
elif isinstance(result, dict) and "raw" in result:
|
||||
return result["raw"]
|
||||
elif isinstance(result, dict) and "tasks_output" in result:
|
||||
outputs = result["tasks_output"]
|
||||
if outputs and isinstance(outputs, list) and len(outputs) > 0:
|
||||
return outputs[0].get('raw', str(result))
|
||||
return outputs[0].get("raw", str(result))
|
||||
|
||||
# Fallback to string representation if we can't extract the raw response
|
||||
return str(result)
|
||||
@@ -204,10 +196,7 @@ def get_agent_response(user_input):
|
||||
def speak_response(text):
|
||||
print(f"🤖 Agent: {text}")
|
||||
audio = tts_client.text_to_speech.convert(
|
||||
text=text,
|
||||
voice_id="JBFqnCBsd6RMkjVDRZzb",
|
||||
model_id="eleven_multilingual_v2",
|
||||
output_format="mp3_44100_128"
|
||||
text=text, voice_id="JBFqnCBsd6RMkjVDRZzb", model_id="eleven_multilingual_v2", output_format="mp3_44100_128"
|
||||
)
|
||||
play(audio)
|
||||
|
||||
@@ -220,7 +209,7 @@ def run_voice_agent():
|
||||
record_audio(tmp_audio.name)
|
||||
try:
|
||||
user_text = transcribe_whisper(tmp_audio.name)
|
||||
if user_text.lower() in ['exit', 'quit', 'stop']:
|
||||
if user_text.lower() in ["exit", "quit", "stop"]:
|
||||
print("👋 Exiting.")
|
||||
break
|
||||
response = get_agent_response(user_text)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "2.1.25",
|
||||
"version": "2.1.29",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -98,7 +98,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": "^0.40.1",
|
||||
"@google/genai": "^0.7.0",
|
||||
"@google/genai": "^1.2.0",
|
||||
"@mistralai/mistralai": "^1.5.2",
|
||||
"@qdrant/js-client-rest": "1.13.0",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
|
||||
Generated
+672
-10
File diff suppressed because it is too large
Load Diff
@@ -159,16 +159,9 @@ export default class MemoryClient {
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
_preparePayload(
|
||||
messages: string | Array<Message>,
|
||||
options: MemoryOptions,
|
||||
): object {
|
||||
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
|
||||
const payload: any = {};
|
||||
if (typeof messages === "string") {
|
||||
payload.messages = [{ role: "user", content: messages }];
|
||||
} else if (Array.isArray(messages)) {
|
||||
payload.messages = messages;
|
||||
}
|
||||
payload.messages = messages;
|
||||
return { ...payload, ...options };
|
||||
}
|
||||
|
||||
@@ -217,7 +210,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
async add(
|
||||
messages: string | Array<Message>,
|
||||
messages: Array<Message>,
|
||||
options: MemoryOptions = {},
|
||||
): Promise<Array<Memory>> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface MemoryOptions {
|
||||
custom_instructions?: string;
|
||||
timestamp?: number;
|
||||
output_format?: string | OutputFormat;
|
||||
async_mode?: boolean;
|
||||
}
|
||||
|
||||
export interface ProjectOptions {
|
||||
@@ -53,7 +54,7 @@ export interface MultiModalMessages {
|
||||
}
|
||||
|
||||
export interface Messages {
|
||||
role: string;
|
||||
role: "user" | "assistant";
|
||||
content: string | MultiModalMessages;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
|
||||
|
||||
let version = "2.1.16";
|
||||
let version = "2.1.26";
|
||||
|
||||
// Safely check for process.env in different environments
|
||||
let MEM0_TELEMETRY = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
|
||||
llm: {
|
||||
provider: "openai",
|
||||
config: {
|
||||
baseURL: "https://api.openai.com/v1",
|
||||
apiKey: process.env.OPENAI_API_KEY || "",
|
||||
model: "gpt-4-turbo-preview",
|
||||
modelProperties: undefined,
|
||||
|
||||
@@ -80,6 +80,7 @@ export class ConfigManager {
|
||||
}
|
||||
|
||||
return {
|
||||
baseURL: userConf?.baseURL || defaultConf.baseURL,
|
||||
apiKey:
|
||||
userConf?.apiKey !== undefined
|
||||
? userConf.apiKey
|
||||
|
||||
@@ -7,7 +7,10 @@ export class OpenAILLM implements LLM {
|
||||
private model: string;
|
||||
|
||||
constructor(config: LLMConfig) {
|
||||
this.openai = new OpenAI({ apiKey: config.apiKey });
|
||||
this.openai = new OpenAI({
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseURL,
|
||||
});
|
||||
this.model = config.model || "gpt-4o-mini";
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface HistoryStoreConfig {
|
||||
|
||||
export interface LLMConfig {
|
||||
provider?: string;
|
||||
baseURL?: string;
|
||||
config?: Record<string, any>;
|
||||
apiKey?: string;
|
||||
model?: string | any;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
TelemetryEventData,
|
||||
} from "./telemetry.types";
|
||||
|
||||
let version = "2.1.16";
|
||||
let version = "2.1.26";
|
||||
|
||||
// Safely check for process.env in different environments
|
||||
let MEM0_TELEMETRY = true;
|
||||
|
||||
+74
-44
@@ -3,7 +3,7 @@ import logging
|
||||
import os
|
||||
import warnings
|
||||
from functools import wraps
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
@@ -95,10 +95,7 @@ class MemoryClient:
|
||||
self.client = client
|
||||
# Ensure the client has the correct base_url and headers
|
||||
self.client.base_url = httpx.URL(self.host)
|
||||
self.client.headers.update({
|
||||
"Authorization": f"Token {self.api_key}",
|
||||
"Mem0-User-ID": self.user_id
|
||||
})
|
||||
self.client.headers.update({"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id})
|
||||
else:
|
||||
self.client = httpx.Client(
|
||||
base_url=self.host,
|
||||
@@ -132,11 +129,11 @@ class MemoryClient:
|
||||
raise ValueError(f"Error: {error_message}")
|
||||
|
||||
@api_error_handler
|
||||
def add(self, messages: Union[str, List[Dict[str, str]]], **kwargs) -> Dict[str, Any]:
|
||||
def add(self, messages: List[Dict[str, str]], **kwargs) -> Dict[str, Any]:
|
||||
"""Add a new memory.
|
||||
|
||||
Args:
|
||||
messages: Either a string message or a list of message dictionaries.
|
||||
messages: A list of message dictionaries.
|
||||
**kwargs: Additional parameters such as user_id, agent_id, app_id, metadata, filters.
|
||||
|
||||
Returns:
|
||||
@@ -237,7 +234,9 @@ class MemoryClient:
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
capture_client_event("client.search", self, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
capture_client_event(
|
||||
"client.search", self, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -357,10 +356,7 @@ class MemoryClient:
|
||||
else:
|
||||
entities = self.users()
|
||||
# Filter entities based on provided IDs using list comprehension
|
||||
to_delete = [
|
||||
{"type": entity["type"], "name": entity["name"]}
|
||||
for entity in entities["results"]
|
||||
]
|
||||
to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
|
||||
|
||||
params = self._prepare_params()
|
||||
|
||||
@@ -373,7 +369,9 @@ class MemoryClient:
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event(
|
||||
"client.delete_users", self, {"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "sync"}
|
||||
"client.delete_users",
|
||||
self,
|
||||
{"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "sync"},
|
||||
)
|
||||
return {
|
||||
"message": "Entity deleted successfully."
|
||||
@@ -454,7 +452,9 @@ class MemoryClient:
|
||||
"""
|
||||
response = self.client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
capture_client_event(
|
||||
"client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "sync"}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -504,12 +504,17 @@ class MemoryClient:
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Update the project settings.
|
||||
|
||||
Args:
|
||||
custom_instructions: New instructions for the project
|
||||
custom_categories: New categories for the project
|
||||
retrieval_criteria: New retrieval criteria for the project
|
||||
enable_graph: Enable or disable the graph for the project
|
||||
version: Version of the project
|
||||
|
||||
Returns:
|
||||
Dictionary containing the API response.
|
||||
@@ -521,13 +526,19 @@ class MemoryClient:
|
||||
if not (self.org_id and self.project_id):
|
||||
raise ValueError("org_id and project_id must be set to update instructions or categories")
|
||||
|
||||
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
|
||||
if custom_instructions is None and custom_categories is None and retrieval_criteria is None and enable_graph is None and version is None:
|
||||
raise ValueError(
|
||||
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
}
|
||||
)
|
||||
response = self.client.patch(
|
||||
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
|
||||
@@ -537,7 +548,14 @@ class MemoryClient:
|
||||
capture_client_event(
|
||||
"client.update_project",
|
||||
self,
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "sync"},
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
"sync_type": "sync",
|
||||
},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -658,7 +676,7 @@ class MemoryClient:
|
||||
return response.json()
|
||||
|
||||
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]:
|
||||
"""Prepare the payload for API requests.
|
||||
|
||||
@@ -670,10 +688,7 @@ class MemoryClient:
|
||||
A dictionary containing the prepared payload.
|
||||
"""
|
||||
payload = {}
|
||||
if isinstance(messages, str):
|
||||
payload["messages"] = [{"role": "user", "content": messages}]
|
||||
elif isinstance(messages, list):
|
||||
payload["messages"] = messages
|
||||
payload["messages"] = messages
|
||||
|
||||
payload.update({k: v for k, v in kwargs.items() if v is not None})
|
||||
return payload
|
||||
@@ -750,10 +765,7 @@ class AsyncMemoryClient:
|
||||
self.async_client = client
|
||||
# Ensure the client has the correct base_url and headers
|
||||
self.async_client.base_url = httpx.URL(self.host)
|
||||
self.async_client.headers.update({
|
||||
"Authorization": f"Token {self.api_key}",
|
||||
"Mem0-User-ID": self.user_id
|
||||
})
|
||||
self.async_client.headers.update({"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id})
|
||||
else:
|
||||
self.async_client = httpx.AsyncClient(
|
||||
base_url=self.host,
|
||||
@@ -768,7 +780,11 @@ class AsyncMemoryClient:
|
||||
"""Validate the API key by making a test request."""
|
||||
try:
|
||||
params = self._prepare_params()
|
||||
response = requests.get(f"{self.host}/v1/ping/", headers={"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id}, params=params)
|
||||
response = requests.get(
|
||||
f"{self.host}/v1/ping/",
|
||||
headers={"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id},
|
||||
params=params,
|
||||
)
|
||||
data = response.json()
|
||||
|
||||
response.raise_for_status()
|
||||
@@ -788,7 +804,7 @@ class AsyncMemoryClient:
|
||||
raise ValueError(f"Error: {error_message}")
|
||||
|
||||
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]:
|
||||
"""Prepare the payload for API requests.
|
||||
|
||||
@@ -800,10 +816,7 @@ class AsyncMemoryClient:
|
||||
A dictionary containing the prepared payload.
|
||||
"""
|
||||
payload = {}
|
||||
if isinstance(messages, str):
|
||||
payload["messages"] = [{"role": "user", "content": messages}]
|
||||
elif isinstance(messages, list):
|
||||
payload["messages"] = messages
|
||||
payload["messages"] = messages
|
||||
|
||||
payload.update({k: v for k, v in kwargs.items() if v is not None})
|
||||
return payload
|
||||
@@ -840,7 +853,7 @@ class AsyncMemoryClient:
|
||||
await self.async_client.aclose()
|
||||
|
||||
@api_error_handler
|
||||
async def add(self, messages: Union[str, List[Dict[str, str]]], **kwargs) -> Dict[str, Any]:
|
||||
async def add(self, messages: List[Dict[str, str]], **kwargs) -> Dict[str, Any]:
|
||||
kwargs = self._prepare_params(kwargs)
|
||||
if kwargs.get("output_format") != "v1.1":
|
||||
kwargs["output_format"] = "v1.1"
|
||||
@@ -973,10 +986,7 @@ class AsyncMemoryClient:
|
||||
else:
|
||||
entities = await self.users()
|
||||
# Filter entities based on provided IDs using list comprehension
|
||||
to_delete = [
|
||||
{"type": entity["type"], "name": entity["name"]}
|
||||
for entity in entities["results"]
|
||||
]
|
||||
to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
|
||||
|
||||
params = self._prepare_params()
|
||||
|
||||
@@ -988,7 +998,11 @@ class AsyncMemoryClient:
|
||||
response = await self.async_client.delete(f"/v2/entities/{entity['type']}/{entity['name']}/", params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event("client.delete_users", self, {"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "async"})
|
||||
capture_client_event(
|
||||
"client.delete_users",
|
||||
self,
|
||||
{"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "async"},
|
||||
)
|
||||
return {
|
||||
"message": "Entity deleted successfully."
|
||||
if (user_id or agent_id or app_id or run_id)
|
||||
@@ -1091,19 +1105,29 @@ class AsyncMemoryClient:
|
||||
|
||||
@api_error_handler
|
||||
async def update_project(
|
||||
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None
|
||||
self,
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
enable_graph: Optional[bool] = None,
|
||||
version: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
if not (self.org_id and self.project_id):
|
||||
raise ValueError("org_id and project_id must be set to update instructions or categories")
|
||||
|
||||
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
|
||||
if custom_instructions is None and custom_categories is None and retrieval_criteria is None and enable_graph is None and version is None:
|
||||
raise ValueError(
|
||||
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
}
|
||||
)
|
||||
response = await self.async_client.patch(
|
||||
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
|
||||
@@ -1113,7 +1137,14 @@ class AsyncMemoryClient:
|
||||
capture_client_event(
|
||||
"client.update_project",
|
||||
self,
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "async"},
|
||||
{
|
||||
"custom_instructions": custom_instructions,
|
||||
"custom_categories": custom_categories,
|
||||
"retrieval_criteria": retrieval_criteria,
|
||||
"enable_graph": enable_graph,
|
||||
"version": version,
|
||||
"sync_type": "async",
|
||||
},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -1174,4 +1205,3 @@ class AsyncMemoryClient:
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.feedback", self, data, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ class BaseLlmConfig(ABC):
|
||||
deepseek_base_url: Optional[str] = None,
|
||||
# XAI specific
|
||||
xai_base_url: Optional[str] = None,
|
||||
# Sarvam specific
|
||||
sarvam_base_url: Optional[str] = "https://api.sarvam.ai/v1",
|
||||
# LM Studio specific
|
||||
lmstudio_base_url: Optional[str] = "http://localhost:1234/v1",
|
||||
# AWS Bedrock specific
|
||||
@@ -89,6 +91,8 @@ class BaseLlmConfig(ABC):
|
||||
:type deepseek_base_url: Optional[str], optional
|
||||
:param xai_base_url: XAI base URL to be use, defaults to None
|
||||
:type xai_base_url: Optional[str], optional
|
||||
:param sarvam_base_url: Sarvam base URL to be use, defaults to "https://api.sarvam.ai/v1"
|
||||
:type sarvam_base_url: Optional[str], optional
|
||||
:param lmstudio_base_url: LM Studio base URL to be use, defaults to "http://localhost:1234/v1"
|
||||
:type lmstudio_base_url: Optional[str], optional
|
||||
"""
|
||||
@@ -125,6 +129,9 @@ class BaseLlmConfig(ABC):
|
||||
# XAI specific
|
||||
self.xai_base_url = xai_base_url
|
||||
|
||||
# Sarvam specific
|
||||
self.sarvam_base_url = sarvam_base_url
|
||||
|
||||
# LM Studio specific
|
||||
self.lmstudio_base_url = lmstudio_base_url
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, Optional, Union, Type
|
||||
from typing import Any, Dict, Optional, Type, Union
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
@@ -7,33 +7,17 @@ class OpenSearchConfig(BaseModel):
|
||||
collection_name: str = Field("mem0", description="Name of the index")
|
||||
host: str = Field("localhost", description="OpenSearch host")
|
||||
port: int = Field(9200, description="OpenSearch port")
|
||||
user: Optional[str] = Field(
|
||||
None, description="Username for authentication"
|
||||
)
|
||||
password: Optional[str] = Field(
|
||||
None, description="Password for authentication"
|
||||
)
|
||||
api_key: Optional[str] = Field(
|
||||
None, description="API key for authentication (if applicable)"
|
||||
)
|
||||
embedding_model_dims: int = Field(
|
||||
1536, description="Dimension of the embedding vector"
|
||||
)
|
||||
verify_certs: bool = Field(
|
||||
False, description="Verify SSL certificates (default False for OpenSearch)"
|
||||
)
|
||||
use_ssl: bool = Field(
|
||||
False, description="Use SSL for connection (default False for OpenSearch)"
|
||||
)
|
||||
http_auth: Optional[object] = Field(
|
||||
None, description="HTTP authentication method / AWS SigV4"
|
||||
)
|
||||
user: Optional[str] = Field(None, description="Username for authentication")
|
||||
password: Optional[str] = Field(None, description="Password for authentication")
|
||||
api_key: Optional[str] = Field(None, description="API key for authentication (if applicable)")
|
||||
embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
|
||||
verify_certs: bool = Field(False, description="Verify SSL certificates (default False for OpenSearch)")
|
||||
use_ssl: bool = Field(False, description="Use SSL for connection (default False for OpenSearch)")
|
||||
http_auth: Optional[object] = Field(None, description="HTTP authentication method / AWS SigV4")
|
||||
connection_class: Optional[Union[str, Type]] = Field(
|
||||
"RequestsHttpConnection", description="Connection class for OpenSearch"
|
||||
)
|
||||
pool_maxsize: int = Field(
|
||||
20, description="Maximum number of connections in the pool"
|
||||
)
|
||||
pool_maxsize: int = Field(20, description="Maximum number of connections in the pool")
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -41,7 +25,7 @@ class OpenSearchConfig(BaseModel):
|
||||
# Check if host is provided
|
||||
if not values.get("host"):
|
||||
raise ValueError("Host must be provided for OpenSearch")
|
||||
|
||||
|
||||
return values
|
||||
|
||||
@model_validator(mode="before")
|
||||
@@ -52,7 +36,6 @@ class OpenSearchConfig(BaseModel):
|
||||
extra_fields = input_fields - allowed_fields
|
||||
if extra_fields:
|
||||
raise ValueError(
|
||||
f"Extra fields not allowed: {', '.join(extra_fields)}. "
|
||||
f"Allowed fields: {', '.join(allowed_fields)}"
|
||||
f"Extra fields not allowed: {', '.join(extra_fields)}. " f"Allowed fields: {', '.join(allowed_fields)}"
|
||||
)
|
||||
return values
|
||||
|
||||
@@ -23,12 +23,12 @@ class AWSBedrockEmbedding(EmbeddingBase):
|
||||
super().__init__(config)
|
||||
|
||||
self.config.model = self.config.model or "amazon.titan-embed-text-v1"
|
||||
|
||||
|
||||
# Get AWS config from environment variables or use defaults
|
||||
aws_access_key = os.environ.get("AWS_ACCESS_KEY_ID", "")
|
||||
aws_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
|
||||
aws_region = os.environ.get("AWS_REGION", "us-west-2")
|
||||
|
||||
|
||||
# Check if AWS config is provided in the config
|
||||
if hasattr(self.config, "aws_access_key_id"):
|
||||
aws_access_key = self.config.aws_access_key_id
|
||||
@@ -36,7 +36,7 @@ class AWSBedrockEmbedding(EmbeddingBase):
|
||||
aws_secret_key = self.config.aws_secret_access_key
|
||||
if hasattr(self.config, "aws_region"):
|
||||
aws_region = self.config.aws_region
|
||||
|
||||
|
||||
self.client = boto3.client(
|
||||
"bedrock-runtime",
|
||||
region_name=aws_region,
|
||||
|
||||
@@ -11,6 +11,7 @@ logging.getLogger("transformers").setLevel(logging.WARNING)
|
||||
logging.getLogger("sentence_transformers").setLevel(logging.WARNING)
|
||||
logging.getLogger("huggingface_hub").setLevel(logging.WARNING)
|
||||
|
||||
|
||||
class HuggingFaceEmbedding(EmbeddingBase):
|
||||
def __init__(self, config: Optional[BaseEmbedderConfig] = None):
|
||||
super().__init__(config)
|
||||
|
||||
@@ -22,7 +22,8 @@ class Neo4jConfig(BaseModel):
|
||||
if not url or not username or not password:
|
||||
raise ValueError("Please provide 'url', 'username' and 'password'.")
|
||||
return values
|
||||
|
||||
|
||||
|
||||
class MemgraphConfig(BaseModel):
|
||||
url: Optional[str] = Field(None, description="Host address for the graph database")
|
||||
username: Optional[str] = Field(None, description="Username for the graph database")
|
||||
|
||||
+11
-15
@@ -20,18 +20,19 @@ def extract_provider(model: str) -> str:
|
||||
return provider
|
||||
raise ValueError(f"Unknown provider in model: {model}")
|
||||
|
||||
|
||||
class AWSBedrockLLM(LLMBase):
|
||||
def __init__(self, config: Optional[BaseLlmConfig] = None):
|
||||
super().__init__(config)
|
||||
|
||||
if not self.config.model:
|
||||
self.config.model = "anthropic.claude-3-5-sonnet-20240620-v1:0"
|
||||
|
||||
|
||||
# Get AWS config from environment variables or use defaults
|
||||
aws_access_key = os.environ.get("AWS_ACCESS_KEY_ID", "")
|
||||
aws_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
|
||||
aws_region = os.environ.get("AWS_REGION", "us-west-2")
|
||||
|
||||
|
||||
# Check if AWS config is provided in the config
|
||||
if hasattr(self.config, "aws_access_key_id"):
|
||||
aws_access_key = self.config.aws_access_key_id
|
||||
@@ -39,14 +40,14 @@ class AWSBedrockLLM(LLMBase):
|
||||
aws_secret_key = self.config.aws_secret_access_key
|
||||
if hasattr(self.config, "aws_region"):
|
||||
aws_region = self.config.aws_region
|
||||
|
||||
|
||||
self.client = boto3.client(
|
||||
"bedrock-runtime",
|
||||
region_name=aws_region,
|
||||
aws_access_key_id=aws_access_key if aws_access_key else None,
|
||||
aws_secret_access_key=aws_secret_key if aws_secret_key else None,
|
||||
)
|
||||
|
||||
|
||||
self.model_kwargs = {
|
||||
"temperature": self.config.temperature,
|
||||
"max_tokens_to_sample": self.config.max_tokens,
|
||||
@@ -145,7 +146,9 @@ class AWSBedrockLLM(LLMBase):
|
||||
input_body = {
|
||||
"inputText": prompt,
|
||||
"textGenerationConfig": {
|
||||
"maxTokenCount": self.model_kwargs["max_tokens_to_sample"] or self.model_kwargs["max_tokens"] or 5000,
|
||||
"maxTokenCount": self.model_kwargs["max_tokens_to_sample"]
|
||||
or self.model_kwargs["max_tokens"]
|
||||
or 5000,
|
||||
"topP": self.model_kwargs["top_p"] or 0.9,
|
||||
"temperature": self.model_kwargs["temperature"] or 0.1,
|
||||
},
|
||||
@@ -243,22 +246,15 @@ class AWSBedrockLLM(LLMBase):
|
||||
body = json.dumps(input_body)
|
||||
|
||||
if provider == "anthropic" or provider == "deepseek":
|
||||
|
||||
input_body = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "text", "text": prompt}]
|
||||
}
|
||||
],
|
||||
"messages": [{"role": "user", "content": [{"type": "text", "text": prompt}]}],
|
||||
"max_tokens": self.model_kwargs["max_tokens_to_sample"] or self.model_kwargs["max_tokens"] or 5000,
|
||||
"temperature": self.model_kwargs["temperature"] or 0.1,
|
||||
"top_p": self.model_kwargs["top_p"] or 0.9,
|
||||
"anthropic_version": "bedrock-2023-05-31",
|
||||
}
|
||||
|
||||
|
||||
body = json.dumps(input_body)
|
||||
|
||||
|
||||
response = self.client.invoke_model(
|
||||
body=body,
|
||||
@@ -272,6 +268,6 @@ class AWSBedrockLLM(LLMBase):
|
||||
modelId=self.config.model,
|
||||
accept="application/json",
|
||||
contentType="application/json",
|
||||
)
|
||||
)
|
||||
|
||||
return self._parse_response(response, tools)
|
||||
|
||||
@@ -24,6 +24,7 @@ class LlmConfig(BaseModel):
|
||||
"gemini",
|
||||
"deepseek",
|
||||
"xai",
|
||||
"sarvam",
|
||||
"lmstudio",
|
||||
"langchain",
|
||||
):
|
||||
|
||||
@@ -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}")
|
||||
+243
-238
@@ -34,17 +34,17 @@ from mem0.utils.factory import EmbedderFactory, LlmFactory, VectorStoreFactory
|
||||
|
||||
|
||||
def _build_filters_and_metadata(
|
||||
*, # Enforce keyword-only arguments
|
||||
*, # Enforce keyword-only arguments
|
||||
user_id: Optional[str] = None,
|
||||
agent_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_filters: Optional[Dict[str, Any]] = None,
|
||||
) -> tuple[Dict[str, Any], Dict[str, Any]]:
|
||||
"""
|
||||
Constructs metadata for storage and filters for querying based on session and actor identifiers.
|
||||
|
||||
|
||||
This helper ties every memory/query to exactly one session id (`user_id`, `agent_id`, or `run_id`) and optionally narrows queries to a specific `actor_id`. It returns two dicts:
|
||||
|
||||
|
||||
@@ -78,10 +78,10 @@ def _build_filters_and_metadata(
|
||||
- effective_query_filters (Dict[str, Any]): Filters for querying memories,
|
||||
scoped to the determined session and potentially a resolved actor.
|
||||
"""
|
||||
|
||||
|
||||
base_metadata_template = deepcopy(input_metadata) if input_metadata else {}
|
||||
effective_query_filters = deepcopy(input_filters) if input_filters else {}
|
||||
|
||||
|
||||
# ---------- resolve session id (mandatory) ----------
|
||||
session_key, session_val = None, None
|
||||
if user_id:
|
||||
@@ -90,20 +90,20 @@ def _build_filters_and_metadata(
|
||||
session_key, session_val = "agent_id", agent_id
|
||||
elif run_id:
|
||||
session_key, session_val = "run_id", run_id
|
||||
|
||||
|
||||
if session_key is None:
|
||||
raise ValueError("One of 'user_id', 'agent_id', or 'run_id' must be provided.")
|
||||
|
||||
|
||||
base_metadata_template[session_key] = session_val
|
||||
effective_query_filters[session_key] = session_val
|
||||
|
||||
|
||||
# ---------- optional actor filter ----------
|
||||
resolved_actor_id = actor_id or effective_query_filters.get("actor_id")
|
||||
if resolved_actor_id:
|
||||
effective_query_filters["actor_id"] = resolved_actor_id
|
||||
|
||||
|
||||
return base_metadata_template, effective_query_filters
|
||||
|
||||
|
||||
|
||||
setup_config()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -189,7 +189,7 @@ class Memory(MemoryBase):
|
||||
):
|
||||
"""
|
||||
Create a new memory.
|
||||
|
||||
|
||||
Adds new memories scoped to a single session id (e.g. `user_id`, `agent_id`, or `run_id`). One of those ids is required.
|
||||
|
||||
Args:
|
||||
@@ -208,7 +208,7 @@ class Memory(MemoryBase):
|
||||
creating procedural memories (typically requires 'agent_id'). Otherwise, memories
|
||||
are treated as general conversational/factual memories.memory_type (str, optional): Type of memory to create. Defaults to None. By default, it creates the short term memories and long term (semantic and episodic) memories. Pass "procedural_memory" to create procedural memories.
|
||||
prompt (str, optional): Prompt to use for the memory creation. Defaults to None.
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the result of the memory addition operation, typically
|
||||
@@ -216,14 +216,14 @@ class Memory(MemoryBase):
|
||||
and potentially "relations" if graph store is enabled.
|
||||
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", "event": "ADD"}]}`
|
||||
"""
|
||||
|
||||
|
||||
processed_metadata, effective_filters = _build_filters_and_metadata(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
input_metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
|
||||
raise ValueError(
|
||||
f"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories."
|
||||
@@ -231,10 +231,10 @@ class Memory(MemoryBase):
|
||||
|
||||
if isinstance(messages, str):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
|
||||
|
||||
elif isinstance(messages, dict):
|
||||
messages = [messages]
|
||||
|
||||
|
||||
elif not isinstance(messages, list):
|
||||
raise ValueError("messages must be str, dict, or list[dict]")
|
||||
|
||||
@@ -255,7 +255,7 @@ class Memory(MemoryBase):
|
||||
|
||||
vector_store_result = future1.result()
|
||||
graph_result = future2.result()
|
||||
|
||||
|
||||
if self.api_version == "v1.0":
|
||||
warnings.warn(
|
||||
"The current add API output format is deprecated. "
|
||||
@@ -277,21 +277,21 @@ class Memory(MemoryBase):
|
||||
def _add_to_vector_store(self, messages, metadata, filters, infer):
|
||||
if not infer:
|
||||
returned_memories = []
|
||||
for message_dict in messages:
|
||||
if not isinstance(message_dict, dict) or \
|
||||
message_dict.get("role") is None or \
|
||||
message_dict.get("content") is None:
|
||||
for message_dict in messages:
|
||||
if (
|
||||
not isinstance(message_dict, dict)
|
||||
or message_dict.get("role") is None
|
||||
or message_dict.get("content") is None
|
||||
):
|
||||
logger.warning(f"Skipping invalid message format: {message_dict}")
|
||||
continue
|
||||
|
||||
if message_dict["role"] == "system":
|
||||
continue
|
||||
continue
|
||||
|
||||
|
||||
per_msg_meta = deepcopy(metadata)
|
||||
per_msg_meta["role"] = message_dict["role"]
|
||||
|
||||
|
||||
actor_name = message_dict.get("name")
|
||||
if actor_name:
|
||||
per_msg_meta["actor_id"] = actor_name
|
||||
@@ -311,8 +311,8 @@ class Memory(MemoryBase):
|
||||
)
|
||||
return returned_memories
|
||||
|
||||
parsed_messages = parse_messages(messages)
|
||||
|
||||
parsed_messages = parse_messages(messages)
|
||||
|
||||
if self.config.custom_fact_extraction_prompt:
|
||||
system_prompt = self.config.custom_fact_extraction_prompt
|
||||
user_prompt = f"Input:\n{parsed_messages}"
|
||||
@@ -334,9 +334,13 @@ class Memory(MemoryBase):
|
||||
logging.error(f"Error in new_retrieved_facts: {e}")
|
||||
new_retrieved_facts = []
|
||||
|
||||
if not new_retrieved_facts:
|
||||
logger.debug("No new facts retrieved from input. Skipping memory update LLM call.")
|
||||
return []
|
||||
|
||||
retrieved_old_memory = []
|
||||
new_message_embeddings = {}
|
||||
for new_mem in new_retrieved_facts:
|
||||
for new_mem in new_retrieved_facts:
|
||||
messages_embeddings = self.embedding_model.embed(new_mem, "add")
|
||||
new_message_embeddings[new_mem] = messages_embeddings
|
||||
existing_memories = self.vector_store.search(
|
||||
@@ -347,7 +351,7 @@ class Memory(MemoryBase):
|
||||
)
|
||||
for mem in existing_memories:
|
||||
retrieved_old_memory.append({"id": mem.id, "text": mem.payload["data"]})
|
||||
|
||||
|
||||
unique_data = {}
|
||||
for item in retrieved_old_memory:
|
||||
unique_data[item["id"]] = item
|
||||
@@ -389,7 +393,7 @@ class Memory(MemoryBase):
|
||||
if not action_text:
|
||||
logging.info("Skipping memory entry because of empty `text` field.")
|
||||
continue
|
||||
|
||||
|
||||
event_type = resp.get("event")
|
||||
if event_type == "ADD":
|
||||
memory_id = self._create_memory(
|
||||
@@ -405,16 +409,23 @@ class Memory(MemoryBase):
|
||||
existing_embeddings=new_message_embeddings,
|
||||
metadata=deepcopy(metadata),
|
||||
)
|
||||
returned_memories.append({
|
||||
"id": temp_uuid_mapping[resp.get("id")], "memory": action_text,
|
||||
"event": event_type, "previous_memory": resp.get("old_memory"),
|
||||
})
|
||||
returned_memories.append(
|
||||
{
|
||||
"id": temp_uuid_mapping[resp.get("id")],
|
||||
"memory": action_text,
|
||||
"event": event_type,
|
||||
"previous_memory": resp.get("old_memory"),
|
||||
}
|
||||
)
|
||||
elif event_type == "DELETE":
|
||||
self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")])
|
||||
returned_memories.append({
|
||||
"id": temp_uuid_mapping[resp.get("id")], "memory": action_text,
|
||||
"event": event_type,
|
||||
})
|
||||
returned_memories.append(
|
||||
{
|
||||
"id": temp_uuid_mapping[resp.get("id")],
|
||||
"memory": action_text,
|
||||
"event": event_type,
|
||||
}
|
||||
)
|
||||
elif event_type == "NONE":
|
||||
logging.info("NOOP for Memory.")
|
||||
except Exception as e:
|
||||
@@ -462,11 +473,8 @@ class Memory(MemoryBase):
|
||||
"actor_id",
|
||||
"role",
|
||||
]
|
||||
|
||||
core_and_promoted_keys = {
|
||||
"data", "hash", "created_at", "updated_at", "id",
|
||||
*promoted_payload_keys
|
||||
}
|
||||
|
||||
core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
|
||||
|
||||
result_item = MemoryItem(
|
||||
id=memory.id,
|
||||
@@ -479,18 +487,16 @@ class Memory(MemoryBase):
|
||||
for key in promoted_payload_keys:
|
||||
if key in memory.payload:
|
||||
result_item[key] = memory.payload[key]
|
||||
|
||||
additional_metadata = {
|
||||
k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys
|
||||
}
|
||||
|
||||
additional_metadata = {k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys}
|
||||
if additional_metadata:
|
||||
result_item["metadata"] = additional_metadata
|
||||
|
||||
|
||||
return result_item
|
||||
|
||||
def get_all(
|
||||
self,
|
||||
*,
|
||||
*,
|
||||
user_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
@@ -505,7 +511,7 @@ class Memory(MemoryBase):
|
||||
agent_id (str, optional): agent id
|
||||
run_id (str, optional): run id
|
||||
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"}`.
|
||||
limit (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
|
||||
@@ -515,21 +521,16 @@ class Memory(MemoryBase):
|
||||
it might return a direct list (see deprecation warning).
|
||||
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", ...}]}`
|
||||
"""
|
||||
|
||||
|
||||
_, effective_filters = _build_filters_and_metadata(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
input_filters=filters
|
||||
user_id=user_id, 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")):
|
||||
raise ValueError("At least one of 'user_id', 'agent_id', or 'run_id' must be specified.")
|
||||
|
||||
capture_event(
|
||||
"mem0.get_all",
|
||||
self,
|
||||
{"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "sync"}
|
||||
"mem0.get_all", self, {"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "sync"}
|
||||
)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
@@ -542,9 +543,9 @@ class Memory(MemoryBase):
|
||||
[future_memories, future_graph_entities] if future_graph_entities else [future_memories]
|
||||
)
|
||||
|
||||
all_memories_result = future_memories.result()
|
||||
all_memories_result = future_memories.result()
|
||||
graph_entities_result = future_graph_entities.result() if future_graph_entities else None
|
||||
|
||||
|
||||
if self.enable_graph:
|
||||
return {"results": all_memories_result, "relations": graph_entities_result}
|
||||
|
||||
@@ -556,26 +557,27 @@ class Memory(MemoryBase):
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return all_memories_result
|
||||
return all_memories_result
|
||||
else:
|
||||
return {"results": all_memories_result}
|
||||
|
||||
def _get_all_from_vector_store(self, filters, 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 = [
|
||||
"user_id", "agent_id", "run_id",
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"actor_id",
|
||||
"role",
|
||||
]
|
||||
core_and_promoted_keys = {
|
||||
"data", "hash", "created_at", "updated_at", "id",
|
||||
*promoted_payload_keys
|
||||
}
|
||||
core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
|
||||
|
||||
formatted_memories = []
|
||||
for mem in actual_memories:
|
||||
for mem in actual_memories:
|
||||
memory_item_dict = MemoryItem(
|
||||
id=mem.id,
|
||||
memory=mem.payload["data"],
|
||||
@@ -587,15 +589,13 @@ class Memory(MemoryBase):
|
||||
for key in promoted_payload_keys:
|
||||
if key in mem.payload:
|
||||
memory_item_dict[key] = mem.payload[key]
|
||||
|
||||
additional_metadata = {
|
||||
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
|
||||
}
|
||||
|
||||
additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
|
||||
if additional_metadata:
|
||||
memory_item_dict["metadata"] = additional_metadata
|
||||
|
||||
|
||||
formatted_memories.append(memory_item_dict)
|
||||
|
||||
|
||||
return formatted_memories
|
||||
|
||||
def search(
|
||||
@@ -624,12 +624,9 @@ class Memory(MemoryBase):
|
||||
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", "score": 0.8, ...}]}`
|
||||
"""
|
||||
_, effective_filters = _build_filters_and_metadata(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
input_filters=filters
|
||||
user_id=user_id, 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")):
|
||||
raise ValueError("At least one of 'user_id', 'agent_id', or 'run_id' must be specified.")
|
||||
|
||||
@@ -651,7 +648,7 @@ class Memory(MemoryBase):
|
||||
|
||||
original_memories = future_memories.result()
|
||||
graph_entities = future_graph_entities.result() if future_graph_entities else None
|
||||
|
||||
|
||||
if self.enable_graph:
|
||||
return {"results": original_memories, "relations": graph_entities}
|
||||
|
||||
@@ -678,11 +675,8 @@ class Memory(MemoryBase):
|
||||
"actor_id",
|
||||
"role",
|
||||
]
|
||||
|
||||
core_and_promoted_keys = {
|
||||
"data", "hash", "created_at", "updated_at", "id",
|
||||
*promoted_payload_keys
|
||||
}
|
||||
|
||||
core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
|
||||
|
||||
original_memories = []
|
||||
for mem in memories:
|
||||
@@ -693,18 +687,16 @@ class Memory(MemoryBase):
|
||||
created_at=mem.payload.get("created_at"),
|
||||
updated_at=mem.payload.get("updated_at"),
|
||||
score=mem.score,
|
||||
).model_dump()
|
||||
).model_dump()
|
||||
|
||||
for key in promoted_payload_keys:
|
||||
if key in mem.payload:
|
||||
memory_item_dict[key] = mem.payload[key]
|
||||
|
||||
additional_metadata = {
|
||||
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
|
||||
}
|
||||
|
||||
additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
|
||||
if additional_metadata:
|
||||
memory_item_dict["metadata"] = additional_metadata
|
||||
|
||||
|
||||
original_memories.append(memory_item_dict)
|
||||
|
||||
return original_memories
|
||||
@@ -738,7 +730,7 @@ class Memory(MemoryBase):
|
||||
self._delete_memory(memory_id)
|
||||
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.
|
||||
|
||||
@@ -860,11 +852,11 @@ class Memory(MemoryBase):
|
||||
except Exception:
|
||||
logger.error(f"Error getting memory with ID {memory_id} during update.")
|
||||
raise ValueError(f"Error getting memory with ID {memory_id}. Please provide a valid 'memory_id'")
|
||||
|
||||
|
||||
prev_value = existing_memory.payload.get("data")
|
||||
|
||||
new_metadata = deepcopy(metadata) if metadata is not None else {}
|
||||
|
||||
|
||||
new_metadata["data"] = data
|
||||
new_metadata["hash"] = hashlib.md5(data.encode()).hexdigest()
|
||||
new_metadata["created_at"] = existing_memory.payload.get("created_at")
|
||||
@@ -875,7 +867,7 @@ class Memory(MemoryBase):
|
||||
if "agent_id" in existing_memory.payload:
|
||||
new_metadata["agent_id"] = existing_memory.payload["agent_id"]
|
||||
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:
|
||||
new_metadata["actor_id"] = existing_memory.payload["actor_id"]
|
||||
if "role" in existing_memory.payload:
|
||||
@@ -885,14 +877,14 @@ class Memory(MemoryBase):
|
||||
embeddings = existing_embeddings[data]
|
||||
else:
|
||||
embeddings = self.embedding_model.embed(data, "update")
|
||||
|
||||
|
||||
self.vector_store.update(
|
||||
vector_id=memory_id,
|
||||
vector=embeddings,
|
||||
payload=new_metadata,
|
||||
)
|
||||
logger.info(f"Updating memory with ID {memory_id=} with {data=}")
|
||||
|
||||
|
||||
self.db.add_history(
|
||||
memory_id,
|
||||
prev_value,
|
||||
@@ -1037,12 +1029,9 @@ class AsyncMemory(MemoryBase):
|
||||
dict: A dictionary containing the result of the memory addition operation.
|
||||
"""
|
||||
processed_metadata, effective_filters = _build_filters_and_metadata(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
input_metadata=metadata
|
||||
user_id=user_id, agent_id=agent_id, run_id=run_id, input_metadata=metadata
|
||||
)
|
||||
|
||||
|
||||
if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
|
||||
raise ValueError(
|
||||
f"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories."
|
||||
@@ -1050,15 +1039,17 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
if isinstance(messages, str):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
|
||||
|
||||
elif isinstance(messages, dict):
|
||||
messages = [messages]
|
||||
|
||||
|
||||
elif not isinstance(messages, list):
|
||||
raise ValueError("messages must be str, dict, or list[dict]")
|
||||
|
||||
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
|
||||
|
||||
if self.config.llm.config.get("enable_vision"):
|
||||
@@ -1066,7 +1057,9 @@ class AsyncMemory(MemoryBase):
|
||||
else:
|
||||
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))
|
||||
|
||||
vector_store_result, graph_result = await asyncio.gather(vector_store_task, graph_task)
|
||||
@@ -1090,8 +1083,8 @@ class AsyncMemory(MemoryBase):
|
||||
return {"results": vector_store_result}
|
||||
|
||||
async def _add_to_vector_store(
|
||||
self,
|
||||
messages: list,
|
||||
self,
|
||||
messages: list,
|
||||
metadata: dict,
|
||||
filters: dict,
|
||||
infer: bool,
|
||||
@@ -1099,9 +1092,11 @@ class AsyncMemory(MemoryBase):
|
||||
if not infer:
|
||||
returned_memories = []
|
||||
for message_dict in messages:
|
||||
if not isinstance(message_dict, dict) or \
|
||||
message_dict.get("role") is None or \
|
||||
message_dict.get("content") is None:
|
||||
if (
|
||||
not isinstance(message_dict, dict)
|
||||
or message_dict.get("role") is None
|
||||
or message_dict.get("content") is None
|
||||
):
|
||||
logger.warning(f"Skipping invalid message format (async): {message_dict}")
|
||||
continue
|
||||
|
||||
@@ -1110,20 +1105,24 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
per_msg_meta = deepcopy(metadata)
|
||||
per_msg_meta["role"] = message_dict["role"]
|
||||
|
||||
|
||||
actor_name = message_dict.get("name")
|
||||
if actor_name:
|
||||
per_msg_meta["actor_id"] = actor_name
|
||||
|
||||
|
||||
msg_content = message_dict["content"]
|
||||
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)
|
||||
|
||||
returned_memories.append({
|
||||
"id": mem_id, "memory": msg_content, "event": "ADD",
|
||||
"actor_id": actor_name if actor_name else None,
|
||||
"role": message_dict["role"],
|
||||
})
|
||||
|
||||
returned_memories.append(
|
||||
{
|
||||
"id": mem_id,
|
||||
"memory": msg_content,
|
||||
"event": "ADD",
|
||||
"actor_id": actor_name if actor_name else None,
|
||||
"role": message_dict["role"],
|
||||
}
|
||||
)
|
||||
return returned_memories
|
||||
|
||||
parsed_messages = parse_messages(messages)
|
||||
@@ -1142,17 +1141,26 @@ class AsyncMemory(MemoryBase):
|
||||
response = remove_code_blocks(response)
|
||||
new_retrieved_facts = json.loads(response)["facts"]
|
||||
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 = []
|
||||
new_message_embeddings = {}
|
||||
|
||||
|
||||
async def process_fact_for_search(new_mem_content):
|
||||
embeddings = await asyncio.to_thread(self.embedding_model.embed, new_mem_content, "add")
|
||||
new_message_embeddings[new_mem_content] = embeddings
|
||||
existing_mems = await asyncio.to_thread(
|
||||
self.vector_store.search, query=new_mem_content, vectors=embeddings,
|
||||
limit=5, filters=filters, # 'filters' is query_filters_for_inference
|
||||
self.vector_store.search,
|
||||
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]
|
||||
|
||||
@@ -1160,9 +1168,10 @@ class AsyncMemory(MemoryBase):
|
||||
search_results_list = await asyncio.gather(*search_tasks)
|
||||
for result_group in search_results_list:
|
||||
retrieved_old_memory.extend(result_group)
|
||||
|
||||
|
||||
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())
|
||||
logging.info(f"Total existing memories: {len(retrieved_old_memory)}")
|
||||
temp_uuid_mapping = {}
|
||||
@@ -1180,35 +1189,54 @@ class AsyncMemory(MemoryBase):
|
||||
response_format={"type": "json_object"},
|
||||
)
|
||||
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:
|
||||
response = remove_code_blocks(response)
|
||||
new_memories_with_actions = json.loads(response)
|
||||
except Exception as e:
|
||||
logging.error(f"Invalid JSON response: {e}"); new_memories_with_actions = {}
|
||||
|
||||
returned_memories = []
|
||||
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 = []
|
||||
try:
|
||||
memory_tasks = []
|
||||
for resp in new_memories_with_actions.get("memory", []):
|
||||
logging.info(resp)
|
||||
try:
|
||||
action_text = resp.get("text")
|
||||
if not action_text: continue
|
||||
if not action_text:
|
||||
continue
|
||||
event_type = resp.get("event")
|
||||
|
||||
if event_type == "ADD":
|
||||
task = asyncio.create_task(self._create_memory(
|
||||
data=action_text, existing_embeddings=new_message_embeddings,
|
||||
metadata=deepcopy(metadata)
|
||||
))
|
||||
task = asyncio.create_task(
|
||||
self._create_memory(
|
||||
data=action_text,
|
||||
existing_embeddings=new_message_embeddings,
|
||||
metadata=deepcopy(metadata),
|
||||
)
|
||||
)
|
||||
memory_tasks.append((task, resp, "ADD", None))
|
||||
elif event_type == "UPDATE":
|
||||
task = asyncio.create_task(self._update_memory(
|
||||
memory_id=temp_uuid_mapping[resp["id"]], data=action_text,
|
||||
existing_embeddings=new_message_embeddings, metadata=deepcopy(metadata)
|
||||
))
|
||||
task = asyncio.create_task(
|
||||
self._update_memory(
|
||||
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"]]))
|
||||
elif event_type == "DELETE":
|
||||
task = asyncio.create_task(self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")]))
|
||||
@@ -1217,31 +1245,30 @@ class AsyncMemory(MemoryBase):
|
||||
logging.info("NOOP for Memory (async).")
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing memory action (async): {resp}, Error: {e}")
|
||||
|
||||
|
||||
for task, resp, event_type, mem_id in memory_tasks:
|
||||
try:
|
||||
result_id = await task
|
||||
if event_type == "ADD":
|
||||
returned_memories.append({
|
||||
"id": result_id, "memory": resp.get("text"), "event": event_type
|
||||
})
|
||||
returned_memories.append({"id": result_id, "memory": resp.get("text"), "event": event_type})
|
||||
elif event_type == "UPDATE":
|
||||
returned_memories.append({
|
||||
"id": mem_id, "memory": resp.get("text"),
|
||||
"event": event_type, "previous_memory": resp.get("old_memory")
|
||||
})
|
||||
returned_memories.append(
|
||||
{
|
||||
"id": mem_id,
|
||||
"memory": resp.get("text"),
|
||||
"event": event_type,
|
||||
"previous_memory": resp.get("old_memory"),
|
||||
}
|
||||
)
|
||||
elif event_type == "DELETE":
|
||||
returned_memories.append({
|
||||
"id": mem_id, "memory": resp.get("text"), "event": event_type
|
||||
})
|
||||
returned_memories.append({"id": mem_id, "memory": resp.get("text"), "event": event_type})
|
||||
except Exception as e:
|
||||
logging.error(f"Error awaiting memory task (async): {e}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error in memory processing loop (async): {e}")
|
||||
|
||||
|
||||
capture_event(
|
||||
"mem0.add", self,
|
||||
{"version": self.api_version, "keys": list(filters.keys()), "sync_type": "async"}
|
||||
"mem0.add", self, {"version": self.api_version, "keys": list(filters.keys()), "sync_type": "async"}
|
||||
)
|
||||
return returned_memories
|
||||
|
||||
@@ -1272,17 +1299,14 @@ class AsyncMemory(MemoryBase):
|
||||
return None
|
||||
|
||||
promoted_payload_keys = [
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"actor_id",
|
||||
"role",
|
||||
]
|
||||
|
||||
core_and_promoted_keys = {
|
||||
"data", "hash", "created_at", "updated_at", "id",
|
||||
*promoted_payload_keys
|
||||
}
|
||||
|
||||
core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
|
||||
|
||||
result_item = MemoryItem(
|
||||
id=memory.id,
|
||||
@@ -1295,18 +1319,16 @@ class AsyncMemory(MemoryBase):
|
||||
for key in promoted_payload_keys:
|
||||
if key in memory.payload:
|
||||
result_item[key] = memory.payload[key]
|
||||
|
||||
additional_metadata = {
|
||||
k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys
|
||||
}
|
||||
|
||||
additional_metadata = {k: v for k, v in memory.payload.items() if k not in core_and_promoted_keys}
|
||||
if additional_metadata:
|
||||
result_item["metadata"] = additional_metadata
|
||||
|
||||
|
||||
return result_item
|
||||
|
||||
async def get_all(
|
||||
self,
|
||||
*,
|
||||
*,
|
||||
user_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
@@ -1314,41 +1336,36 @@ class AsyncMemory(MemoryBase):
|
||||
limit: int = 100,
|
||||
):
|
||||
"""
|
||||
List all memories.
|
||||
List all memories.
|
||||
|
||||
Args:
|
||||
user_id (str, optional): user id
|
||||
agent_id (str, optional): agent id
|
||||
run_id (str, optional): run id
|
||||
filters (dict, optional): Additional custom key-value filters to apply to the search.
|
||||
These are merged with the ID-based scoping filters. For example,
|
||||
`filters={"actor_id": "some_user"}`.
|
||||
limit (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
Args:
|
||||
user_id (str, optional): user id
|
||||
agent_id (str, optional): agent id
|
||||
run_id (str, optional): run id
|
||||
filters (dict, optional): Additional custom key-value filters to apply to the search.
|
||||
These are merged with the ID-based scoping filters. For example,
|
||||
`filters={"actor_id": "some_user"}`.
|
||||
limit (int, optional): The maximum number of memories to return. Defaults to 100.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing a list of memories under the "results" key,
|
||||
and potentially "relations" if graph store is enabled. For API v1.0,
|
||||
it might return a direct list (see deprecation warning).
|
||||
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", ...}]}`
|
||||
Returns:
|
||||
dict: A dictionary containing a list of memories under the "results" key,
|
||||
and potentially "relations" if graph store is enabled. For API v1.0,
|
||||
it might return a direct list (see deprecation warning).
|
||||
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", ...}]}`
|
||||
"""
|
||||
|
||||
|
||||
_, effective_filters = _build_filters_and_metadata(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
input_filters=filters
|
||||
user_id=user_id, 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")):
|
||||
raise ValueError(
|
||||
"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."
|
||||
)
|
||||
raise ValueError(
|
||||
"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."
|
||||
)
|
||||
|
||||
capture_event(
|
||||
"mem0.get_all",
|
||||
self,
|
||||
{"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "async"}
|
||||
"mem0.get_all", self, {"limit": limit, "keys": list(effective_filters.keys()), "sync_type": "async"}
|
||||
)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
@@ -1361,9 +1378,9 @@ class AsyncMemory(MemoryBase):
|
||||
[future_memories, future_graph_entities] if future_graph_entities else [future_memories]
|
||||
)
|
||||
|
||||
all_memories_result = future_memories.result()
|
||||
all_memories_result = future_memories.result()
|
||||
graph_entities_result = future_graph_entities.result() if future_graph_entities else None
|
||||
|
||||
|
||||
if self.enable_graph:
|
||||
return {"results": all_memories_result, "relations": graph_entities_result}
|
||||
|
||||
@@ -1381,20 +1398,21 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
async def _get_all_from_vector_store(self, filters, limit):
|
||||
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit)
|
||||
actual_memories = memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result
|
||||
actual_memories = (
|
||||
memories_result[0] if isinstance(memories_result, tuple) and len(memories_result) > 0 else memories_result
|
||||
)
|
||||
|
||||
promoted_payload_keys = [
|
||||
"user_id", "agent_id", "run_id",
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"actor_id",
|
||||
"role",
|
||||
]
|
||||
core_and_promoted_keys = {
|
||||
"data", "hash", "created_at", "updated_at", "id",
|
||||
*promoted_payload_keys
|
||||
}
|
||||
core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
|
||||
|
||||
formatted_memories = []
|
||||
for mem in actual_memories:
|
||||
for mem in actual_memories:
|
||||
memory_item_dict = MemoryItem(
|
||||
id=mem.id,
|
||||
memory=mem.payload["data"],
|
||||
@@ -1406,15 +1424,13 @@ class AsyncMemory(MemoryBase):
|
||||
for key in promoted_payload_keys:
|
||||
if key in mem.payload:
|
||||
memory_item_dict[key] = mem.payload[key]
|
||||
|
||||
additional_metadata = {
|
||||
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
|
||||
}
|
||||
|
||||
additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
|
||||
if additional_metadata:
|
||||
memory_item_dict["metadata"] = additional_metadata
|
||||
|
||||
|
||||
formatted_memories.append(memory_item_dict)
|
||||
|
||||
|
||||
return formatted_memories
|
||||
|
||||
async def search(
|
||||
@@ -1442,16 +1458,13 @@ class AsyncMemory(MemoryBase):
|
||||
and potentially "relations" if graph store is enabled.
|
||||
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", "score": 0.8, ...}]}`
|
||||
"""
|
||||
|
||||
|
||||
_, effective_filters = _build_filters_and_metadata(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
run_id=run_id,
|
||||
input_filters=filters
|
||||
user_id=user_id, 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")):
|
||||
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(
|
||||
"mem0.search",
|
||||
@@ -1460,22 +1473,20 @@ class AsyncMemory(MemoryBase):
|
||||
)
|
||||
|
||||
vector_store_task = asyncio.create_task(self._search_vector_store(query, effective_filters, limit))
|
||||
|
||||
|
||||
graph_task = None
|
||||
if self.enable_graph:
|
||||
if hasattr(self.graph.search, "__await__"): # Check if graph search is async
|
||||
graph_task = asyncio.create_task(self.graph.search(query, effective_filters, limit))
|
||||
else:
|
||||
graph_task = asyncio.create_task(
|
||||
asyncio.to_thread(self.graph.search, query, effective_filters, limit)
|
||||
)
|
||||
|
||||
graph_task = asyncio.create_task(asyncio.to_thread(self.graph.search, query, effective_filters, limit))
|
||||
|
||||
if graph_task:
|
||||
original_memories, graph_entities = await asyncio.gather(vector_store_task, graph_task)
|
||||
else:
|
||||
original_memories = await vector_store_task
|
||||
graph_entities = None
|
||||
|
||||
|
||||
if self.enable_graph:
|
||||
return {"results": original_memories, "relations": graph_entities}
|
||||
|
||||
@@ -1504,11 +1515,8 @@ class AsyncMemory(MemoryBase):
|
||||
"actor_id",
|
||||
"role",
|
||||
]
|
||||
|
||||
core_and_promoted_keys = {
|
||||
"data", "hash", "created_at", "updated_at", "id",
|
||||
*promoted_payload_keys
|
||||
}
|
||||
|
||||
core_and_promoted_keys = {"data", "hash", "created_at", "updated_at", "id", *promoted_payload_keys}
|
||||
|
||||
original_memories = []
|
||||
for mem in memories:
|
||||
@@ -1518,19 +1526,17 @@ class AsyncMemory(MemoryBase):
|
||||
hash=mem.payload.get("hash"),
|
||||
created_at=mem.payload.get("created_at"),
|
||||
updated_at=mem.payload.get("updated_at"),
|
||||
score=mem.score,
|
||||
).model_dump()
|
||||
score=mem.score,
|
||||
).model_dump()
|
||||
|
||||
for key in promoted_payload_keys:
|
||||
if key in mem.payload:
|
||||
memory_item_dict[key] = mem.payload[key]
|
||||
|
||||
additional_metadata = {
|
||||
k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys
|
||||
}
|
||||
|
||||
additional_metadata = {k: v for k, v in mem.payload.items() if k not in core_and_promoted_keys}
|
||||
if additional_metadata:
|
||||
memory_item_dict["metadata"] = additional_metadata
|
||||
|
||||
|
||||
original_memories.append(memory_item_dict)
|
||||
|
||||
return original_memories
|
||||
@@ -1650,7 +1656,7 @@ class AsyncMemory(MemoryBase):
|
||||
capture_event("mem0._create_memory", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
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
|
||||
|
||||
@@ -1709,11 +1715,11 @@ class AsyncMemory(MemoryBase):
|
||||
except Exception:
|
||||
logger.error(f"Error getting memory with ID {memory_id} during update.")
|
||||
raise ValueError(f"Error getting memory with ID {memory_id}. Please provide a valid 'memory_id'")
|
||||
|
||||
|
||||
prev_value = existing_memory.payload.get("data")
|
||||
|
||||
new_metadata = deepcopy(metadata) if metadata is not None else {}
|
||||
|
||||
|
||||
new_metadata["data"] = data
|
||||
new_metadata["hash"] = hashlib.md5(data.encode()).hexdigest()
|
||||
new_metadata["created_at"] = existing_memory.payload.get("created_at")
|
||||
@@ -1725,8 +1731,7 @@ class AsyncMemory(MemoryBase):
|
||||
new_metadata["agent_id"] = existing_memory.payload["agent_id"]
|
||||
if "run_id" in existing_memory.payload:
|
||||
new_metadata["run_id"] = existing_memory.payload["run_id"]
|
||||
|
||||
|
||||
|
||||
if "actor_id" in existing_memory.payload:
|
||||
new_metadata["actor_id"] = existing_memory.payload["actor_id"]
|
||||
if "role" in existing_memory.payload:
|
||||
@@ -1736,7 +1741,7 @@ class AsyncMemory(MemoryBase):
|
||||
embeddings = existing_embeddings[data]
|
||||
else:
|
||||
embeddings = await asyncio.to_thread(self.embedding_model.embed, data, "update")
|
||||
|
||||
|
||||
await asyncio.to_thread(
|
||||
self.vector_store.update,
|
||||
vector_id=memory_id,
|
||||
@@ -1744,7 +1749,7 @@ class AsyncMemory(MemoryBase):
|
||||
payload=new_metadata,
|
||||
)
|
||||
logger.info(f"Updating memory with ID {memory_id=} with {data=}")
|
||||
|
||||
|
||||
await asyncio.to_thread(
|
||||
self.db.add_history,
|
||||
memory_id,
|
||||
|
||||
@@ -5,16 +5,12 @@ from mem0.memory.utils import format_entities
|
||||
try:
|
||||
from langchain_memgraph import Memgraph
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"langchain_memgraph is not installed. Please install it using pip install langchain-memgraph"
|
||||
)
|
||||
raise ImportError("langchain_memgraph is not installed. Please install it using pip install langchain-memgraph")
|
||||
|
||||
try:
|
||||
from rank_bm25 import BM25Okapi
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"rank_bm25 is not installed. Please install it using pip install rank-bm25"
|
||||
)
|
||||
raise ImportError("rank_bm25 is not installed. Please install it using pip install rank-bm25")
|
||||
|
||||
from mem0.graphs.tools import (
|
||||
DELETE_MEMORY_STRUCT_TOOL_GRAPH,
|
||||
@@ -74,22 +70,14 @@ class MemoryGraph:
|
||||
filters (dict): A dictionary containing filters to be applied during the addition.
|
||||
"""
|
||||
entity_type_map = self._retrieve_nodes_from_data(data, filters)
|
||||
to_be_added = self._establish_nodes_relations_from_data(
|
||||
data, filters, entity_type_map
|
||||
)
|
||||
search_output = self._search_graph_db(
|
||||
node_list=list(entity_type_map.keys()), filters=filters
|
||||
)
|
||||
to_be_deleted = self._get_delete_entities_from_search_output(
|
||||
search_output, data, filters
|
||||
)
|
||||
to_be_added = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
|
||||
search_output = self._search_graph_db(node_list=list(entity_type_map.keys()), filters=filters)
|
||||
to_be_deleted = self._get_delete_entities_from_search_output(search_output, data, filters)
|
||||
|
||||
# TODO: Batch queries with APOC plugin
|
||||
# TODO: Add more filter support
|
||||
deleted_entities = self._delete_entities(to_be_deleted, filters["user_id"])
|
||||
added_entities = self._add_entities(
|
||||
to_be_added, filters["user_id"], entity_type_map
|
||||
)
|
||||
added_entities = self._add_entities(to_be_added, filters["user_id"], entity_type_map)
|
||||
|
||||
return {"deleted_entities": deleted_entities, "added_entities": added_entities}
|
||||
|
||||
@@ -108,16 +96,13 @@ class MemoryGraph:
|
||||
- "entities": List of related graph data based on the query.
|
||||
"""
|
||||
entity_type_map = self._retrieve_nodes_from_data(query, filters)
|
||||
search_output = self._search_graph_db(
|
||||
node_list=list(entity_type_map.keys()), filters=filters
|
||||
)
|
||||
search_output = self._search_graph_db(node_list=list(entity_type_map.keys()), filters=filters)
|
||||
|
||||
if not search_output:
|
||||
return []
|
||||
|
||||
search_outputs_sequence = [
|
||||
[item["source"], item["relationship"], item["destination"]]
|
||||
for item in search_output
|
||||
[item["source"], item["relationship"], item["destination"]] for item in search_output
|
||||
]
|
||||
bm25 = BM25Okapi(search_outputs_sequence)
|
||||
|
||||
@@ -126,9 +111,7 @@ class MemoryGraph:
|
||||
|
||||
search_results = []
|
||||
for item in reranked_results:
|
||||
search_results.append(
|
||||
{"source": item[0], "relationship": item[1], "destination": item[2]}
|
||||
)
|
||||
search_results.append({"source": item[0], "relationship": item[1], "destination": item[2]})
|
||||
|
||||
logger.info(f"Returned {len(search_results)} search results")
|
||||
|
||||
@@ -161,9 +144,7 @@ class MemoryGraph:
|
||||
RETURN n.name AS source, type(r) AS relationship, m.name AS target
|
||||
LIMIT $limit
|
||||
"""
|
||||
results = self.graph.query(
|
||||
query, params={"user_id": filters["user_id"], "limit": limit}
|
||||
)
|
||||
results = self.graph.query(query, params={"user_id": filters["user_id"], "limit": limit})
|
||||
|
||||
final_results = []
|
||||
for result in results:
|
||||
@@ -208,13 +189,8 @@ class MemoryGraph:
|
||||
f"Error in search tool: {e}, llm_provider={self.llm_provider}, search_results={search_results}"
|
||||
)
|
||||
|
||||
entity_type_map = {
|
||||
k.lower().replace(" ", "_"): v.lower().replace(" ", "_")
|
||||
for k, v in entity_type_map.items()
|
||||
}
|
||||
logger.debug(
|
||||
f"Entity type map: {entity_type_map}\n search_results={search_results}"
|
||||
)
|
||||
entity_type_map = {k.lower().replace(" ", "_"): v.lower().replace(" ", "_") for k, v in entity_type_map.items()}
|
||||
logger.debug(f"Entity type map: {entity_type_map}\n search_results={search_results}")
|
||||
return entity_type_map
|
||||
|
||||
def _establish_nodes_relations_from_data(self, data, filters, entity_type_map):
|
||||
@@ -223,9 +199,7 @@ class MemoryGraph:
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": EXTRACT_RELATIONS_PROMPT.replace(
|
||||
"USER_ID", filters["user_id"]
|
||||
).replace(
|
||||
"content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]).replace(
|
||||
"CUSTOM_PROMPT", f"4. {self.config.graph_store.custom_prompt}"
|
||||
),
|
||||
},
|
||||
@@ -235,9 +209,7 @@ class MemoryGraph:
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": EXTRACT_RELATIONS_PROMPT.replace(
|
||||
"USER_ID", filters["user_id"]
|
||||
),
|
||||
"content": EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["user_id"]),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
@@ -304,9 +276,7 @@ class MemoryGraph:
|
||||
def _get_delete_entities_from_search_output(self, search_output, data, filters):
|
||||
"""Get the entities to be deleted from the search output."""
|
||||
search_output_string = format_entities(search_output)
|
||||
system_prompt, user_prompt = get_delete_messages(
|
||||
search_output_string, data, filters["user_id"]
|
||||
)
|
||||
system_prompt, user_prompt = get_delete_messages(search_output_string, data, filters["user_id"])
|
||||
|
||||
_tools = [DELETE_MEMORY_TOOL_GRAPH]
|
||||
if self.llm_provider in ["azure_openai_structured", "openai_structured"]:
|
||||
@@ -379,12 +349,8 @@ class MemoryGraph:
|
||||
# search for the nodes with the closest embeddings; this is basically
|
||||
# comparison of one embedding to all embeddings in a graph -> vector
|
||||
# search with cosine similarity metric
|
||||
source_node_search_result = self._search_source_node(
|
||||
source_embedding, user_id, threshold=0.9
|
||||
)
|
||||
destination_node_search_result = self._search_destination_node(
|
||||
dest_embedding, user_id, threshold=0.9
|
||||
)
|
||||
source_node_search_result = self._search_source_node(source_embedding, user_id, threshold=0.9)
|
||||
destination_node_search_result = self._search_destination_node(dest_embedding, user_id, threshold=0.9)
|
||||
|
||||
# TODO: Create a cypher query and common params for all the cases
|
||||
if not destination_node_search_result and source_node_search_result:
|
||||
@@ -424,9 +390,7 @@ class MemoryGraph:
|
||||
"""
|
||||
|
||||
params = {
|
||||
"destination_id": destination_node_search_result[0][
|
||||
"id(destination_candidate)"
|
||||
],
|
||||
"destination_id": destination_node_search_result[0]["id(destination_candidate)"],
|
||||
"source_name": source,
|
||||
"source_embedding": source_embedding,
|
||||
"user_id": user_id,
|
||||
@@ -445,9 +409,7 @@ class MemoryGraph:
|
||||
"""
|
||||
params = {
|
||||
"source_id": source_node_search_result[0]["id(source_candidate)"],
|
||||
"destination_id": destination_node_search_result[0][
|
||||
"id(destination_candidate)"
|
||||
],
|
||||
"destination_id": destination_node_search_result[0]["id(destination_candidate)"],
|
||||
"user_id": user_id,
|
||||
}
|
||||
else:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
import threading
|
||||
import uuid
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,9 +23,7 @@ class SQLiteManager:
|
||||
"""
|
||||
with self._lock, self.connection:
|
||||
cur = self.connection.cursor()
|
||||
cur.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='history'"
|
||||
)
|
||||
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='history'")
|
||||
if cur.fetchone() is None:
|
||||
return # nothing to migrate
|
||||
|
||||
@@ -51,13 +49,11 @@ class SQLiteManager:
|
||||
logger.info("Migrating history table to new schema (no convo columns).")
|
||||
cur.execute("ALTER TABLE history RENAME TO history_old")
|
||||
|
||||
self._create_history_table()
|
||||
self._create_history_table()
|
||||
|
||||
intersecting = list(expected_cols & old_cols)
|
||||
cols_csv = ", ".join(intersecting)
|
||||
cur.execute(
|
||||
f"INSERT INTO history ({cols_csv}) SELECT {cols_csv} FROM history_old"
|
||||
)
|
||||
cur.execute(f"INSERT INTO history ({cols_csv}) SELECT {cols_csv} FROM history_old")
|
||||
cur.execute("DROP TABLE history_old")
|
||||
|
||||
def _create_history_table(self) -> None:
|
||||
|
||||
@@ -9,8 +9,8 @@ import mem0
|
||||
from mem0.memory.setup import get_or_create_user_id
|
||||
|
||||
MEM0_TELEMETRY = os.environ.get("MEM0_TELEMETRY", "True")
|
||||
PROJECT_API_KEY="phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||
HOST="https://us.i.posthog.com"
|
||||
PROJECT_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||
HOST = "https://us.i.posthog.com"
|
||||
|
||||
if isinstance(MEM0_TELEMETRY, str):
|
||||
MEM0_TELEMETRY = MEM0_TELEMETRY.lower() in ("true", "1", "yes")
|
||||
|
||||
@@ -27,6 +27,7 @@ class LlmFactory:
|
||||
"gemini": "mem0.llms.gemini.GeminiLLM",
|
||||
"deepseek": "mem0.llms.deepseek.DeepSeekLLM",
|
||||
"xai": "mem0.llms.xai.XAILLM",
|
||||
"sarvam": "mem0.llms.sarvam.SarvamLLM",
|
||||
"lmstudio": "mem0.llms.lmstudio.LMStudioLLM",
|
||||
"langchain": "mem0.llms.langchain.LangchainLLM",
|
||||
}
|
||||
@@ -98,9 +99,8 @@ class VectorStoreFactory:
|
||||
return vector_store_instance(**config)
|
||||
else:
|
||||
raise ValueError(f"Unsupported VectorStore provider: {provider_name}")
|
||||
|
||||
|
||||
@classmethod
|
||||
def reset(cls, instance):
|
||||
instance.reset()
|
||||
return instance
|
||||
|
||||
|
||||
@@ -377,4 +377,3 @@ class AzureAISearch(VectorStoreBase):
|
||||
except Exception as e:
|
||||
logger.error(f"Error resetting index {self.index_name}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class VectorStoreBase(ABC):
|
||||
def list(self, filters=None, limit=None):
|
||||
"""List all memories."""
|
||||
pass
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def reset(self):
|
||||
"""Reset by delete the collection and recreate it."""
|
||||
|
||||
@@ -221,7 +221,7 @@ class ChromaDB(VectorStoreBase):
|
||||
"""
|
||||
results = self.collection.get(where=filters, limit=limit)
|
||||
return [self._parse_output(results)]
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -58,7 +58,12 @@ class ElasticsearchDB(VectorStoreBase):
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"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"}}},
|
||||
}
|
||||
},
|
||||
@@ -222,7 +227,7 @@ class ElasticsearchDB(VectorStoreBase):
|
||||
)
|
||||
|
||||
return [results]
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -465,7 +465,7 @@ class FAISS(VectorStoreBase):
|
||||
break
|
||||
|
||||
return [results]
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -14,6 +14,7 @@ from mem0.vector_stores.base import VectorStoreBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OutputData(BaseModel):
|
||||
id: Optional[str] # memory id
|
||||
score: Optional[float] # distance
|
||||
@@ -162,10 +163,7 @@ class Langchain(VectorStoreBase):
|
||||
if filters and "user_id" in filters:
|
||||
where_clause = {"user_id": filters["user_id"]}
|
||||
|
||||
result = self.client._collection.get(
|
||||
where=where_clause,
|
||||
limit=limit
|
||||
)
|
||||
result = self.client._collection.get(where=where_clause, limit=limit)
|
||||
|
||||
# Convert the result to the expected format
|
||||
if result and isinstance(result, dict):
|
||||
|
||||
@@ -237,7 +237,7 @@ class MilvusDB(VectorStoreBase):
|
||||
obj = OutputData(id=data.get("id"), score=None, payload=data.get("metadata"))
|
||||
memories.append(obj)
|
||||
return [memories]
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
try:
|
||||
from opensearchpy import OpenSearch, RequestsHttpConnection
|
||||
@@ -34,7 +34,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
use_ssl=config.use_ssl,
|
||||
verify_certs=config.verify_certs,
|
||||
connection_class=RequestsHttpConnection,
|
||||
pool_maxsize=20
|
||||
pool_maxsize=20,
|
||||
)
|
||||
|
||||
self.collection_name = config.collection_name
|
||||
@@ -69,9 +69,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
def create_col(self, name: str, vector_size: int) -> None:
|
||||
"""Create a new collection (index in OpenSearch)."""
|
||||
index_settings = {
|
||||
"settings": {
|
||||
"index.knn": True
|
||||
},
|
||||
"settings": {"index.knn": True},
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"vector_field": {
|
||||
@@ -82,7 +80,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
"payload": {"type": "object"},
|
||||
"id": {"type": "keyword"},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if not self.client.indices.exists(index=name):
|
||||
@@ -102,9 +100,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
except Exception:
|
||||
retry_count += 1
|
||||
if retry_count == max_retries:
|
||||
raise TimeoutError(
|
||||
f"Index {name} creation timed out after {max_retries} seconds"
|
||||
)
|
||||
raise TimeoutError(f"Index {name} creation timed out after {max_retries} seconds")
|
||||
time.sleep(0.5)
|
||||
|
||||
def insert(
|
||||
@@ -145,10 +141,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
}
|
||||
|
||||
# Start building the full query
|
||||
query_body = {
|
||||
"size": limit * 2,
|
||||
"query": None
|
||||
}
|
||||
query_body = {"size": limit * 2, "query": None}
|
||||
|
||||
# Prepare filter conditions if applicable
|
||||
filter_clauses = []
|
||||
@@ -156,18 +149,11 @@ class OpenSearchDB(VectorStoreBase):
|
||||
for key in ["user_id", "run_id", "agent_id"]:
|
||||
value = filters.get(key)
|
||||
if value:
|
||||
filter_clauses.append({
|
||||
"term": {f"payload.{key}.keyword": value}
|
||||
})
|
||||
filter_clauses.append({"term": {f"payload.{key}.keyword": value}})
|
||||
|
||||
# Combine knn with filters if needed
|
||||
if filter_clauses:
|
||||
query_body["query"] = {
|
||||
"bool": {
|
||||
"must": knn_query,
|
||||
"filter": filter_clauses
|
||||
}
|
||||
}
|
||||
query_body["query"] = {"bool": {"must": knn_query, "filter": filter_clauses}}
|
||||
else:
|
||||
query_body["query"] = knn_query
|
||||
|
||||
@@ -176,11 +162,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
|
||||
hits = response["hits"]["hits"]
|
||||
results = [
|
||||
OutputData(
|
||||
id=hit["_source"].get("id"),
|
||||
score=hit["_score"],
|
||||
payload=hit["_source"].get("payload", {})
|
||||
)
|
||||
OutputData(id=hit["_source"].get("id"), score=hit["_score"], payload=hit["_source"].get("payload", {}))
|
||||
for hit in hits
|
||||
]
|
||||
return results
|
||||
@@ -188,13 +170,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
def delete(self, vector_id: str) -> None:
|
||||
"""Delete a vector by custom ID."""
|
||||
# First, find the document by custom ID
|
||||
search_query = {
|
||||
"query": {
|
||||
"term": {
|
||||
"id": vector_id
|
||||
}
|
||||
}
|
||||
}
|
||||
search_query = {"query": {"term": {"id": vector_id}}}
|
||||
|
||||
response = self.client.search(index=self.collection_name, body=search_query)
|
||||
hits = response.get("hits", {}).get("hits", [])
|
||||
@@ -207,18 +183,11 @@ class OpenSearchDB(VectorStoreBase):
|
||||
# Delete using the actual document 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:
|
||||
"""Update a vector and its payload using the custom 'id' field."""
|
||||
|
||||
# First, find the document by custom ID
|
||||
search_query = {
|
||||
"query": {
|
||||
"term": {
|
||||
"id": vector_id
|
||||
}
|
||||
}
|
||||
}
|
||||
search_query = {"query": {"term": {"id": vector_id}}}
|
||||
|
||||
response = self.client.search(index=self.collection_name, body=search_query)
|
||||
hits = response.get("hits", {}).get("hits", [])
|
||||
@@ -241,7 +210,6 @@ class OpenSearchDB(VectorStoreBase):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get(self, vector_id: str) -> Optional[OutputData]:
|
||||
"""Retrieve a vector by ID."""
|
||||
try:
|
||||
@@ -251,13 +219,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
self.create_col(self.collection_name, self.embedding_model_dims)
|
||||
return None
|
||||
|
||||
search_query = {
|
||||
"query": {
|
||||
"term": {
|
||||
"id": vector_id
|
||||
}
|
||||
}
|
||||
}
|
||||
search_query = {"query": {"term": {"id": vector_id}}}
|
||||
response = self.client.search(index=self.collection_name, body=search_query)
|
||||
|
||||
hits = response["hits"]["hits"]
|
||||
@@ -265,11 +227,7 @@ class OpenSearchDB(VectorStoreBase):
|
||||
if not hits:
|
||||
return None
|
||||
|
||||
return OutputData(
|
||||
id=hits[0]["_source"].get("id"),
|
||||
score=1.0,
|
||||
payload=hits[0]["_source"].get("payload", {})
|
||||
)
|
||||
return OutputData(id=hits[0]["_source"].get("id"), score=1.0, payload=hits[0]["_source"].get("payload", {}))
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving vector {vector_id}: {str(e)}")
|
||||
return None
|
||||
@@ -287,30 +245,19 @@ class OpenSearchDB(VectorStoreBase):
|
||||
return self.client.indices.get(index=name)
|
||||
|
||||
def list(self, filters: Optional[Dict] = None, limit: Optional[int] = None) -> List[OutputData]:
|
||||
|
||||
try:
|
||||
"""List all memories with optional filters."""
|
||||
query: Dict = {
|
||||
"query": {
|
||||
"match_all": {}
|
||||
}
|
||||
}
|
||||
query: Dict = {"query": {"match_all": {}}}
|
||||
|
||||
filter_clauses = []
|
||||
if filters:
|
||||
for key in ["user_id", "run_id", "agent_id"]:
|
||||
value = filters.get(key)
|
||||
if value:
|
||||
filter_clauses.append({
|
||||
"term": {f"payload.{key}.keyword": value}
|
||||
})
|
||||
filter_clauses.append({"term": {f"payload.{key}.keyword": value}})
|
||||
|
||||
if filter_clauses:
|
||||
query["query"] = {
|
||||
"bool": {
|
||||
"filter": filter_clauses
|
||||
}
|
||||
}
|
||||
query["query"] = {"bool": {"filter": filter_clauses}}
|
||||
|
||||
if limit:
|
||||
query["size"] = limit
|
||||
@@ -318,18 +265,15 @@ class OpenSearchDB(VectorStoreBase):
|
||||
response = self.client.search(index=self.collection_name, body=query)
|
||||
hits = response["hits"]["hits"]
|
||||
|
||||
return [[
|
||||
OutputData(
|
||||
id=hit["_source"].get("id"),
|
||||
score=1.0,
|
||||
payload=hit["_source"].get("payload", {})
|
||||
)
|
||||
for hit in hits
|
||||
]]
|
||||
return [
|
||||
[
|
||||
OutputData(id=hit["_source"].get("id"), score=1.0, payload=hit["_source"].get("payload", {}))
|
||||
for hit in hits
|
||||
]
|
||||
]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -286,7 +286,7 @@ class PGVector(VectorStoreBase):
|
||||
self.cur.close()
|
||||
if hasattr(self, "conn"):
|
||||
self.conn.close()
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -232,7 +232,7 @@ class Qdrant(VectorStoreBase):
|
||||
with_vectors=False,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
@@ -88,7 +88,7 @@ class RedisDB(VectorStoreBase):
|
||||
The created index object.
|
||||
"""
|
||||
# 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
|
||||
distance_metric = distance or "cosine"
|
||||
|
||||
@@ -237,17 +237,16 @@ class RedisDB(VectorStoreBase):
|
||||
"""
|
||||
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}...")
|
||||
self.delete_col()
|
||||
|
||||
|
||||
self.index = SearchIndex.from_dict(self.schema)
|
||||
self.index.set_client(self.client)
|
||||
self.index.create(overwrite=True)
|
||||
|
||||
#or use
|
||||
#self.create_col(collection_name, self.embedding_model_dims)
|
||||
|
||||
# or use
|
||||
# self.create_col(collection_name, self.embedding_model_dims)
|
||||
|
||||
# Recreate the index with the same parameters
|
||||
self.create_col(collection_name, self.embedding_model_dims)
|
||||
|
||||
@@ -229,7 +229,7 @@ class Supabase(VectorStoreBase):
|
||||
records = self.collection.fetch(ids=ids)
|
||||
|
||||
return [[OutputData(id=str(record[0]), score=None, payload=record[2]) for record in records]]
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""Reset the index by deleting and recreating it."""
|
||||
logger.warning(f"Resetting index {self.collection_name}...")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user