Compare commits

..
Author SHA1 Message Date
kartik-mem0 68e2bca647 refactor: use extract_json for OllamaLLM argument parsing 2026-03-18 19:00:36 +05:30
Small c0069f0a59 fix(ollama): pass tools to client.chat and parse tool_calls from response
OllamaLLM.generate_response() never forwarded the tools parameter to
ollama.Client.chat(), and _parse_response() hard-coded tool_calls to an
empty list. This caused graph memory entity extraction to silently return
zero results when using Ollama as the LLM provider.

- Forward tools to client.chat() when provided
- Parse tool_calls from response (supports both dict and object formats)
- Handle string arguments via json.loads

Fixes #4175

Signed-off-by: sxu75374 <imshuaixu@gmail.com>
Signed-off-by: Small <imshuaixu@gmail.com>
2026-03-01 06:55:20 -08:00
Saket AryanandGitHub 93c720301e docs: update delete_all to reflect filter validation breaking change (#4103) 2026-02-25 21:10:35 +05:30
mgoulartandGitHub db15d5c629 fix(oss): validate LLM fact output via FactRetrievalSchema before embedding (#4083) 2026-02-22 19:57:48 -08:00
mem0-bot[bot]andGitHub aa4a944b51 fix: Bug: Openclaw Extension OSS Mode lacks threshold restrictions (#4106) (#4115) 2026-02-22 18:44:11 -08:00
PrathameshandGitHub ab5e930cf7 Update OpenClaw integration architecture diagram (#4079) 2026-02-19 16:42:43 -08:00
Deshraj YadavandGitHub 0e76de7bd5 Add source openclaw (#4082) 2026-02-19 16:35:41 -08:00
Mragank ShekharandGitHub 5a93643f12 docs: add memory_categorize webhook event type (#4077) 2026-02-19 15:09:11 +05:30
13 changed files with 288 additions and 37 deletions
@@ -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

+21 -6
View File
@@ -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
View File
@@ -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
}
},
+4
View File
@@ -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.
+16 -3
View File
@@ -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.
+4 -1
View File
@@ -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;
}
+3 -1
View File
@@ -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:",
+10 -1
View File
@@ -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."),
});
+27 -1
View File
@@ -1,3 +1,4 @@
import json
from typing import Dict, List, Optional, Union
try:
@@ -8,6 +9,7 @@ except ImportError:
from mem0.configs.llms.base import BaseLlmConfig
from mem0.configs.llms.ollama import OllamaConfig
from mem0.llms.base import LLMBase
from mem0.memory.utils import extract_json
class OllamaLLM(LLMBase):
@@ -61,7 +63,28 @@ class OllamaLLM(LLMBase):
"tool_calls": [],
}
# Ollama doesn't support tool calls in the same way, so we return the content
if isinstance(response, dict):
raw_calls = response.get("message", {}).get("tool_calls") or []
else:
raw_calls = getattr(response.message, "tool_calls", None) or []
for tool_call in raw_calls:
if isinstance(tool_call, dict):
fn = tool_call.get("function", {})
name = fn.get("name", "")
arguments = fn.get("arguments", {})
else:
fn = getattr(tool_call, "function", None)
name = getattr(fn, "name", "") if fn else ""
arguments = getattr(fn, "arguments", {}) if fn else {}
if isinstance(arguments, str):
arguments = json.loads(extract_json(arguments))
processed_response["tool_calls"].append(
{"name": name, "arguments": arguments}
)
return processed_response
else:
return content
@@ -113,5 +136,8 @@ class OllamaLLM(LLMBase):
# Remove OpenAI-specific parameters that Ollama doesn't support
params.pop("max_tokens", None) # Ollama uses different parameter names
if tools:
params["tools"] = tools
response = self.client.chat(**params)
return self._parse_response(response, tools)
+25 -3
View File
@@ -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 -1
View File
@@ -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",
+108
View File
@@ -32,3 +32,111 @@ def test_generate_response_without_tools(mock_ollama_client):
model="llama3.1:70b", messages=messages, options={"temperature": 0.7, "num_predict": 100, "top_p": 1.0}
)
assert response == "I'm doing well, thank you for asking!"
def test_generate_response_with_tools_passes_tools_to_client(mock_ollama_client):
"""Tools should be forwarded to ollama client.chat()."""
config = OllamaConfig(model="llama3.1:70b", temperature=0.1, max_tokens=100, top_p=1.0)
llm = OllamaLLM(config)
messages = [{"role": "user", "content": "Extract entities from: Alice works at UCSD"}]
tools = [
{
"type": "function",
"function": {
"name": "extract_entities",
"description": "Extract entities",
"parameters": {"type": "object", "properties": {"entities": {"type": "array"}}},
},
}
]
mock_response = {
"message": {
"content": "",
"tool_calls": [
{
"function": {
"name": "extract_entities",
"arguments": {"entities": [{"name": "Alice"}, {"name": "UCSD"}]},
}
}
],
}
}
mock_ollama_client.chat.return_value = mock_response
response = llm.generate_response(messages, tools=tools)
# Verify tools were passed to client.chat
call_kwargs = mock_ollama_client.chat.call_args
assert "tools" in call_kwargs.kwargs or (len(call_kwargs.args) > 0 and "tools" in call_kwargs[1])
assert call_kwargs[1]["tools"] == tools
# Verify tool_calls were parsed correctly
assert response["tool_calls"] == [
{"name": "extract_entities", "arguments": {"entities": [{"name": "Alice"}, {"name": "UCSD"}]}}
]
def test_generate_response_with_tools_no_tool_calls_in_response(mock_ollama_client):
"""When model returns content without tool_calls, tool_calls should be empty list."""
config = OllamaConfig(model="llama3.1:70b", temperature=0.1, max_tokens=100, top_p=1.0)
llm = OllamaLLM(config)
messages = [{"role": "user", "content": "Hello"}]
tools = [{"type": "function", "function": {"name": "noop", "parameters": {}}}]
mock_response = {"message": {"content": "I cannot use tools for this.", "tool_calls": []}}
mock_ollama_client.chat.return_value = mock_response
response = llm.generate_response(messages, tools=tools)
assert response["content"] == "I cannot use tools for this."
assert response["tool_calls"] == []
def test_generate_response_with_tools_string_arguments(mock_ollama_client):
"""When tool_call arguments come as JSON string, they should be parsed."""
config = OllamaConfig(model="llama3.1:70b", temperature=0.1, max_tokens=100, top_p=1.0)
llm = OllamaLLM(config)
messages = [{"role": "user", "content": "test"}]
tools = [{"type": "function", "function": {"name": "test_fn", "parameters": {}}}]
mock_response = {
"message": {
"content": "",
"tool_calls": [
{"function": {"name": "test_fn", "arguments": '{"key": "value"}'}}
],
}
}
mock_ollama_client.chat.return_value = mock_response
response = llm.generate_response(messages, tools=tools)
assert response["tool_calls"] == [{"name": "test_fn", "arguments": {"key": "value"}}]
def test_parse_response_with_tools_object_style(mock_ollama_client):
"""Test _parse_response with object-style response (non-dict)."""
config = OllamaConfig(model="llama3.1:70b")
llm = OllamaLLM(config)
# Simulate object-style response
mock_fn = Mock()
mock_fn.name = "extract"
mock_fn.arguments = {"entities": ["Alice"]}
mock_tool_call = Mock()
mock_tool_call.function = mock_fn
mock_message = Mock()
mock_message.content = ""
mock_message.tool_calls = [mock_tool_call]
mock_response = Mock()
mock_response.message = mock_message
tools = [{"type": "function", "function": {"name": "extract"}}]
result = llm._parse_response(mock_response, tools)
assert result["tool_calls"] == [{"name": "extract", "arguments": {"entities": ["Alice"]}}]