Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78912928bc | ||
|
|
8bd0d2dc24 | ||
|
|
f0bdd2c341 | ||
|
|
bf0c4adc0c | ||
|
|
2cca50db80 | ||
|
|
b8e4d0980a | ||
|
|
3613e2f14a | ||
|
|
541030d69c |
@@ -6,6 +6,43 @@ mode: "wide"
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
<Update label="2025-04-16" description="v0.1.91">
|
||||
|
||||
**New Features:**
|
||||
- **LLM Integrations:** Added Azure OpenAI Embedding Model
|
||||
- **Examples:**
|
||||
- Added movie recommendation using grok3
|
||||
- Added Voice Assistant using Elevenlabs
|
||||
|
||||
**Improvements:**
|
||||
- **Documentation:**
|
||||
- Added keywords AI
|
||||
- Reformatted navbar page URLs
|
||||
- Updated changelog
|
||||
- Updated openai.mdx
|
||||
- **FAISS:** Silenced FAISS info logs
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-04-11" description="v0.1.90">
|
||||
|
||||
**New Features:**
|
||||
- **LLM Integrations:** Added Mistral AI as LLM provider
|
||||
|
||||
**Improvements:**
|
||||
- **Documentation:**
|
||||
- Updated changelog
|
||||
- Fixed memory exclusion example
|
||||
- Updated xAI documentation
|
||||
- Updated YouTube Chrome extension example documentation
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** Fixed EmbedderFactory.create() in GraphMemory
|
||||
- **Azure OpenAI:** Added patch to fix Azure OpenAI
|
||||
- **Telemetry:** Fixed telemetry issue
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-04-11" description="v0.1.89">
|
||||
|
||||
**New Features:**
|
||||
@@ -127,6 +164,10 @@ mode: "wide"
|
||||
|
||||
<Tab title="TypeScript">
|
||||
|
||||
<Update label="2025-04-17" description="v2.1.18">
|
||||
**Improvements:**
|
||||
- **Client:** Added support for custom instructions
|
||||
</Update>
|
||||
|
||||
<Update label="2025-04-15" description="v2.1.17">
|
||||
**New Features:**
|
||||
|
||||
+1
-1
@@ -1229,7 +1229,7 @@
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl -X POST 'https://api.mem0.ai/v2/memories/' \\\n-H 'Authorization: Token your-api-key' \\\n-H 'Content-Type: application/json' \\\n-d '{\n \"filters\": {\n \"AND\": [\n { \"user_id\": \"alex\" },\n { \"created_at\": { \"gte\": \"2024-07-01\", \"lte\": \"2024-07-31\" } }\n ]\n }\n}'"
|
||||
"source": "curl -X POST 'https://api.mem0.ai/v2/memories/' \\\n-H 'Authorization: Token your-api-key' \\\n-H 'Content-Type: application/json' \\\n-d '{\n \"filters\": {\n \"AND\": [\n { \"user_id\": \"alex\" },\n { \"created_at\": { \"gte\": \"2024-07-01\", \"lte\": \"2024-07-31\" } }\n ]\n },\n \"org_id\": \"your-org-id\",\n \"project_id\": \"your-project-id\"\n}'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Simple Fitness Memory Tracker that tracks your fitness progress and knows your health priorities.
|
||||
Uses Mem0 for memory and GPT-4o for image understanding.
|
||||
|
||||
In order to run this file, you need to set up your Mem0 API at Mem0 platform and also need an OpenAI API key.
|
||||
export OPENAI_API_KEY="your_openai_api_key"
|
||||
export MEM0_API_KEY="your_mem0_api_key"
|
||||
"""
|
||||
|
||||
from mem0 import MemoryClient
|
||||
from agno.agent import Agent
|
||||
from agno.models.openai import OpenAIChat
|
||||
|
||||
# Initialize memory
|
||||
memory_client = MemoryClient(api_key="your-mem0-api-key")
|
||||
USER_ID = "Anish"
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
# 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-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
|
||||
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.
|
||||
|
||||
Use your memory to personalize suggestions — consider his constraints, goals, patterns, and lifestyle when responding.
|
||||
|
||||
Here is what you remember about {user_id}:
|
||||
{memory_context}
|
||||
|
||||
User query:
|
||||
{user_input}"""
|
||||
response = agent.run(prompt)
|
||||
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."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"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": "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": "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."
|
||||
},
|
||||
]
|
||||
store_user_preferences(messages)
|
||||
|
||||
# Example usage with fitness coach
|
||||
fitness_coach("How much was I lifting for bench press a month ago?")
|
||||
# OUTPUT: A month ago, you were lifting 55kg for your bench press as part of your push day routine. It looks like you've increased your bench press weight by 5kg since then! Keep up the good work on your journey to gain lean muscle.
|
||||
fitness_coach("Suggest a post-workout meal, but I’ve had poor sleep last night.")
|
||||
# OUTPUT: Anish, since you had poor sleep, focus on a recovery-friendly, lactose-free meal: tofu or chicken for protein, paired with quinoa or brown rice for lasting energy. Turmeric almond milk will help with inflammation. Based on your past leg day recovery, continue magnesium, stay well-hydrated, and avoid caffeine after 4PM. Aim for 7–8 hours of sleep, and consider light stretching or a warm bath to ease soreness.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "2.1.17",
|
||||
"version": "2.1.18",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface MemoryOptions {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
custom_instructions?: string;
|
||||
}
|
||||
|
||||
export interface ProjectOptions {
|
||||
|
||||
+62
-54
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
import hashlib
|
||||
from functools import wraps
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
@@ -83,13 +84,16 @@ class MemoryClient:
|
||||
if not self.api_key:
|
||||
raise ValueError("Mem0 API Key not provided. Please provide an API Key.")
|
||||
|
||||
# Create MD5 hash of API key for user_id
|
||||
self.user_id = hashlib.md5(self.api_key.encode()).hexdigest()
|
||||
|
||||
self.client = httpx.Client(
|
||||
base_url=self.host,
|
||||
headers={"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id},
|
||||
timeout=300,
|
||||
)
|
||||
self.user_email = self._validate_api_key()
|
||||
capture_client_event("client.init", self)
|
||||
capture_client_event("client.init", self, {"sync_type": "sync"})
|
||||
|
||||
def _validate_api_key(self):
|
||||
"""Validate the API key by making a test request."""
|
||||
@@ -143,7 +147,7 @@ class MemoryClient:
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
capture_client_event("client.add", self, {"keys": list(kwargs.keys())})
|
||||
capture_client_event("client.add", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -162,7 +166,7 @@ class MemoryClient:
|
||||
params = self._prepare_params()
|
||||
response = self.client.get(f"/v1/memories/{memory_id}/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.get", self, {"memory_id": memory_id})
|
||||
capture_client_event("client.get", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -194,7 +198,7 @@ class MemoryClient:
|
||||
capture_client_event(
|
||||
"client.get_all",
|
||||
self,
|
||||
{"api_version": version, "keys": list(kwargs.keys())},
|
||||
{"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -220,7 +224,7 @@ 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())})
|
||||
capture_client_event("client.search", self, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -233,7 +237,7 @@ class MemoryClient:
|
||||
Returns:
|
||||
Dict[str, Any]: The response from the server.
|
||||
"""
|
||||
capture_client_event("client.update", self, {"memory_id": memory_id})
|
||||
capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
params = self._prepare_params()
|
||||
response = self.client.put(f"/v1/memories/{memory_id}/", json={"text": data}, params=params)
|
||||
response.raise_for_status()
|
||||
@@ -255,7 +259,7 @@ class MemoryClient:
|
||||
params = self._prepare_params()
|
||||
response = self.client.delete(f"/v1/memories/{memory_id}/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.delete", self, {"memory_id": memory_id})
|
||||
capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -274,7 +278,7 @@ class MemoryClient:
|
||||
params = self._prepare_params(kwargs)
|
||||
response = self.client.delete("/v1/memories/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys())})
|
||||
capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -293,7 +297,7 @@ class MemoryClient:
|
||||
params = self._prepare_params()
|
||||
response = self.client.get(f"/v1/memories/{memory_id}/history/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.history", self, {"memory_id": memory_id})
|
||||
capture_client_event("client.history", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -302,7 +306,7 @@ class MemoryClient:
|
||||
params = self._prepare_params()
|
||||
response = self.client.get("/v1/entities/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.users", self)
|
||||
capture_client_event("client.users", self, {"sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -354,7 +358,7 @@ 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}
|
||||
"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."
|
||||
@@ -378,7 +382,7 @@ class MemoryClient:
|
||||
# This will also delete the memories
|
||||
self.delete_users()
|
||||
|
||||
capture_client_event("client.reset", self)
|
||||
capture_client_event("client.reset", self, {"sync_type": "sync"})
|
||||
return {"message": "Client reset successful. All users and memories deleted."}
|
||||
|
||||
@api_error_handler
|
||||
@@ -399,7 +403,7 @@ class MemoryClient:
|
||||
response = self.client.put("/v1/batch/", json={"memories": memories})
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event("client.batch_update", self)
|
||||
capture_client_event("client.batch_update", self, {"sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -419,7 +423,7 @@ class MemoryClient:
|
||||
response = self.client.request("DELETE", "/v1/batch/", json={"memories": memories})
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event("client.batch_delete", self)
|
||||
capture_client_event("client.batch_delete", self, {"sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -435,7 +439,7 @@ 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())})
|
||||
capture_client_event("client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -450,7 +454,7 @@ class MemoryClient:
|
||||
"""
|
||||
response = self.client.post("/v1/exports/get/", json=self._prepare_params(kwargs))
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys())})
|
||||
capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -476,12 +480,15 @@ class MemoryClient:
|
||||
params=params,
|
||||
)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.get_project_details", self, {"fields": fields})
|
||||
capture_client_event("client.get_project_details", self, {"fields": fields, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
def update_project(
|
||||
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None
|
||||
self,
|
||||
custom_instructions: Optional[str] = None,
|
||||
custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Update the project settings.
|
||||
|
||||
@@ -499,13 +506,13 @@ 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:
|
||||
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
|
||||
raise ValueError(
|
||||
"Currently we only support updating custom_instructions or custom_categories, so you must provide at least one of them"
|
||||
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
|
||||
)
|
||||
|
||||
payload = self._prepare_params(
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories}
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
|
||||
)
|
||||
response = self.client.patch(
|
||||
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
|
||||
@@ -515,7 +522,7 @@ class MemoryClient:
|
||||
capture_client_event(
|
||||
"client.update_project",
|
||||
self,
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories},
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "sync"},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -544,7 +551,7 @@ class MemoryClient:
|
||||
|
||||
response = self.client.get(f"api/v1/webhooks/projects/{project_id}/")
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.get_webhook", self)
|
||||
capture_client_event("client.get_webhook", self, {"sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -567,7 +574,7 @@ class MemoryClient:
|
||||
payload = {"url": url, "name": name, "event_types": event_types}
|
||||
response = self.client.post(f"api/v1/webhooks/projects/{project_id}/", json=payload)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.create_webhook", self)
|
||||
capture_client_event("client.create_webhook", self, {"sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -596,7 +603,7 @@ class MemoryClient:
|
||||
payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
|
||||
response = self.client.put(f"api/v1/webhooks/{webhook_id}/", json=payload)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.update_webhook", self, {"webhook_id": webhook_id})
|
||||
capture_client_event("client.update_webhook", self, {"webhook_id": webhook_id, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -615,7 +622,7 @@ class MemoryClient:
|
||||
|
||||
response = self.client.delete(f"api/v1/webhooks/{webhook_id}/")
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id})
|
||||
capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id, "sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -632,7 +639,7 @@ class MemoryClient:
|
||||
|
||||
response = self.client.post("/v1/feedback/", json=data)
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.feedback", self, data)
|
||||
capture_client_event("client.feedback", self, data, {"sync_type": "sync"})
|
||||
return response.json()
|
||||
|
||||
def _prepare_payload(
|
||||
@@ -721,7 +728,7 @@ class AsyncMemoryClient:
|
||||
response.raise_for_status()
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
capture_client_event("async_client.add", self.sync_client, {"keys": list(kwargs.keys())})
|
||||
capture_client_event("client.add", self.sync_client, {"keys": list(kwargs.keys()), "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -729,7 +736,7 @@ class AsyncMemoryClient:
|
||||
params = self.sync_client._prepare_params()
|
||||
response = await self.async_client.get(f"/v1/memories/{memory_id}/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.get", self.sync_client, {"memory_id": memory_id})
|
||||
capture_client_event("client.get", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -743,7 +750,7 @@ class AsyncMemoryClient:
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
capture_client_event(
|
||||
"async_client.get_all", self.sync_client, {"api_version": version, "keys": list(kwargs.keys())}
|
||||
"client.get_all", self.sync_client, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "async"}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -756,7 +763,7 @@ class AsyncMemoryClient:
|
||||
if "metadata" in kwargs:
|
||||
del kwargs["metadata"]
|
||||
capture_client_event(
|
||||
"async_client.search", self.sync_client, {"api_version": version, "keys": list(kwargs.keys())}
|
||||
"client.search", self.sync_client, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "async"}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -765,7 +772,7 @@ class AsyncMemoryClient:
|
||||
params = self.sync_client._prepare_params()
|
||||
response = await self.async_client.put(f"/v1/memories/{memory_id}/", json={"text": data}, params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.update", self.sync_client, {"memory_id": memory_id})
|
||||
capture_client_event("client.update", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -773,7 +780,7 @@ class AsyncMemoryClient:
|
||||
params = self.sync_client._prepare_params()
|
||||
response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.delete", self.sync_client, {"memory_id": memory_id})
|
||||
capture_client_event("client.delete", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -781,7 +788,7 @@ class AsyncMemoryClient:
|
||||
params = self.sync_client._prepare_params(kwargs)
|
||||
response = await self.async_client.delete("/v1/memories/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.delete_all", self.sync_client, {"keys": list(kwargs.keys())})
|
||||
capture_client_event("client.delete_all", self.sync_client, {"keys": list(kwargs.keys()), "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -789,7 +796,7 @@ class AsyncMemoryClient:
|
||||
params = self.sync_client._prepare_params()
|
||||
response = await self.async_client.get(f"/v1/memories/{memory_id}/history/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.history", self.sync_client, {"memory_id": memory_id})
|
||||
capture_client_event("client.history", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -797,7 +804,7 @@ class AsyncMemoryClient:
|
||||
params = self.sync_client._prepare_params()
|
||||
response = await self.async_client.get("/v1/entities/", params=params)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.users", self.sync_client)
|
||||
capture_client_event("client.users", self.sync_client, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -848,7 +855,7 @@ class AsyncMemoryClient:
|
||||
response = await self.async_client.delete(f"/v1/entities/{entity['type']}/{entity['id']}/", params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event("async_client.delete_users", self.sync_client)
|
||||
capture_client_event("client.delete_users", self.sync_client, {"sync_type": "async"})
|
||||
return {
|
||||
"message": "Entity deleted successfully."
|
||||
if (user_id or agent_id or app_id or run_id)
|
||||
@@ -858,7 +865,7 @@ class AsyncMemoryClient:
|
||||
@api_error_handler
|
||||
async def reset(self) -> Dict[str, str]:
|
||||
await self.delete_users()
|
||||
capture_client_event("async_client.reset", self.sync_client)
|
||||
capture_client_event("client.reset", self.sync_client, {"sync_type": "async"})
|
||||
return {"message": "Client reset successful. All users and memories deleted."}
|
||||
|
||||
@api_error_handler
|
||||
@@ -879,7 +886,7 @@ class AsyncMemoryClient:
|
||||
response = await self.async_client.put("/v1/batch/", json={"memories": memories})
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event("async_client.batch_update", self.sync_client)
|
||||
capture_client_event("client.batch_update", self.sync_client, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -899,7 +906,7 @@ class AsyncMemoryClient:
|
||||
response = await self.async_client.request("DELETE", "/v1/batch/", json={"memories": memories})
|
||||
response.raise_for_status()
|
||||
|
||||
capture_client_event("async_client.batch_delete", self.sync_client)
|
||||
capture_client_event("client.batch_delete", self.sync_client, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -916,7 +923,7 @@ class AsyncMemoryClient:
|
||||
response = await self.async_client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
|
||||
response.raise_for_status()
|
||||
capture_client_event(
|
||||
"async_client.create_memory_export", self.sync_client, {"schema": schema, "keys": list(kwargs.keys())}
|
||||
"client.create_memory_export", self.sync_client, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "async"}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -932,7 +939,7 @@ class AsyncMemoryClient:
|
||||
"""
|
||||
response = await self.async_client.post("/v1/exports/get/", json=self._prepare_params(kwargs))
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.get_memory_export", self.sync_client, {"keys": list(kwargs.keys())})
|
||||
capture_client_event("client.get_memory_export", self.sync_client, {"keys": list(kwargs.keys()), "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -946,23 +953,24 @@ class AsyncMemoryClient:
|
||||
params=params,
|
||||
)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.get_project", self.sync_client, {"fields": fields})
|
||||
capture_client_event("client.get_project", self.sync_client, {"fields": fields, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
async def update_project(
|
||||
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None
|
||||
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None,
|
||||
retrieval_criteria: Optional[List[Dict[str, Any]]] = None
|
||||
) -> Dict[str, Any]:
|
||||
if not (self.sync_client.org_id and self.sync_client.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:
|
||||
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
|
||||
raise ValueError(
|
||||
"Currently we only support updating custom_instructions or custom_categories, so you must provide at least one of them"
|
||||
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
|
||||
)
|
||||
|
||||
payload = self.sync_client._prepare_params(
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories}
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
|
||||
)
|
||||
response = await self.async_client.patch(
|
||||
f"/api/v1/orgs/organizations/{self.sync_client.org_id}/projects/{self.sync_client.project_id}/",
|
||||
@@ -970,9 +978,9 @@ class AsyncMemoryClient:
|
||||
)
|
||||
response.raise_for_status()
|
||||
capture_client_event(
|
||||
"async_client.update_project",
|
||||
"client.update_project",
|
||||
self.sync_client,
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories},
|
||||
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "async"},
|
||||
)
|
||||
return response.json()
|
||||
|
||||
@@ -985,7 +993,7 @@ class AsyncMemoryClient:
|
||||
f"api/v1/webhooks/projects/{project_id}/",
|
||||
)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.get_webhook", self.sync_client)
|
||||
capture_client_event("client.get_webhook", self.sync_client, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -993,7 +1001,7 @@ class AsyncMemoryClient:
|
||||
payload = {"url": url, "name": name, "event_types": event_types}
|
||||
response = await self.async_client.post(f"api/v1/webhooks/projects/{project_id}/", json=payload)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.create_webhook", self.sync_client)
|
||||
capture_client_event("client.create_webhook", self.sync_client, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -1007,14 +1015,14 @@ class AsyncMemoryClient:
|
||||
payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
|
||||
response = await self.async_client.put(f"api/v1/webhooks/{webhook_id}/", json=payload)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.update_webhook", self.sync_client, {"webhook_id": webhook_id})
|
||||
capture_client_event("client.update_webhook", self.sync_client, {"webhook_id": webhook_id, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
async def delete_webhook(self, webhook_id: int) -> Dict[str, str]:
|
||||
response = await self.async_client.delete(f"api/v1/webhooks/{webhook_id}/")
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.delete_webhook", self.sync_client, {"webhook_id": webhook_id})
|
||||
capture_client_event("client.delete_webhook", self.sync_client, {"webhook_id": webhook_id, "sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@api_error_handler
|
||||
@@ -1031,5 +1039,5 @@ class AsyncMemoryClient:
|
||||
|
||||
response = await self.async_client.post("/v1/feedback/", json=data)
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.feedback", self.sync_client, data)
|
||||
capture_client_event("client.feedback", self.sync_client, data, {"sync_type": "async"})
|
||||
return response.json()
|
||||
|
||||
@@ -6,9 +6,12 @@ from pydantic import BaseModel, Field
|
||||
from mem0.embeddings.configs import EmbedderConfig
|
||||
from mem0.graphs.configs import GraphStoreConfig
|
||||
from mem0.llms.configs import LlmConfig
|
||||
from mem0.memory.setup import mem0_dir
|
||||
from mem0.vector_stores.configs import VectorStoreConfig
|
||||
|
||||
# Set up the directory path
|
||||
home_dir = os.path.expanduser("~")
|
||||
mem0_dir = os.environ.get("MEM0_DIR") or os.path.join(home_dir, ".mem0")
|
||||
|
||||
|
||||
class MemoryItem(BaseModel):
|
||||
id: str = Field(..., description="The unique identifier for the text data")
|
||||
|
||||
@@ -7,7 +7,7 @@ class AzureAISearchConfig(BaseModel):
|
||||
collection_name: str = Field("mem0", description="Name of the collection")
|
||||
service_name: str = Field(None, description="Azure AI Search service name")
|
||||
api_key: str = Field(None, description="API key for the Azure AI Search service")
|
||||
embedding_model_dims: int = Field(None, description="Dimension of the embedding vector")
|
||||
embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
|
||||
compression_type: Optional[str] = Field(
|
||||
None, description="Type of vector compression to use. Options: 'scalar', 'binary', or None"
|
||||
)
|
||||
|
||||
@@ -7,7 +7,9 @@ class LangchainConfig(BaseModel):
|
||||
try:
|
||||
from langchain_community.vectorstores import VectorStore
|
||||
except ImportError:
|
||||
raise ImportError("The 'langchain_community' library is required. Please install it using 'pip install langchain_community'.")
|
||||
raise ImportError(
|
||||
"The 'langchain_community' library is required. Please install it using 'pip install langchain_community'."
|
||||
)
|
||||
VectorStore: ClassVar[type] = VectorStore
|
||||
|
||||
client: VectorStore = Field(description="Existing VectorStore instance")
|
||||
|
||||
@@ -35,9 +35,7 @@ class MemoryGraph:
|
||||
self.config.graph_store.config.password,
|
||||
)
|
||||
self.embedding_model = EmbedderFactory.create(
|
||||
self.config.embedder.provider,
|
||||
self.config.embedder.config,
|
||||
self.config.vector_store.config
|
||||
self.config.embedder.provider, self.config.embedder.config, self.config.vector_store.config
|
||||
)
|
||||
|
||||
self.llm_provider = "openai_structured"
|
||||
|
||||
+77
-34
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
import asyncio
|
||||
import concurrent
|
||||
import gc
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
@@ -18,7 +20,7 @@ from mem0.configs.prompts import (
|
||||
get_update_memory_messages,
|
||||
)
|
||||
from mem0.memory.base import MemoryBase
|
||||
from mem0.memory.setup import setup_config
|
||||
from mem0.memory.setup import setup_config, mem0_dir
|
||||
from mem0.memory.storage import SQLiteManager
|
||||
from mem0.memory.telemetry import capture_event
|
||||
from mem0.memory.utils import (
|
||||
@@ -62,7 +64,17 @@ class Memory(MemoryBase):
|
||||
self.graph = MemoryGraph(self.config)
|
||||
self.enable_graph = True
|
||||
|
||||
capture_event("mem0.init", self)
|
||||
self.config.vector_store.config.collection_name = "mem0_migrations"
|
||||
if self.config.vector_store.provider in ["faiss", "qdrant"]:
|
||||
provider_path = f"migrations_{self.config.vector_store.provider}"
|
||||
self.config.vector_store.config.path = os.path.join(mem0_dir, provider_path)
|
||||
os.makedirs(self.config.vector_store.config.path, exist_ok=True)
|
||||
|
||||
self._telemetry_vector_store = VectorStoreFactory.create(
|
||||
self.config.vector_store.provider, self.config.vector_store.config
|
||||
)
|
||||
|
||||
capture_event("mem0.init", self, {"sync_type": "sync"})
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config_dict: Dict[str, Any]):
|
||||
@@ -318,7 +330,7 @@ class Memory(MemoryBase):
|
||||
capture_event(
|
||||
"mem0.add",
|
||||
self,
|
||||
{"version": self.api_version, "keys": list(filters.keys())},
|
||||
{"version": self.api_version, "keys": list(filters.keys()), "sync_type": "sync"},
|
||||
)
|
||||
|
||||
return returned_memories
|
||||
@@ -344,7 +356,7 @@ class Memory(MemoryBase):
|
||||
Returns:
|
||||
dict: Retrieved memory.
|
||||
"""
|
||||
capture_event("mem0.get", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.get", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
memory = self.vector_store.get(vector_id=memory_id)
|
||||
if not memory:
|
||||
return None
|
||||
@@ -394,7 +406,7 @@ class Memory(MemoryBase):
|
||||
if run_id:
|
||||
filters["run_id"] = run_id
|
||||
|
||||
capture_event("mem0.get_all", self, {"limit": limit, "keys": list(filters.keys())})
|
||||
capture_event("mem0.get_all", self, {"limit": limit, "keys": list(filters.keys()), "sync_type": "sync"})
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future_memories = executor.submit(self._get_all_from_vector_store, filters, limit)
|
||||
@@ -484,7 +496,7 @@ class Memory(MemoryBase):
|
||||
capture_event(
|
||||
"mem0.search",
|
||||
self,
|
||||
{"limit": limit, "version": self.api_version, "keys": list(filters.keys())},
|
||||
{"limit": limit, "version": self.api_version, "keys": list(filters.keys()), "sync_type": "sync"},
|
||||
)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
@@ -563,7 +575,7 @@ class Memory(MemoryBase):
|
||||
Returns:
|
||||
dict: Updated memory.
|
||||
"""
|
||||
capture_event("mem0.update", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.update", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
|
||||
existing_embeddings = {data: self.embedding_model.embed(data, "update")}
|
||||
|
||||
@@ -577,7 +589,7 @@ class Memory(MemoryBase):
|
||||
Args:
|
||||
memory_id (str): ID of the memory to delete.
|
||||
"""
|
||||
capture_event("mem0.delete", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.delete", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
self._delete_memory(memory_id)
|
||||
return {"message": "Memory deleted successfully!"}
|
||||
|
||||
@@ -603,7 +615,7 @@ class Memory(MemoryBase):
|
||||
"At least one filter is required to delete all memories. If you want to delete all memories, use the `reset()` method."
|
||||
)
|
||||
|
||||
capture_event("mem0.delete_all", self, {"keys": list(filters.keys())})
|
||||
capture_event("mem0.delete_all", self, {"keys": list(filters.keys()), "sync_type": "sync"})
|
||||
memories = self.vector_store.list(filters=filters)[0]
|
||||
for memory in memories:
|
||||
self._delete_memory(memory.id)
|
||||
@@ -625,7 +637,7 @@ class Memory(MemoryBase):
|
||||
Returns:
|
||||
list: List of changes for the memory.
|
||||
"""
|
||||
capture_event("mem0.history", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.history", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return self.db.get_history(memory_id)
|
||||
|
||||
def _create_memory(self, data, existing_embeddings, metadata=None):
|
||||
@@ -646,7 +658,7 @@ class Memory(MemoryBase):
|
||||
payloads=[metadata],
|
||||
)
|
||||
self.db.add_history(memory_id, None, data, "ADD", created_at=metadata["created_at"])
|
||||
capture_event("mem0._create_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._create_memory", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return memory_id
|
||||
|
||||
def _create_procedural_memory(self, messages, metadata=None, prompt=None):
|
||||
@@ -683,7 +695,7 @@ class Memory(MemoryBase):
|
||||
embeddings = self.embedding_model.embed(procedural_memory, memory_action="add")
|
||||
# Create the memory
|
||||
memory_id = self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=metadata)
|
||||
capture_event("mem0._create_procedural_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._create_procedural_memory", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
|
||||
# Return results in the same format as add()
|
||||
result = {"results": [{"id": memory_id, "memory": procedural_memory, "event": "ADD"}]}
|
||||
@@ -730,7 +742,7 @@ class Memory(MemoryBase):
|
||||
created_at=new_metadata["created_at"],
|
||||
updated_at=new_metadata["updated_at"],
|
||||
)
|
||||
capture_event("mem0._update_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._update_memory", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return memory_id
|
||||
|
||||
def _delete_memory(self, memory_id):
|
||||
@@ -739,20 +751,37 @@ class Memory(MemoryBase):
|
||||
prev_value = existing_memory.payload["data"]
|
||||
self.vector_store.delete(vector_id=memory_id)
|
||||
self.db.add_history(memory_id, prev_value, None, "DELETE", is_deleted=1)
|
||||
capture_event("mem0._delete_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._delete_memory", self, {"memory_id": memory_id, "sync_type": "sync"})
|
||||
return memory_id
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the memory store.
|
||||
Reset the memory store by:
|
||||
Deletes the vector store collection
|
||||
Resets the database
|
||||
Recreates the vector store with a new client
|
||||
"""
|
||||
logger.warning("Resetting all memories")
|
||||
self.vector_store.delete_col()
|
||||
|
||||
gc.collect()
|
||||
|
||||
# Close the client if it has a close method
|
||||
if hasattr(self.vector_store, 'client') and hasattr(self.vector_store.client, 'close'):
|
||||
self.vector_store.client.close()
|
||||
|
||||
# Close the old connection if possible
|
||||
if hasattr(self.db, 'connection') and self.db.connection:
|
||||
self.db.connection.execute("DROP TABLE IF EXISTS history")
|
||||
self.db.connection.close()
|
||||
|
||||
self.db = SQLiteManager(self.config.history_db_path)
|
||||
|
||||
# Create a new vector store with the same configuration
|
||||
self.vector_store = VectorStoreFactory.create(
|
||||
self.config.vector_store.provider, self.config.vector_store.config
|
||||
)
|
||||
self.db.reset()
|
||||
capture_event("mem0.reset", self)
|
||||
capture_event("mem0.reset", self, {"sync_type": "sync"})
|
||||
|
||||
def chat(self, query):
|
||||
raise NotImplementedError("Chat function not implemented yet.")
|
||||
@@ -783,7 +812,7 @@ class AsyncMemory(MemoryBase):
|
||||
self.graph = MemoryGraph(self.config)
|
||||
self.enable_graph = True
|
||||
|
||||
capture_event("async_mem0.init", self)
|
||||
capture_event("mem0.init", self, {"sync_type": "async"})
|
||||
|
||||
@classmethod
|
||||
async def from_config(cls, config_dict: Dict[str, Any]):
|
||||
@@ -1063,7 +1092,7 @@ class AsyncMemory(MemoryBase):
|
||||
except Exception as e:
|
||||
logging.error(f"Error in new_memories_with_actions: {e}")
|
||||
|
||||
capture_event("async_mem0.add", self, {"version": self.api_version, "keys": list(filters.keys())})
|
||||
capture_event("mem0.add", self, {"version": self.api_version, "keys": list(filters.keys()), "sync_type": "async"})
|
||||
|
||||
return returned_memories
|
||||
|
||||
@@ -1088,7 +1117,7 @@ class AsyncMemory(MemoryBase):
|
||||
Returns:
|
||||
dict: Retrieved memory.
|
||||
"""
|
||||
capture_event("async_mem0.get", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.get", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
memory = await asyncio.to_thread(self.vector_store.get, vector_id=memory_id)
|
||||
if not memory:
|
||||
return None
|
||||
@@ -1129,7 +1158,7 @@ class AsyncMemory(MemoryBase):
|
||||
if run_id:
|
||||
filters["run_id"] = run_id
|
||||
|
||||
capture_event("async_mem0.get_all", self, {"limit": limit, "keys": list(filters.keys())})
|
||||
capture_event("mem0.get_all", self, {"limit": limit, "keys": list(filters.keys()), "sync_type": "async"})
|
||||
|
||||
# Run vector store and graph operations concurrently
|
||||
vector_store_task = asyncio.create_task(self._get_all_from_vector_store(filters, limit))
|
||||
@@ -1216,9 +1245,9 @@ class AsyncMemory(MemoryBase):
|
||||
raise ValueError("One of the filters: user_id, agent_id or run_id is required!")
|
||||
|
||||
capture_event(
|
||||
"async_mem0.search",
|
||||
"mem0.search",
|
||||
self,
|
||||
{"limit": limit, "version": self.api_version, "keys": list(filters.keys())},
|
||||
{"limit": limit, "version": self.api_version, "keys": list(filters.keys()), "sync_type": "async"},
|
||||
)
|
||||
|
||||
# Run vector store and graph operations concurrently
|
||||
@@ -1296,7 +1325,7 @@ class AsyncMemory(MemoryBase):
|
||||
Returns:
|
||||
dict: Updated memory.
|
||||
"""
|
||||
capture_event("async_mem0.update", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.update", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
|
||||
embeddings = await asyncio.to_thread(self.embedding_model.embed, data, "update")
|
||||
existing_embeddings = {data: embeddings}
|
||||
@@ -1311,7 +1340,7 @@ class AsyncMemory(MemoryBase):
|
||||
Args:
|
||||
memory_id (str): ID of the memory to delete.
|
||||
"""
|
||||
capture_event("async_mem0.delete", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.delete", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
await self._delete_memory(memory_id)
|
||||
return {"message": "Memory deleted successfully!"}
|
||||
|
||||
@@ -1337,7 +1366,7 @@ class AsyncMemory(MemoryBase):
|
||||
"At least one filter is required to delete all memories. If you want to delete all memories, use the `reset()` method."
|
||||
)
|
||||
|
||||
capture_event("async_mem0.delete_all", self, {"keys": list(filters.keys())})
|
||||
capture_event("mem0.delete_all", self, {"keys": list(filters.keys()), "sync_type": "async"})
|
||||
memories = await asyncio.to_thread(self.vector_store.list, filters=filters)
|
||||
|
||||
delete_tasks = []
|
||||
@@ -1363,7 +1392,7 @@ class AsyncMemory(MemoryBase):
|
||||
Returns:
|
||||
list: List of changes for the memory.
|
||||
"""
|
||||
capture_event("async_mem0.history", self, {"memory_id": memory_id})
|
||||
capture_event("mem0.history", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return await asyncio.to_thread(self.db.get_history, memory_id)
|
||||
|
||||
async def _create_memory(self, data, existing_embeddings, metadata=None):
|
||||
@@ -1388,7 +1417,7 @@ class AsyncMemory(MemoryBase):
|
||||
|
||||
await asyncio.to_thread(self.db.add_history, memory_id, None, data, "ADD", created_at=metadata["created_at"])
|
||||
|
||||
capture_event("async_mem0._create_memory", self, {"memory_id": memory_id})
|
||||
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):
|
||||
@@ -1438,7 +1467,7 @@ class AsyncMemory(MemoryBase):
|
||||
embeddings = await asyncio.to_thread(self.embedding_model.embed, procedural_memory, memory_action="add")
|
||||
# Create the memory
|
||||
memory_id = await self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=metadata)
|
||||
capture_event("async_mem0._create_procedural_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._create_procedural_memory", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
|
||||
# Return results in the same format as add()
|
||||
result = {"results": [{"id": memory_id, "memory": procedural_memory, "event": "ADD"}]}
|
||||
@@ -1492,7 +1521,7 @@ class AsyncMemory(MemoryBase):
|
||||
updated_at=new_metadata["updated_at"],
|
||||
)
|
||||
|
||||
capture_event("async_mem0._update_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._update_memory", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return memory_id
|
||||
|
||||
async def _delete_memory(self, memory_id):
|
||||
@@ -1503,20 +1532,34 @@ class AsyncMemory(MemoryBase):
|
||||
await asyncio.to_thread(self.vector_store.delete, vector_id=memory_id)
|
||||
await asyncio.to_thread(self.db.add_history, memory_id, prev_value, None, "DELETE", is_deleted=1)
|
||||
|
||||
capture_event("async_mem0._delete_memory", self, {"memory_id": memory_id})
|
||||
capture_event("mem0._delete_memory", self, {"memory_id": memory_id, "sync_type": "async"})
|
||||
return memory_id
|
||||
|
||||
async def reset(self):
|
||||
"""
|
||||
Reset the memory store asynchronously.
|
||||
Reset the memory store asynchronously by:
|
||||
Deletes the vector store collection
|
||||
Resets the database
|
||||
Recreates the vector store with a new client
|
||||
"""
|
||||
logger.warning("Resetting all memories")
|
||||
await asyncio.to_thread(self.vector_store.delete_col)
|
||||
|
||||
gc.collect()
|
||||
|
||||
if hasattr(self.vector_store, 'client') and hasattr(self.vector_store.client, 'close'):
|
||||
await asyncio.to_thread(self.vector_store.client.close)
|
||||
|
||||
if hasattr(self.db, 'connection') and self.db.connection:
|
||||
await asyncio.to_thread(lambda: self.db.connection.execute("DROP TABLE IF EXISTS history"))
|
||||
await asyncio.to_thread(self.db.connection.close)
|
||||
|
||||
self.db = SQLiteManager(self.config.history_db_path)
|
||||
|
||||
self.vector_store = VectorStoreFactory.create(
|
||||
self.config.vector_store.provider, self.config.vector_store.config
|
||||
)
|
||||
await asyncio.to_thread(self.db.reset)
|
||||
capture_event("async_mem0.reset", self)
|
||||
capture_event("mem0.reset", self, {"sync_type": "async"})
|
||||
|
||||
async def chat(self, query):
|
||||
raise NotImplementedError("Chat function not implemented yet.")
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
import uuid
|
||||
|
||||
# Set up the directory path
|
||||
VECTOR_ID = str(uuid.uuid4())
|
||||
home_dir = os.path.expanduser("~")
|
||||
mem0_dir = os.environ.get("MEM0_DIR") or os.path.join(home_dir, ".mem0")
|
||||
os.makedirs(mem0_dir, exist_ok=True)
|
||||
@@ -29,3 +30,27 @@ def get_user_id():
|
||||
return user_id
|
||||
except Exception:
|
||||
return "anonymous_user"
|
||||
|
||||
|
||||
def get_or_create_user_id(vector_store):
|
||||
"""Store user_id in vector store and return it."""
|
||||
user_id = get_user_id()
|
||||
|
||||
# Try to get existing user_id from vector store
|
||||
try:
|
||||
existing = vector_store.get(vector_id=VECTOR_ID)
|
||||
if existing and hasattr(existing, "payload") and existing.payload and "user_id" in existing.payload:
|
||||
return existing.payload["user_id"]
|
||||
except:
|
||||
pass
|
||||
|
||||
# If we get here, we need to insert the user_id
|
||||
try:
|
||||
dims = getattr(vector_store, "embedding_model_dims", 1)
|
||||
vector_store.insert(
|
||||
vectors=[[0.0] * dims], payloads=[{"user_id": user_id, "type": "user_identity"}], ids=[VECTOR_ID]
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
return user_id
|
||||
|
||||
@@ -142,9 +142,3 @@ class SQLiteManager:
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def reset(self):
|
||||
with self._lock:
|
||||
with self.connection:
|
||||
self.connection.execute("DROP TABLE IF EXISTS history")
|
||||
self._create_history_table()
|
||||
|
||||
+17
-13
@@ -6,9 +6,11 @@ import sys
|
||||
from posthog import Posthog
|
||||
|
||||
import mem0
|
||||
from mem0.memory.setup import get_user_id, setup_config
|
||||
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"
|
||||
|
||||
if isinstance(MEM0_TELEMETRY, str):
|
||||
MEM0_TELEMETRY = MEM0_TELEMETRY.lower() in ("true", "1", "yes")
|
||||
@@ -21,11 +23,11 @@ logging.getLogger("urllib3").setLevel(logging.CRITICAL + 1)
|
||||
|
||||
|
||||
class AnonymousTelemetry:
|
||||
def __init__(self, project_api_key, host):
|
||||
self.posthog = Posthog(project_api_key=project_api_key, host=host)
|
||||
# Call setup config to ensure that the user_id is generated
|
||||
setup_config()
|
||||
self.user_id = get_user_id()
|
||||
def __init__(self, vector_store=None):
|
||||
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
|
||||
|
||||
self.user_id = get_or_create_user_id(vector_store)
|
||||
|
||||
if not MEM0_TELEMETRY:
|
||||
self.posthog.disabled = True
|
||||
|
||||
@@ -50,14 +52,16 @@ class AnonymousTelemetry:
|
||||
self.posthog.shutdown()
|
||||
|
||||
|
||||
# Initialize AnonymousTelemetry
|
||||
telemetry = AnonymousTelemetry(
|
||||
project_api_key="phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX",
|
||||
host="https://us.i.posthog.com",
|
||||
)
|
||||
client_telemetry = AnonymousTelemetry()
|
||||
|
||||
|
||||
def capture_event(event_name, memory_instance, additional_data=None):
|
||||
oss_telemetry = AnonymousTelemetry(
|
||||
vector_store=memory_instance._telemetry_vector_store
|
||||
if hasattr(memory_instance, "_telemetry_vector_store")
|
||||
else None,
|
||||
)
|
||||
|
||||
event_data = {
|
||||
"collection": memory_instance.collection_name,
|
||||
"vector_size": memory_instance.embedding_model.config.embedding_dims,
|
||||
@@ -73,7 +77,7 @@ def capture_event(event_name, memory_instance, additional_data=None):
|
||||
if additional_data:
|
||||
event_data.update(additional_data)
|
||||
|
||||
telemetry.capture_event(event_name, event_data)
|
||||
oss_telemetry.capture_event(event_name, event_data)
|
||||
|
||||
|
||||
def capture_client_event(event_name, instance, additional_data=None):
|
||||
@@ -83,4 +87,4 @@ def capture_client_event(event_name, instance, additional_data=None):
|
||||
if additional_data:
|
||||
event_data.update(additional_data)
|
||||
|
||||
telemetry.capture_event(event_name, event_data, instance.user_email)
|
||||
client_telemetry.capture_event(event_name, event_data, instance.user_email)
|
||||
|
||||
@@ -5,7 +5,9 @@ from pydantic import BaseModel
|
||||
try:
|
||||
from langchain_community.vectorstores import VectorStore
|
||||
except ImportError:
|
||||
raise ImportError("The 'langchain_community' library is required. Please install it using 'pip install langchain_community'.")
|
||||
raise ImportError(
|
||||
"The 'langchain_community' library is required. Please install it using 'pip install langchain_community'."
|
||||
)
|
||||
|
||||
from mem0.vector_stores.base import VectorStoreBase
|
||||
|
||||
@@ -15,11 +17,12 @@ class OutputData(BaseModel):
|
||||
score: Optional[float] # distance
|
||||
payload: Optional[Dict] # metadata
|
||||
|
||||
|
||||
class Langchain(VectorStoreBase):
|
||||
def __init__(self, client: VectorStore, collection_name: str = "mem0"):
|
||||
self.client = client
|
||||
self.collection_name = collection_name
|
||||
|
||||
|
||||
def _parse_output(self, data: Dict) -> List[OutputData]:
|
||||
"""
|
||||
Parse the output data.
|
||||
@@ -31,17 +34,17 @@ class Langchain(VectorStoreBase):
|
||||
List[OutputData]: Parsed output data.
|
||||
"""
|
||||
# Check if input is a list of Document objects
|
||||
if isinstance(data, list) and all(hasattr(doc, 'metadata') for doc in data if hasattr(doc, '__dict__')):
|
||||
if isinstance(data, list) and all(hasattr(doc, "metadata") for doc in data if hasattr(doc, "__dict__")):
|
||||
result = []
|
||||
for doc in data:
|
||||
entry = OutputData(
|
||||
id=getattr(doc, "id", None),
|
||||
score=None, # Document objects typically don't include scores
|
||||
payload=getattr(doc, "metadata", {})
|
||||
payload=getattr(doc, "metadata", {}),
|
||||
)
|
||||
result.append(entry)
|
||||
return result
|
||||
|
||||
|
||||
# Original format handling
|
||||
keys = ["ids", "distances", "metadatas"]
|
||||
values = []
|
||||
@@ -70,26 +73,20 @@ class Langchain(VectorStoreBase):
|
||||
self.collection_name = name
|
||||
return self.client
|
||||
|
||||
def insert(self, vectors: List[List[float]], payloads: Optional[List[Dict]] = None, ids: Optional[List[str]] = None):
|
||||
def insert(
|
||||
self, vectors: List[List[float]], payloads: Optional[List[Dict]] = None, ids: Optional[List[str]] = None
|
||||
):
|
||||
"""
|
||||
Insert vectors into the LangChain vectorstore.
|
||||
"""
|
||||
# Check if client has add_embeddings method
|
||||
if hasattr(self.client, "add_embeddings"):
|
||||
# Some LangChain vectorstores have a direct add_embeddings method
|
||||
self.client.add_embeddings(
|
||||
embeddings=vectors,
|
||||
metadatas=payloads,
|
||||
ids=ids
|
||||
)
|
||||
self.client.add_embeddings(embeddings=vectors, metadatas=payloads, ids=ids)
|
||||
else:
|
||||
# Fallback to add_texts method
|
||||
texts = [payload.get("data", "") for payload in payloads] if payloads else [""] * len(vectors)
|
||||
self.client.add_texts(
|
||||
texts=texts,
|
||||
metadatas=payloads,
|
||||
ids=ids
|
||||
)
|
||||
self.client.add_texts(texts=texts, metadatas=payloads, ids=ids)
|
||||
|
||||
def search(self, query: str, vectors: List[List[float]], limit: int = 5, filters: Optional[Dict] = None):
|
||||
"""
|
||||
@@ -97,16 +94,9 @@ class Langchain(VectorStoreBase):
|
||||
"""
|
||||
# For each vector, perform a similarity search
|
||||
if filters:
|
||||
results = self.client.similarity_search_by_vector(
|
||||
embedding=vectors,
|
||||
k=limit,
|
||||
filter=filters
|
||||
)
|
||||
results = self.client.similarity_search_by_vector(embedding=vectors, k=limit, filter=filters)
|
||||
else:
|
||||
results = self.client.similarity_search_by_vector(
|
||||
embedding=vectors,
|
||||
k=limit
|
||||
)
|
||||
results = self.client.similarity_search_by_vector(embedding=vectors, k=limit)
|
||||
|
||||
final_results = self._parse_output(results)
|
||||
return final_results
|
||||
@@ -133,26 +123,26 @@ class Langchain(VectorStoreBase):
|
||||
doc = docs[0]
|
||||
return self._parse_output([doc])[0]
|
||||
return None
|
||||
|
||||
|
||||
def list_cols(self):
|
||||
"""
|
||||
List all collections.
|
||||
"""
|
||||
# LangChain doesn't have collections
|
||||
return [self.collection_name]
|
||||
|
||||
|
||||
def delete_col(self):
|
||||
"""
|
||||
Delete a collection.
|
||||
"""
|
||||
self.client.delete(ids=None)
|
||||
|
||||
|
||||
def col_info(self):
|
||||
"""
|
||||
Get information about a collection.
|
||||
"""
|
||||
return {"name": self.collection_name}
|
||||
|
||||
|
||||
def list(self, filters=None, limit=None):
|
||||
"""
|
||||
List all vectors in a collection.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "mem0ai"
|
||||
version = "0.1.91"
|
||||
version = "0.1.92"
|
||||
description = "Long-term memory for AI Agents"
|
||||
authors = ["Mem0 <founders@mem0.ai>"]
|
||||
exclude = [
|
||||
|
||||
@@ -195,21 +195,6 @@ def test_delete_all(memory_instance, version, enable_graph):
|
||||
assert result["message"] == "Memories deleted successfully!"
|
||||
|
||||
|
||||
def test_reset(memory_instance):
|
||||
memory_instance.vector_store.delete_col = Mock()
|
||||
# persisting vector store to make sure previous collection is deleted
|
||||
initial_vector_store = memory_instance.vector_store
|
||||
memory_instance.db.reset = Mock()
|
||||
|
||||
with patch.object(VectorStoreFactory, "create", return_value=Mock()) as mock_create:
|
||||
memory_instance.reset()
|
||||
|
||||
initial_vector_store.delete_col.assert_called_once()
|
||||
memory_instance.db.reset.assert_called_once()
|
||||
mock_create.assert_called_once_with(
|
||||
memory_instance.config.vector_store.provider, memory_instance.config.vector_store.config
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"version, enable_graph, expected_result",
|
||||
|
||||
Reference in New Issue
Block a user