Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34c797d285 | ||
|
|
a0d8a02b94 | ||
|
|
93c720301e | ||
|
|
db15d5c629 | ||
|
|
aa4a944b51 | ||
|
|
ab5e930cf7 | ||
|
|
0e76de7bd5 | ||
|
|
5a93643f12 |
@@ -7,6 +7,11 @@ mode: "wide"
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
<Update label="2026-03-03" description="v1.0.5">
|
||||
- **Telemetry Fix**
|
||||
- Fixed an issue where the PostHog client was initialized even after telemetry was disabled. Although events were not captured, the client was unnecessarily initialized.
|
||||
</Update>
|
||||
|
||||
<Update label="2026-02-17" description="v1.0.4">
|
||||
|
||||
**New Features & Updates:**
|
||||
|
||||
@@ -107,6 +107,12 @@ client = MemoryClient(api_key="your-api-key")
|
||||
|
||||
# Delete all memories for a specific user
|
||||
client.delete_all(user_id="alice")
|
||||
|
||||
# Delete all memories for a specific agent
|
||||
client.delete_all(agent_id="support-bot")
|
||||
|
||||
# Delete all memories for a specific run
|
||||
client.delete_all(run_id="session-xyz")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -127,7 +133,48 @@ You can also filter by other parameters such as:
|
||||
- `metadata` (as JSON string)
|
||||
|
||||
<Warning>
|
||||
`delete_all` requires at least one filter (user, agent, run, or metadata). Calling it with no filters raises an error to prevent accidental data loss.
|
||||
**Breaking change:** `delete_all` previously wiped all project memories when called with no filters. It now **raises an error** if no filters are provided. Use `"*"` wildcards for intentional bulk deletion (see below).
|
||||
</Warning>
|
||||
|
||||
### Wildcard deletes
|
||||
|
||||
Setting a filter to `"*"` deletes **all memories** for that entity type across the entire project. This is an intentionally explicit opt-in to bulk deletion.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-api-key")
|
||||
|
||||
# Delete all memories across every user in the project
|
||||
client.delete_all(user_id="*")
|
||||
|
||||
# Delete all memories across every agent in the project
|
||||
client.delete_all(agent_id="*")
|
||||
|
||||
# Full project wipe — all four filters must be explicitly set to "*"
|
||||
client.delete_all(user_id="*", agent_id="*", app_id="*", run_id="*")
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
import MemoryClient from 'mem0ai';
|
||||
|
||||
const client = new MemoryClient({ apiKey: "your-api-key" });
|
||||
|
||||
// Delete all memories across every user in the project
|
||||
client.deleteAll({ user_id: "*" })
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
|
||||
// Full project wipe — all four filters must be explicitly set to "*"
|
||||
client.deleteAll({ user_id: "*", agent_id: "*", app_id: "*", run_id: "*" })
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
A full project wipe requires **all four** filters set to `"*"`. Setting only some to `"*"` deletes memories only for those entity types, not the entire project.
|
||||
</Warning>
|
||||
|
||||
## Delete with Mem0 OSS
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 178 KiB |
@@ -255,13 +255,28 @@ def delete(
|
||||
|
||||
### delete_all() Method
|
||||
|
||||
#### No Breaking Changes
|
||||
#### Breaking Change — Empty filter no longer silently deletes everything
|
||||
|
||||
**Before:** calling `delete_all()` with no filters silently deleted **all memories in the project**.
|
||||
|
||||
**After:**
|
||||
- No filters → raises a validation error (prevents accidental full-project wipe).
|
||||
- Concrete ID (e.g. `user_id="alice"`) → deletes memories for that entity (unchanged).
|
||||
- `"*"` for a filter → deletes all memories for that entity type across the project (new).
|
||||
- All four filters set to `"*"` → explicit full project wipe (new, requires opt-in on every parameter).
|
||||
|
||||
This change replaces the silent full-project delete (triggered by an empty or missing filter) with a validation error, and introduces `"*"` wildcards as the intentional path for bulk deletion.
|
||||
|
||||
```python
|
||||
# Same signature in both versions
|
||||
def delete_all(
|
||||
self,
|
||||
user_id: str
|
||||
) -> dict
|
||||
# v0.x — no filter silently wiped all project memories
|
||||
m.delete_all() # DANGER: deleted everything
|
||||
m.delete_all(user_id="alice") # deleted alice's memories
|
||||
|
||||
# v1.x — no filter now raises an error; use "*" for intentional bulk deletes
|
||||
m.delete_all() # ERROR: at least one filter required
|
||||
m.delete_all(user_id="alice") # unchanged
|
||||
m.delete_all(user_id="*") # NEW — delete all users' memories
|
||||
m.delete_all(user_id="*", agent_id="*", app_id="*", run_id="*") # NEW — full project wipe
|
||||
```
|
||||
|
||||
## Platform Client (MemoryClient) Changes
|
||||
|
||||
+21
-19
@@ -1180,7 +1180,7 @@
|
||||
"tags": [
|
||||
"memories"
|
||||
],
|
||||
"description": "Delete memories.",
|
||||
"description": "Delete memories by filter. At least one filter is required — previously omitting all filters silently deleted everything; now it returns a validation error.",
|
||||
"operationId": "memories_delete",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1189,7 +1189,7 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter memories by user ID."
|
||||
"description": "Filter by user ID. Pass `*` to delete memories for all users."
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
@@ -1197,7 +1197,7 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter memories by agent ID."
|
||||
"description": "Filter by agent ID. Pass `*` to delete memories for all agents."
|
||||
},
|
||||
{
|
||||
"name": "app_id",
|
||||
@@ -1205,7 +1205,7 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter memories by app ID."
|
||||
"description": "Filter by app ID. Pass `*` to delete memories for all apps."
|
||||
},
|
||||
{
|
||||
"name": "run_id",
|
||||
@@ -1213,7 +1213,7 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter memories by run ID."
|
||||
"description": "Filter by run ID. Pass `*` to delete memories for all runs."
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
@@ -1263,15 +1263,15 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Delete all memories for a specific user\nclient.delete_all(user_id=\"<user_id>\")"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Delete all memories for a specific user\nclient.delete_all(user_id=\"<user_id>\")\n\n# Delete all memories for every user in the project (wildcard)\nclient.delete_all(user_id=\"*\")\n\n# Full project wipe — all four filters must be explicitly set to \"*\"\nclient.delete_all(user_id=\"*\", agent_id=\"*\", app_id=\"*\", run_id=\"*\")\n\n# NOTE: Calling delete_all() with no filters raises a validation error.\n# At least one filter is required to prevent accidental data loss."
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Delete all memories for a specific user\nclient.deleteAll({ user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Delete all memories for a specific user\nclient.deleteAll({ user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Delete all memories for every user in the project (wildcard)\nclient.deleteAll({ user_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Full project wipe — all four filters must be explicitly set to \"*\"\nclient.deleteAll({ user_id: \"*\", agent_id: \"*\", app_id: \"*\", run_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request DELETE \\\n --url https://api.mem0.ai/v1/memories/ \\\n --header 'Authorization: Token <api-key>'"
|
||||
"source": "# Delete memories for a specific user\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=<user_id>' \\\n --header 'Authorization: Token <api-key>'\n\n# Delete memories for all users (wildcard)\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*' \\\n --header 'Authorization: Token <api-key>'\n\n# Full project wipe — all four filters must be set to *\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*&agent_id=*&app_id=*&run_id=*' \\\n --header 'Authorization: Token <api-key>'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
@@ -4539,7 +4539,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Get all webhooks\nwebhooks = client.get_webhooks(project_id=\"your_project_id\")\nprint(webhooks)\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\"]\n)\nprint(webhook)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Get all webhooks\nwebhooks = client.get_webhooks(project_id=\"your_project_id\")\nprint(webhooks)\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -4606,7 +4606,8 @@
|
||||
"enum": [
|
||||
"memory:add",
|
||||
"memory:update",
|
||||
"memory:delete"
|
||||
"memory:delete",
|
||||
"memory:categorize"
|
||||
]
|
||||
},
|
||||
"description": "List of event types to subscribe to."
|
||||
@@ -4701,7 +4702,7 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\"]\n)\nprint(webhook)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
@@ -4767,7 +4768,8 @@
|
||||
"enum": [
|
||||
"memory:add",
|
||||
"memory:update",
|
||||
"memory:delete"
|
||||
"memory:delete",
|
||||
"memory:categorize"
|
||||
]
|
||||
},
|
||||
"description": "New list of event types to subscribe to"
|
||||
@@ -4845,11 +4847,11 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Update a webhook\nwebhook = client.update_webhook(\n webhook_id=\"your_webhook_id\",\n name=\"Updated Webhook\",\n url=\"https://new-webhook-url.com\",\n event_types=[\"memory:add\"]\n)\nprint(webhook)\n\n# Delete a webhook\nresponse = client.delete_webhook(webhook_id=\"your_webhook_id\")\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Update a webhook\nwebhook = client.update_webhook(\n webhook_id=\"your_webhook_id\",\n name=\"Updated Webhook\",\n url=\"https://new-webhook-url.com\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)\n\n# Delete a webhook\nresponse = client.delete_webhook(webhook_id=\"your_webhook_id\")\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: 'your-api-key' });\n\n// Update a webhook\nclient.updateWebhook('your_webhook_id', {\n name: 'Updated Webhook',\n url: 'https://new-webhook-url.com',\n event_types: ['memory:add']\n})\n .then(webhook => console.log(webhook))\n .catch(err => console.error(err));\n\n// Delete a webhook\nclient.deleteWebhook('your_webhook_id')\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: 'your-api-key' });\n\n// Update a webhook\nclient.updateWebhook('your_webhook_id', {\n name: 'Updated Webhook',\n url: 'https://new-webhook-url.com',\n event_types: ['memory:add', 'memory:categorize']\n})\n .then(webhook => console.log(webhook))\n .catch(err => console.error(err));\n\n// Delete a webhook\nclient.deleteWebhook('your_webhook_id')\n .then(response => console.log(response))\n .catch(err => console.error(err));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
@@ -5568,26 +5570,26 @@
|
||||
},
|
||||
"DeleteMemoriesInput": {
|
||||
"type": "object",
|
||||
"description": "Input for deleting memories associated with a specific user, agent, app, or run.",
|
||||
"description": "Filters for bulk memory deletion. At least one field is required. Pass \"*\" for a field to delete all memories for that entity type. Set all four to \"*\" for a full project wipe.",
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier of the user whose memories should be deleted.",
|
||||
"description": "User ID to delete memories for. Pass \"*\" for all users.",
|
||||
"nullable": true
|
||||
},
|
||||
"agent_id": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier of the agent whose memories should be deleted.",
|
||||
"description": "Agent ID to delete memories for. Pass \"*\" for all agents.",
|
||||
"nullable": true
|
||||
},
|
||||
"app_id": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier of the application whose memories should be deleted.",
|
||||
"description": "App ID to delete memories for. Pass \"*\" for all apps.",
|
||||
"nullable": true
|
||||
},
|
||||
"run_id": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier of the run whose memories should be deleted.",
|
||||
"description": "Run ID to delete memories for. Pass \"*\" for all runs.",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -123,6 +123,10 @@ await client.deleteAll({ user_id: "alice" });
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
At least one filter (`user_id`, `agent_id`, `app_id`, or `run_id`) is required — calling `delete_all` with no filters raises an error to prevent accidental data loss. You can pass `"*"` as a value to delete all memories for a given entity type (e.g., `user_id="*"` removes memories for every user). A full project wipe requires all four filters set to `"*"`.
|
||||
</Note>
|
||||
|
||||
### History
|
||||
|
||||
Get the history of a specific memory asynchronously.
|
||||
|
||||
@@ -5,7 +5,7 @@ description: 'Configure and manage webhooks to receive real-time notifications a
|
||||
|
||||
## Overview
|
||||
|
||||
Webhooks enable real-time notifications for memory events in your Mem0 project. Webhooks are configured at the project level, meaning each webhook is tied to a specific project and receives events solely from that project. You can configure webhooks to send HTTP POST requests to your specified URLs whenever memories are created, updated, or deleted.
|
||||
Webhooks enable real-time notifications for memory events in your Mem0 project. Webhooks are configured at the project level, meaning each webhook is tied to a specific project and receives events solely from that project. You can configure webhooks to send HTTP POST requests to your specified URLs whenever memories are created, updated, deleted, or categorized.
|
||||
|
||||
## Managing Webhooks
|
||||
|
||||
@@ -27,7 +27,7 @@ webhook = client.create_webhook(
|
||||
url="https://your-app.com/webhook",
|
||||
name="Memory Logger",
|
||||
project_id="proj_123",
|
||||
event_types=["memory_add"]
|
||||
event_types=["memory_add", "memory_categorize"]
|
||||
)
|
||||
print(webhook)
|
||||
```
|
||||
@@ -41,7 +41,7 @@ const webhook = await client.createWebhook({
|
||||
url: "https://your-app.com/webhook",
|
||||
name: "Memory Logger",
|
||||
projectId: "proj_123",
|
||||
eventTypes: ["memory_add"]
|
||||
eventTypes: ["memory_add", "memory_categorize"]
|
||||
});
|
||||
console.log(webhook);
|
||||
```
|
||||
@@ -167,11 +167,13 @@ Mem0 supports the following event types for webhooks:
|
||||
- `memory_add`: Triggered when a memory is added.
|
||||
- `memory_update`: Triggered when an existing memory is updated.
|
||||
- `memory_delete`: Triggered when a memory is deleted.
|
||||
- `memory_categorize`: Triggered when a memory is categorized.
|
||||
|
||||
## Webhook Payload
|
||||
|
||||
When a memory event occurs, Mem0 sends an HTTP POST request to your webhook URL with the following payload:
|
||||
|
||||
**Memory add/update/delete payload:**
|
||||
```json
|
||||
{
|
||||
"event_details": {
|
||||
@@ -184,6 +186,17 @@ When a memory event occurs, Mem0 sends an HTTP POST request to your webhook URL
|
||||
}
|
||||
```
|
||||
|
||||
**Memory categorize payload:**
|
||||
```json
|
||||
{
|
||||
"event_details": {
|
||||
"event": "CATEGORIZE",
|
||||
"memory_id": "a1b2c3d4-e5f6-4g7h-8i9j-k0l1m2n3o4p5",
|
||||
"categories": ["hobbies", "travel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Implement Retry Logic**: Ensure your webhook endpoint can handle temporary failures.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "2.2.3",
|
||||
"version": "2.2.4",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -91,7 +91,7 @@
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"axios": "1.13.6",
|
||||
"openai": "^4.93.0",
|
||||
"uuid": "9.0.1",
|
||||
"zod": "^3.24.1"
|
||||
|
||||
Generated
+25
-10
@@ -44,8 +44,8 @@ importers:
|
||||
specifier: 3.1.11
|
||||
version: 3.1.11
|
||||
axios:
|
||||
specifier: 1.7.7
|
||||
version: 1.7.7
|
||||
specifier: 1.13.6
|
||||
version: 1.13.6
|
||||
cloudflare:
|
||||
specifier: ^4.2.0
|
||||
version: 4.3.0(encoding@0.1.13)
|
||||
@@ -1621,10 +1621,10 @@ packages:
|
||||
integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==,
|
||||
}
|
||||
|
||||
axios@1.7.7:
|
||||
axios@1.13.6:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==,
|
||||
integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==,
|
||||
}
|
||||
|
||||
babel-jest@29.7.0:
|
||||
@@ -2560,10 +2560,10 @@ packages:
|
||||
}
|
||||
hasBin: true
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
follow-redirects@1.15.11:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==,
|
||||
integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==,
|
||||
}
|
||||
engines: { node: ">=4.0" }
|
||||
peerDependencies:
|
||||
@@ -2592,6 +2592,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 6" }
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==,
|
||||
}
|
||||
engines: { node: ">= 6" }
|
||||
|
||||
formdata-node@4.4.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -6566,10 +6573,10 @@ snapshots:
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.7.7:
|
||||
axios@1.13.6:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.2
|
||||
follow-redirects: 1.15.11
|
||||
form-data: 4.0.5
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@@ -7170,7 +7177,7 @@ snapshots:
|
||||
kolorist: 1.8.0
|
||||
read-pkg: 8.1.0
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
follow-redirects@1.15.11: {}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
@@ -7186,6 +7193,14 @@ snapshots:
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
form-data@4.0.5:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
formdata-node@4.4.1:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
|
||||
@@ -27,9 +27,12 @@ export class OllamaEmbedder implements Embedder {
|
||||
} catch (err) {
|
||||
logger.error(`Error ensuring model exists: ${err}`);
|
||||
}
|
||||
// Ollama's Go server requires prompt to be a string. Coerce defensively
|
||||
// since callers may pass values parsed from untrusted LLM JSON output.
|
||||
const prompt = typeof text === "string" ? text : JSON.stringify(text);
|
||||
const response = await this.ollama.embeddings({
|
||||
model: this.model,
|
||||
prompt: text,
|
||||
prompt,
|
||||
});
|
||||
return response.embedding;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
HistoryManagerFactory,
|
||||
} from "../utils/factory";
|
||||
import {
|
||||
FactRetrievalSchema,
|
||||
getFactRetrievalMessages,
|
||||
getUpdateMemoryMessages,
|
||||
parseMessages,
|
||||
@@ -261,7 +262,8 @@ export class Memory {
|
||||
const cleanResponse = removeCodeBlocks(response as string);
|
||||
let facts: string[] = [];
|
||||
try {
|
||||
facts = JSON.parse(cleanResponse).facts || [];
|
||||
const parsed = FactRetrievalSchema.parse(JSON.parse(cleanResponse));
|
||||
facts = parsed.facts;
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Failed to parse facts from LLM response:",
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Accepts a string directly, or an object with a "fact" or "text" key
|
||||
// (common malformed shapes from smaller LLMs like llama3.1:8b).
|
||||
const factItem = z.union([
|
||||
z.string(),
|
||||
z.object({ fact: z.string() }).transform((o) => o.fact),
|
||||
z.object({ text: z.string() }).transform((o) => o.text),
|
||||
]);
|
||||
|
||||
// Define Zod schema for fact retrieval output
|
||||
export const FactRetrievalSchema = z.object({
|
||||
facts: z
|
||||
.array(z.string())
|
||||
.array(factItem)
|
||||
.transform((arr) => arr.filter((s) => s.length > 0))
|
||||
.describe("An array of distinct facts extracted from the conversation."),
|
||||
});
|
||||
|
||||
|
||||
@@ -24,14 +24,18 @@ logging.getLogger("urllib3").setLevel(logging.CRITICAL + 1)
|
||||
|
||||
class AnonymousTelemetry:
|
||||
def __init__(self, vector_store=None):
|
||||
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
|
||||
if not MEM0_TELEMETRY:
|
||||
self.posthog = None
|
||||
self.user_id = None
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
def capture_event(self, event_name, properties=None, user_email=None):
|
||||
if self.posthog is None:
|
||||
return
|
||||
|
||||
if properties is None:
|
||||
properties = {}
|
||||
properties = {
|
||||
@@ -49,13 +53,17 @@ class AnonymousTelemetry:
|
||||
self.posthog.capture(distinct_id=distinct_id, event=event_name, properties=properties)
|
||||
|
||||
def close(self):
|
||||
self.posthog.shutdown()
|
||||
if self.posthog is not None:
|
||||
self.posthog.shutdown()
|
||||
|
||||
|
||||
client_telemetry = AnonymousTelemetry()
|
||||
|
||||
|
||||
def capture_event(event_name, memory_instance, additional_data=None):
|
||||
if not MEM0_TELEMETRY:
|
||||
return
|
||||
|
||||
oss_telemetry = AnonymousTelemetry(
|
||||
vector_store=memory_instance._telemetry_vector_store
|
||||
if hasattr(memory_instance, "_telemetry_vector_store")
|
||||
@@ -81,6 +89,9 @@ def capture_event(event_name, memory_instance, additional_data=None):
|
||||
|
||||
|
||||
def capture_client_event(event_name, instance, additional_data=None):
|
||||
if not MEM0_TELEMETRY:
|
||||
return
|
||||
|
||||
event_data = {
|
||||
"function": f"{instance.__class__.__module__}.{instance.__class__.__name__}",
|
||||
}
|
||||
|
||||
+25
-3
@@ -56,6 +56,7 @@ interface AddOptions {
|
||||
custom_categories?: Array<Record<string, string>>;
|
||||
enable_graph?: boolean;
|
||||
output_format?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface SearchOptions {
|
||||
@@ -66,12 +67,14 @@ interface SearchOptions {
|
||||
limit?: number;
|
||||
keyword_search?: boolean;
|
||||
reranking?: boolean;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface ListOptions {
|
||||
user_id: string;
|
||||
run_id?: string;
|
||||
page_size?: number;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface MemoryItem {
|
||||
@@ -152,6 +155,7 @@ class PlatformProvider implements Mem0Provider {
|
||||
opts.custom_categories = options.custom_categories;
|
||||
if (options.enable_graph) opts.enable_graph = options.enable_graph;
|
||||
if (options.output_format) opts.output_format = options.output_format;
|
||||
if (options.source) opts.source = options.source;
|
||||
|
||||
const result = await this.client.add(messages, opts);
|
||||
return normalizeAddResult(result);
|
||||
@@ -165,6 +169,7 @@ class PlatformProvider implements Mem0Provider {
|
||||
if (options.threshold != null) opts.threshold = options.threshold;
|
||||
if (options.keyword_search != null) opts.keyword_search = options.keyword_search;
|
||||
if (options.reranking != null) opts.reranking = options.reranking;
|
||||
if (options.source) opts.source = options.source;
|
||||
|
||||
const results = await this.client.search(query, opts);
|
||||
return normalizeSearchResults(results);
|
||||
@@ -181,6 +186,7 @@ class PlatformProvider implements Mem0Provider {
|
||||
const opts: Record<string, unknown> = { user_id: options.user_id };
|
||||
if (options.run_id) opts.run_id = options.run_id;
|
||||
if (options.page_size != null) opts.page_size = options.page_size;
|
||||
if (options.source) opts.source = options.source;
|
||||
|
||||
const results = await this.client.getAll(opts);
|
||||
if (Array.isArray(results)) return results.map(normalizeMemoryItem);
|
||||
@@ -247,6 +253,7 @@ class OSSProvider implements Mem0Provider {
|
||||
// OSS SDK uses camelCase: userId/runId, not user_id/run_id
|
||||
const addOpts: Record<string, unknown> = { userId: options.user_id };
|
||||
if (options.run_id) addOpts.runId = options.run_id;
|
||||
if (options.source) addOpts.source = options.source;
|
||||
const result = await this.memory.add(messages, addOpts);
|
||||
return normalizeAddResult(result);
|
||||
}
|
||||
@@ -260,9 +267,18 @@ class OSSProvider implements Mem0Provider {
|
||||
else if (options.top_k != null) opts.limit = options.top_k;
|
||||
if (options.keyword_search != null) opts.keyword_search = options.keyword_search;
|
||||
if (options.reranking != null) opts.reranking = options.reranking;
|
||||
if (options.source) opts.source = options.source;
|
||||
if (options.threshold != null) opts.threshold = options.threshold;
|
||||
|
||||
const results = await this.memory.search(query, opts);
|
||||
return normalizeSearchResults(results);
|
||||
const normalized = normalizeSearchResults(results);
|
||||
|
||||
// Filter results by threshold if specified (client-side filtering as fallback)
|
||||
if (options.threshold != null) {
|
||||
return normalized.filter(item => (item.score ?? 0) >= options.threshold!);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
async get(memoryId: string): Promise<MemoryItem> {
|
||||
@@ -276,6 +292,7 @@ class OSSProvider implements Mem0Provider {
|
||||
// OSS SDK uses camelCase: userId/runId, not user_id/run_id
|
||||
const getAllOpts: Record<string, unknown> = { userId: options.user_id };
|
||||
if (options.run_id) getAllOpts.runId = options.run_id;
|
||||
if (options.source) getAllOpts.source = options.source;
|
||||
const results = await this.memory.getAll(getAllOpts);
|
||||
if (Array.isArray(results)) return results.map(normalizeMemoryItem);
|
||||
if (results?.results && Array.isArray(results.results))
|
||||
@@ -615,6 +632,7 @@ const memoryPlugin = {
|
||||
function buildAddOptions(userIdOverride?: string, runId?: string): AddOptions {
|
||||
const opts: AddOptions = {
|
||||
user_id: userIdOverride || cfg.userId,
|
||||
source: "OPENCLAW",
|
||||
};
|
||||
if (runId) opts.run_id = runId;
|
||||
if (cfg.mode === "platform") {
|
||||
@@ -639,6 +657,7 @@ const memoryPlugin = {
|
||||
threshold: cfg.searchThreshold,
|
||||
keyword_search: true,
|
||||
reranking: true,
|
||||
source: "OPENCLAW",
|
||||
};
|
||||
if (runId) opts.run_id = runId;
|
||||
return opts;
|
||||
@@ -931,18 +950,20 @@ const memoryPlugin = {
|
||||
memories = await provider.getAll({
|
||||
user_id: uid,
|
||||
run_id: currentSessionId,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
}
|
||||
} else if (scope === "long-term") {
|
||||
memories = await provider.getAll({ user_id: uid });
|
||||
memories = await provider.getAll({ user_id: uid, source: "OPENCLAW" });
|
||||
} else {
|
||||
// "all" — combine both scopes
|
||||
const longTerm = await provider.getAll({ user_id: uid });
|
||||
const longTerm = await provider.getAll({ user_id: uid, source: "OPENCLAW" });
|
||||
let session: MemoryItem[] = [];
|
||||
if (currentSessionId) {
|
||||
session = await provider.getAll({
|
||||
user_id: uid,
|
||||
run_id: currentSessionId,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
}
|
||||
const seen = new Set(longTerm.map((r) => r.id));
|
||||
@@ -1195,6 +1216,7 @@ const memoryPlugin = {
|
||||
try {
|
||||
const memories = await provider.getAll({
|
||||
user_id: cfg.userId,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
console.log(`Mode: ${cfg.mode}`);
|
||||
console.log(`User: ${cfg.userId}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/openclaw-mem0",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0ai"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
description = "Long-term memory for AI Agents"
|
||||
authors = [
|
||||
{ name = "Mem0", email = "founders@mem0.ai" }
|
||||
|
||||
+104
-25
@@ -1,35 +1,114 @@
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
import threading
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
MEM0_TELEMETRY = os.environ.get("MEM0_TELEMETRY", "True")
|
||||
|
||||
if isinstance(MEM0_TELEMETRY, str):
|
||||
MEM0_TELEMETRY = MEM0_TELEMETRY.lower() in ("true", "1", "yes")
|
||||
import mem0.memory.telemetry as telemetry_module
|
||||
|
||||
|
||||
def use_telemetry():
|
||||
if os.getenv("MEM0_TELEMETRY", "true").lower() == "true":
|
||||
return True
|
||||
return False
|
||||
class TestTelemetryDisabled:
|
||||
"""Verify PostHog is never instantiated when telemetry is disabled."""
|
||||
|
||||
def test_posthog_not_created_when_disabled(self):
|
||||
"""Posthog() constructor should never be called when MEM0_TELEMETRY=False."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", False):
|
||||
with patch("mem0.memory.telemetry.Posthog") as mock_posthog:
|
||||
at = telemetry_module.AnonymousTelemetry()
|
||||
mock_posthog.assert_not_called()
|
||||
assert at.posthog is None
|
||||
assert at.user_id is None
|
||||
|
||||
def test_capture_event_noop_when_disabled(self):
|
||||
"""capture_event() should return immediately without creating AnonymousTelemetry."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", False):
|
||||
with patch("mem0.memory.telemetry.AnonymousTelemetry") as mock_cls:
|
||||
telemetry_module.capture_event("test.event", MagicMock())
|
||||
mock_cls.assert_not_called()
|
||||
|
||||
def test_capture_client_event_noop_when_disabled(self):
|
||||
"""capture_client_event() should return immediately without calling posthog."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", False):
|
||||
mock_instance = MagicMock()
|
||||
mock_client_telemetry = MagicMock()
|
||||
with patch.object(telemetry_module, "client_telemetry", mock_client_telemetry):
|
||||
telemetry_module.capture_client_event("test.event", mock_instance)
|
||||
mock_client_telemetry.capture_event.assert_not_called()
|
||||
|
||||
def test_instance_capture_event_noop_when_posthog_is_none(self):
|
||||
"""AnonymousTelemetry.capture_event() should be a no-op when posthog is None."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", False):
|
||||
at = telemetry_module.AnonymousTelemetry()
|
||||
at.capture_event("test.event", {"key": "value"}) # should not raise
|
||||
|
||||
def test_close_noop_when_posthog_is_none(self):
|
||||
"""close() should not raise when posthog is None."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", False):
|
||||
at = telemetry_module.AnonymousTelemetry()
|
||||
at.close() # should not raise
|
||||
|
||||
def test_no_threads_spawned_when_disabled(self):
|
||||
"""No consumer threads should be created when telemetry is disabled."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", False):
|
||||
threads_before = threading.active_count()
|
||||
telemetry_module.AnonymousTelemetry()
|
||||
threads_after = threading.active_count()
|
||||
assert threads_after == threads_before
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_env():
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
yield
|
||||
class TestTelemetryEnabled:
|
||||
"""Verify PostHog works normally when telemetry is enabled."""
|
||||
|
||||
def test_posthog_created_when_enabled(self):
|
||||
"""Posthog() should be instantiated when MEM0_TELEMETRY=True."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", True):
|
||||
with patch("mem0.memory.telemetry.Posthog") as mock_posthog:
|
||||
with patch("mem0.memory.telemetry.get_or_create_user_id", return_value="test-user"):
|
||||
at = telemetry_module.AnonymousTelemetry()
|
||||
mock_posthog.assert_called_once()
|
||||
assert at.posthog is not None
|
||||
assert at.user_id == "test-user"
|
||||
|
||||
def test_capture_event_sends_when_enabled(self):
|
||||
"""capture_event() should create AnonymousTelemetry and call capture when enabled."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", True):
|
||||
with patch("mem0.memory.telemetry.AnonymousTelemetry") as mock_cls:
|
||||
mock_at = MagicMock()
|
||||
mock_cls.return_value = mock_at
|
||||
mock_memory = MagicMock()
|
||||
mock_memory.config.graph_store.config = None
|
||||
mock_memory.api_version = "v1"
|
||||
telemetry_module.capture_event("test.event", mock_memory)
|
||||
mock_at.capture_event.assert_called_once()
|
||||
|
||||
def test_capture_client_event_sends_when_enabled(self):
|
||||
"""capture_client_event() should call client_telemetry.capture_event when enabled."""
|
||||
with patch.object(telemetry_module, "MEM0_TELEMETRY", True):
|
||||
mock_client_telemetry = MagicMock()
|
||||
with patch.object(telemetry_module, "client_telemetry", mock_client_telemetry):
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.user_email = "test@example.com"
|
||||
telemetry_module.capture_client_event("test.event", mock_instance)
|
||||
mock_client_telemetry.capture_event.assert_called_once()
|
||||
|
||||
|
||||
def test_telemetry_enabled():
|
||||
with patch.dict(os.environ, {"MEM0_TELEMETRY": "true"}):
|
||||
assert use_telemetry() is True
|
||||
class TestTelemetryEnvVar:
|
||||
"""Verify the MEM0_TELEMETRY env var parsing logic."""
|
||||
|
||||
|
||||
def test_telemetry_disabled():
|
||||
with patch.dict(os.environ, {"MEM0_TELEMETRY": "false"}):
|
||||
assert use_telemetry() is False
|
||||
|
||||
|
||||
def test_telemetry_default_enabled():
|
||||
assert use_telemetry() is True
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
("true", True),
|
||||
("True", True),
|
||||
("TRUE", True),
|
||||
("1", True),
|
||||
("yes", True),
|
||||
("false", False),
|
||||
("False", False),
|
||||
("0", False),
|
||||
("no", False),
|
||||
("anything_else", False),
|
||||
],
|
||||
)
|
||||
def test_env_var_parsing(self, value, expected):
|
||||
result = value.lower() in ("true", "1", "yes")
|
||||
assert result == expected
|
||||
|
||||
Reference in New Issue
Block a user