Compare commits
78
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44dc74d78f | ||
|
|
d258b638ef | ||
|
|
bbbfcfea07 | ||
|
|
fbef369b91 | ||
|
|
a7ed68e697 | ||
|
|
8a92cf0306 | ||
|
|
0fbbb2f525 | ||
|
|
818c2981b7 | ||
|
|
1f66aadfa3 | ||
|
|
b91c745fbc | ||
|
|
af70668308 | ||
|
|
890473f891 | ||
|
|
d2ff83cf72 | ||
|
|
0e02effaf7 | ||
|
|
9269a0ad6e | ||
|
|
3d06006f36 | ||
|
|
6bb1d328ad | ||
|
|
ac296f7534 | ||
|
|
ac8f862ff7 | ||
|
|
b33fa5427c | ||
|
|
5d573dd2ae | ||
|
|
98dbf90864 | ||
|
|
6ddf1669f4 | ||
|
|
d3d2e89fd5 | ||
|
|
43175d85f2 | ||
|
|
661ecb9f0f | ||
|
|
09f181c577 | ||
|
|
3497f26a00 | ||
|
|
e9c0547423 | ||
|
|
25bc1b7426 | ||
|
|
fee344db85 | ||
|
|
b611f69381 | ||
|
|
c2862831db | ||
|
|
1678e682ee | ||
|
|
ced4af681f | ||
|
|
565db27121 | ||
|
|
c0ac9f81fa | ||
|
|
879c68555c | ||
|
|
c2e723352e | ||
|
|
716f021df8 | ||
|
|
7fa996261d | ||
|
|
87bd2d91e0 | ||
|
|
15a930dac2 | ||
|
|
fa9abc77a6 | ||
|
|
4e448269bc | ||
|
|
29d131f7aa | ||
|
|
42fe129330 | ||
|
|
bd5996f41e | ||
|
|
299c423213 | ||
|
|
ce0531a13e | ||
|
|
513b56159f | ||
|
|
1676b3168d | ||
|
|
8a786bf72d | ||
|
|
a48f34cf77 | ||
|
|
650b734b1b | ||
|
|
871a1de7d2 | ||
|
|
e615cc66de | ||
|
|
ca86a164bd | ||
|
|
2ac3f3956a | ||
|
|
7a9f03af3f | ||
|
|
48f1d6f010 | ||
|
|
f0ccd99924 | ||
|
|
c5971193a2 | ||
|
|
ff53fd60b7 | ||
|
|
ffa334537a | ||
|
|
bd7ce2c13c | ||
|
|
5d767219ff | ||
|
|
6b744845c3 | ||
|
|
5b4478458b | ||
|
|
1751e7bff9 | ||
|
|
7ae6a8c36a | ||
|
|
1dcee153b9 | ||
|
|
4065e846f6 | ||
|
|
466249113c | ||
|
|
3e2ae734e7 | ||
|
|
96b31c4bc0 | ||
|
|
0117d5838b | ||
|
|
42a3b4043c |
@@ -12,7 +12,7 @@
|
||||
"name": "mem0",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
||||
"version": "0.2.10"
|
||||
"version": "0.2.11"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"name": "mem0",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
||||
"version": "0.2.10"
|
||||
"version": "0.2.11"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,15 @@ All notable changes to `@mem0/cli` are documented here.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.2.9] — 2026-06-19
|
||||
|
||||
### Security
|
||||
|
||||
- Telemetry no longer passes the Mem0 API key to its child process via
|
||||
command-line arguments. The context is now sent over stdin, so the key is no
|
||||
longer visible in the process list (`ps`, `/proc/<pid>/cmdline`, Activity
|
||||
Monitor). Fixes #4862.
|
||||
|
||||
## [0.2.8] — 2026-06-01
|
||||
|
||||
### Security
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/cli",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"description": "The official CLI for mem0 — the memory layer for AI agents",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -145,11 +145,11 @@ export function captureEvent(
|
||||
anonDistinctIdToAlias: anonIdToAlias,
|
||||
};
|
||||
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[SENDER_SCRIPT, JSON.stringify(context)],
|
||||
{ detached: true, stdio: "ignore" },
|
||||
);
|
||||
const child = spawn(process.execPath, [SENDER_SCRIPT], {
|
||||
detached: true,
|
||||
stdio: ["pipe", "ignore", "ignore"],
|
||||
});
|
||||
child.stdin?.end(JSON.stringify(context));
|
||||
child.unref();
|
||||
} catch {
|
||||
/* silently swallow */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Standalone telemetry sender — runs as a detached child process.
|
||||
*
|
||||
* Usage: node telemetry-sender.cjs '<json context>'
|
||||
* Usage: node telemetry-sender.cjs (JSON context is read from stdin; a single
|
||||
* argv argument is still accepted as a legacy fallback)
|
||||
*
|
||||
* This script is spawned by telemetry.captureEvent() and runs independently
|
||||
* of the parent CLI process. It:
|
||||
@@ -19,6 +20,31 @@
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
|
||||
function loadContext() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.argv[2]) {
|
||||
try {
|
||||
resolve(JSON.parse(process.argv[2]));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let data = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => (data += chunk));
|
||||
process.stdin.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
process.stdin.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
function httpsRequest(url, method, headers, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const u = new URL(url);
|
||||
@@ -108,7 +134,7 @@ async function sendIdentifyEvent(ctx, payload, anonId) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const ctx = JSON.parse(process.argv[2]);
|
||||
const ctx = await loadContext();
|
||||
const payload = ctx.payload;
|
||||
|
||||
if (ctx.needsEmail && ctx.mem0ApiKey) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockLoadConfig = vi.fn();
|
||||
const mockSaveConfig = vi.fn();
|
||||
const mockSpawn = vi.fn();
|
||||
|
||||
vi.mock("../src/config.js", () => ({
|
||||
CONFIG_FILE: "/tmp/mem0-config.json",
|
||||
loadConfig: mockLoadConfig,
|
||||
saveConfig: mockSaveConfig,
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
spawn: mockSpawn,
|
||||
}));
|
||||
|
||||
describe("captureEvent", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mockLoadConfig.mockReset();
|
||||
mockSaveConfig.mockReset();
|
||||
mockSpawn.mockReset();
|
||||
delete process.env.MEM0_TELEMETRY;
|
||||
});
|
||||
|
||||
it("pipes the telemetry context through stdin instead of argv", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
platform: {
|
||||
apiKey: "m0-node-secret",
|
||||
baseUrl: "https://api.mem0.ai",
|
||||
userEmail: "",
|
||||
},
|
||||
telemetry: {
|
||||
anonymousId: "cli-anon-node",
|
||||
},
|
||||
});
|
||||
|
||||
const stdin = { end: vi.fn() };
|
||||
const child = { stdin, unref: vi.fn() };
|
||||
mockSpawn.mockReturnValue(child);
|
||||
|
||||
const { captureEvent } = await import("../src/telemetry.js");
|
||||
captureEvent("node_test_event", { case: "stdin-secret" });
|
||||
|
||||
expect(mockSpawn).toHaveBeenCalledTimes(1);
|
||||
const [execPath, args, options] = mockSpawn.mock.calls[0];
|
||||
expect(execPath).toBe(process.execPath);
|
||||
expect(args).toHaveLength(1);
|
||||
expect(String(args[0])).toContain("telemetry-sender.cjs");
|
||||
expect(JSON.stringify(args)).not.toContain("m0-node-secret");
|
||||
expect(options).toMatchObject({ detached: true, stdio: ["pipe", "ignore", "ignore"] });
|
||||
|
||||
expect(stdin.end).toHaveBeenCalledTimes(1);
|
||||
const payload = JSON.parse(stdin.end.mock.calls[0][0]);
|
||||
expect(payload.mem0ApiKey).toBe("m0-node-secret");
|
||||
expect(payload.payload.event).toBe("node_test_event");
|
||||
expect(child.unref).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,19 @@ All notable changes to `mem0-cli` (Python) are documented here.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.2.8] — 2026-06-19
|
||||
|
||||
### Security
|
||||
|
||||
- Telemetry no longer passes the Mem0 API key to its child process via
|
||||
command-line arguments. The context is now sent over stdin, so the key is no
|
||||
longer visible in the process list (`ps`, `/proc/<pid>/cmdline`, Activity
|
||||
Monitor). Fixes #4862.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `__version__` now matches the packaged version (was stale at 0.2.4).
|
||||
|
||||
## [0.2.7] — 2026-05-20
|
||||
|
||||
### Added
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0-cli"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
description = "The official CLI for mem0 — the memory layer for AI agents"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
|
||||
|
||||
__version__ = "0.2.4"
|
||||
__version__ = "0.2.8"
|
||||
|
||||
@@ -137,12 +137,19 @@ def capture_event(
|
||||
"anon_distinct_id_to_alias": anon_id_to_alias,
|
||||
}
|
||||
|
||||
subprocess.Popen(
|
||||
[sys.executable, "-m", "mem0_cli.telemetry_sender", json.dumps(context)],
|
||||
child = subprocess.Popen(
|
||||
[sys.executable, "-m", "mem0_cli.telemetry_sender"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
close_fds=True,
|
||||
text=True,
|
||||
)
|
||||
if child.stdin:
|
||||
with contextlib.suppress(Exception):
|
||||
child.stdin.write(json.dumps(context))
|
||||
with contextlib.suppress(Exception):
|
||||
child.stdin.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Standalone telemetry sender — runs as a detached subprocess.
|
||||
|
||||
Usage: python -m mem0_cli.telemetry_sender '<json context>'
|
||||
Usage: python -m mem0_cli.telemetry_sender (JSON context is read from stdin;
|
||||
a single argv argument is still accepted as a legacy fallback)
|
||||
|
||||
This module is spawned by telemetry.capture_event() and runs independently
|
||||
of the parent CLI process. It:
|
||||
@@ -20,8 +21,18 @@ import sys
|
||||
import urllib.request
|
||||
|
||||
|
||||
def _load_context() -> dict:
|
||||
"""Load telemetry context from stdin, falling back to argv for compatibility."""
|
||||
raw = ""
|
||||
if not sys.stdin.isatty():
|
||||
raw = sys.stdin.read().strip()
|
||||
if not raw and len(sys.argv) > 1:
|
||||
raw = sys.argv[1]
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ctx = json.loads(sys.argv[1])
|
||||
ctx = _load_context()
|
||||
payload = ctx["payload"]
|
||||
|
||||
if ctx.get("needs_email") and ctx.get("mem0_api_key"):
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Tests for telemetry subprocess secret handling."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from mem0_cli.config import Mem0Config, save_config
|
||||
from mem0_cli.telemetry import capture_event
|
||||
from mem0_cli.telemetry_sender import _load_context
|
||||
|
||||
|
||||
class _CaptureStdin:
|
||||
def __init__(self):
|
||||
self.buffer = ""
|
||||
self.closed = False
|
||||
|
||||
def write(self, value: str) -> None:
|
||||
self.buffer += value
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class _DummyProcess:
|
||||
def __init__(self):
|
||||
self.stdin = _CaptureStdin()
|
||||
|
||||
|
||||
def test_capture_event_writes_context_to_stdin_not_argv(isolate_config, monkeypatch):
|
||||
config = Mem0Config()
|
||||
config.platform.api_key = "m0-test-secret"
|
||||
config.telemetry.anonymous_id = "cli-anon-test"
|
||||
save_config(config)
|
||||
|
||||
captured: dict[str, object] = {}
|
||||
proc = _DummyProcess()
|
||||
|
||||
def fake_popen(args, **kwargs):
|
||||
captured["args"] = args
|
||||
captured["kwargs"] = kwargs
|
||||
return proc
|
||||
|
||||
monkeypatch.setattr("mem0_cli.telemetry.subprocess.Popen", fake_popen)
|
||||
|
||||
capture_event("unit_test_event", {"case": "stdin-secret"})
|
||||
|
||||
argv = captured["args"]
|
||||
assert argv == [sys.executable, "-m", "mem0_cli.telemetry_sender"]
|
||||
assert all("m0-test-secret" not in arg for arg in argv)
|
||||
|
||||
kwargs = captured["kwargs"]
|
||||
assert kwargs["stdin"] == subprocess.PIPE
|
||||
assert kwargs["text"] is True
|
||||
|
||||
ctx = json.loads(proc.stdin.buffer)
|
||||
assert ctx["mem0_api_key"] == "m0-test-secret"
|
||||
assert ctx["payload"]["event"] == "unit_test_event"
|
||||
|
||||
assert proc.stdin.closed
|
||||
|
||||
|
||||
def test_load_context_reads_from_stdin(monkeypatch):
|
||||
monkeypatch.setattr("sys.argv", ["telemetry_sender"])
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO('{"payload": {"event": "stdin"}}'))
|
||||
|
||||
ctx = _load_context()
|
||||
|
||||
assert ctx["payload"]["event"] == "stdin"
|
||||
|
||||
|
||||
def test_load_context_falls_back_to_argv(monkeypatch):
|
||||
monkeypatch.setattr("sys.argv", ["telemetry_sender", '{"payload": {"event": "argv"}}'])
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO(""))
|
||||
|
||||
ctx = _load_context()
|
||||
|
||||
assert ctx["payload"]["event"] == "argv"
|
||||
@@ -50,6 +50,7 @@ Provide conversation messages for Mem0 to extract memories from. At least one en
|
||||
| `app_id` | string | No* | Associates the memory with an app. |
|
||||
| `metadata` | object | Optional | Custom key/value metadata (e.g., `{"topic": "preferences"}`). |
|
||||
| `infer` | boolean (default `true`) | Optional | Set to `false` to skip inference and store the provided text as-is. |
|
||||
| `expiration_date` | string | Optional | Date in `YYYY-MM-DD` format. The memory is visible through this date and hidden by default after it passes. |
|
||||
|
||||
> \* At least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`) is required.
|
||||
|
||||
@@ -83,3 +84,11 @@ The request is queued for background processing. The response contains an `event
|
||||
<Info>
|
||||
Poll the event status via `GET /v1/event/{event_id}/`. Status will be `SUCCEEDED` or `FAILED` once processing completes.
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
Memories with `expiration_date` remain stored after they expire. Search and get-all hide them by default; pass `show_expired: true` to include them.
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
Python uses `expiration_date`; TypeScript uses `expirationDate`.
|
||||
</Info>
|
||||
|
||||
@@ -6,6 +6,10 @@ openapi: post /v3/memories/
|
||||
|
||||
List memories scoped by filters with paginated results. Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object — top-level entity IDs are rejected with 400.
|
||||
|
||||
Expired memories are hidden by default. Pass `show_expired: true` to include memories whose `expiration_date` has passed.
|
||||
|
||||
Python uses `show_expired`; TypeScript uses `showExpired`.
|
||||
|
||||
The `filters` object supports complex logical operations (AND, OR, NOT) and comparison operators:
|
||||
|
||||
- `in`: Matches any of the values specified
|
||||
@@ -32,6 +36,7 @@ memories = client.get_all(
|
||||
}
|
||||
]
|
||||
},
|
||||
show_expired=False,
|
||||
page=1,
|
||||
page_size=50
|
||||
)
|
||||
@@ -46,12 +51,14 @@ memories = client.get_all(
|
||||
{
|
||||
"id": "f4cbdb08-7062-4f3e-8eb2-9f5c80dfe64c",
|
||||
"memory": "Alex is planning a trip to San Francisco from July 1st to July 10th",
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-01T12:00:00Z",
|
||||
"updated_at": "2024-07-01T12:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "a2b8c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
|
||||
"memory": "Alex prefers vegetarian restaurants",
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-05T15:30:00Z",
|
||||
"updated_at": "2024-07-05T15:30:00Z"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ Relevance-ranked hybrid search across stored memories. V3 uses multi-signal retr
|
||||
|
||||
Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object — top-level entity IDs are rejected with 400. At least one entity ID is required.
|
||||
|
||||
Expired memories are hidden by default. Pass `show_expired: true` to include memories whose `expiration_date` has passed.
|
||||
|
||||
Python uses `show_expired`; TypeScript uses `showExpired`.
|
||||
|
||||
The `filters` object supports complex logical operations (AND, OR, NOT) and comparison operators:
|
||||
- `in`: Matches any of the values specified
|
||||
- `gte`: Greater than or equal to
|
||||
@@ -30,6 +34,7 @@ The `filters` object supports complex logical operations (AND, OR, NOT) and comp
|
||||
```python Platform API Example
|
||||
related_memories = client.search(
|
||||
query="What are Alice's hobbies?",
|
||||
show_expired=False,
|
||||
filters={
|
||||
"OR": [
|
||||
{
|
||||
@@ -54,6 +59,7 @@ related_memories = client.search(
|
||||
"category": "hobbies"
|
||||
},
|
||||
"score": 0.82,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T10:29:36.630547-07:00",
|
||||
"updated_at": null,
|
||||
"categories": ["hobbies"]
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
---
|
||||
title: 'Update Memory'
|
||||
description: "Update the content or metadata of a single memory by its unique ID using the PUT endpoint."
|
||||
description: "Update the content, metadata, timestamp, or expiration date of a single memory by its unique ID using the PUT endpoint."
|
||||
openapi: put /v1/memories/{memory_id}/
|
||||
---
|
||||
---
|
||||
|
||||
Use this endpoint to update mutable memory fields. To make a memory expire, set `expiration_date` to a `YYYY-MM-DD` date. To make it permanent again, send `expiration_date: null`.
|
||||
|
||||
```python
|
||||
client.update("mem_123", expiration_date="2030-01-31")
|
||||
client.update("mem_123", expiration_date=None)
|
||||
```
|
||||
|
||||
TypeScript uses `expirationDate`.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Remove Organization Member"
|
||||
description: "Remove a member from a Mem0 organization by revoking their access and clearing their assigned role."
|
||||
openapi: "delete /api/v1/orgs/organizations/{org_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Organization Member"
|
||||
description: "Update the role assigned to an existing member of a Mem0 organization to change their permissions."
|
||||
openapi: "put /api/v1/orgs/organizations/{org_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Remove Project Member"
|
||||
description: "Remove a member from a Mem0 project to revoke their access and unassign their project-level role."
|
||||
openapi: "delete /api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Project Member"
|
||||
description: "Update the role assigned to an existing member of a Mem0 project to change their project-level permissions."
|
||||
openapi: "put /api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Project"
|
||||
description: "Update settings and configuration for an existing Mem0 project, including name and project-level options."
|
||||
openapi: "patch /api/v1/orgs/organizations/{org_id}/projects/{project_id}/"
|
||||
---
|
||||
@@ -7,6 +7,65 @@ mode: "wide"
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
<Update label="2026-06-24" description="v2.0.9">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory (OSS):** Improve entity extraction precision by avoiding sentence-start common noun noise, preserving useful topic phrases, and exact-deduplicating entity links before semantic matching ([#5829](https://github.com/mem0ai/mem0/pull/5829))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-24" description="v2.0.8">
|
||||
|
||||
**New Features:**
|
||||
- **Embeddings:** Add native `embed_batch` to five embedders — LM Studio, Together, HuggingFace, Vertex AI, and Google GenAI — for batched embedding requests ([#5609](https://github.com/mem0ai/mem0/pull/5609))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** Guard against malformed `image_url` entries in `parse_vision_messages` to prevent crashes ([#5631](https://github.com/mem0ai/mem0/pull/5631))
|
||||
- **Core:** Return `attributed_to` from `get()`, `get_all()`, and `search()` ([#5629](https://github.com/mem0ai/mem0/pull/5629))
|
||||
- **Core:** Fix `reset()` only dropping the history table and leaving stale messages behind ([#5541](https://github.com/mem0ai/mem0/pull/5541))
|
||||
- **Core:** Guard against an entity `embed_batch` count mismatch in the v3 add pipeline ([#5604](https://github.com/mem0ai/mem0/pull/5604))
|
||||
- **Core:** Fix an async `delete_all` race condition that corrupted the entity store's `linked_memory_ids` ([#5553](https://github.com/mem0ai/mem0/pull/5553))
|
||||
- **LLMs:** Skip the JSON `response_format` for Groq compound models that reject it ([#5513](https://github.com/mem0ai/mem0/pull/5513))
|
||||
- **LLMs:** Preserve reasoning fields during base-to-provider config conversion ([#5638](https://github.com/mem0ai/mem0/pull/5638))
|
||||
- **LLMs:** Pass the configured `anthropic_base_url` to the Anthropic client ([#5626](https://github.com/mem0ai/mem0/pull/5626))
|
||||
- **LLMs:** Stop the Azure provider from mutating and corrupting caller messages during content rewrite ([#5731](https://github.com/mem0ai/mem0/pull/5731))
|
||||
- **LLMs & Embeddings:** Repair HTTP proxy support for `httpx>=0.28` and preserve `proxies` in `LlmFactory` ([#5447](https://github.com/mem0ai/mem0/pull/5447))
|
||||
- **Embeddings:** Forward `embedding_dims` to Titan V2 in the AWS Bedrock embedder ([#5671](https://github.com/mem0ai/mem0/pull/5671))
|
||||
- **Rerankers:** Log reranking failures instead of swallowing them silently ([#5717](https://github.com/mem0ai/mem0/pull/5717))
|
||||
- **Rerankers:** Clamp out-of-range LLM scores instead of mis-parsing them ([#5635](https://github.com/mem0ai/mem0/pull/5635))
|
||||
- **Rerankers:** Export all five rerankers from the package root ([#5636](https://github.com/mem0ai/mem0/pull/5636))
|
||||
- **Vector Stores:** Point the FastEmbed-missing warning at `mem0ai[extras]` ([#5622](https://github.com/mem0ai/mem0/pull/5622))
|
||||
- **Vector Stores:** Preserve empty Azure AI Search update values ([#5524](https://github.com/mem0ai/mem0/pull/5524))
|
||||
- **Vector Stores:** Add an `auto_refresh` option for OpenSearch Serverless compatibility ([#3893](https://github.com/mem0ai/mem0/pull/3893))
|
||||
- **Vector Stores:** Wrap a scalar `vector_id` in a list for Chroma `delete()` ([#5703](https://github.com/mem0ai/mem0/pull/5703))
|
||||
- **Vector Stores:** Wrap Chroma `update()` ids, embeddings, and metadatas in lists ([#5757](https://github.com/mem0ai/mem0/pull/5757))
|
||||
- **Vector Stores:** Wrap a scalar `vector_id` in a list for Milvus `delete()` ([#5704](https://github.com/mem0ai/mem0/pull/5704))
|
||||
- **Vector Stores:** Map all comparison operators in the Pinecone `_create_filter()` ([#5707](https://github.com/mem0ai/mem0/pull/5707))
|
||||
- **Vector Stores:** Return `None` instead of `{}` from Chroma `_generate_where_clause` for empty filters ([#5713](https://github.com/mem0ai/mem0/pull/5713))
|
||||
- **Vector Stores:** Return `[[]]` from the OpenSearch `list()` error path to honor the `list()` contract ([#5727](https://github.com/mem0ai/mem0/pull/5727))
|
||||
- **Vector Stores:** Return `[[]]` from the Pinecone `list()` error path instead of a dict ([#5706](https://github.com/mem0ai/mem0/pull/5706))
|
||||
- **Vector Stores:** Return `[[]]` for an uninitialized FAISS index to honor the `list()` contract ([#5725](https://github.com/mem0ai/mem0/pull/5725))
|
||||
- **Vector Stores:** Wrap the MongoDB `list()` return in an outer list to match the interface contract ([#5729](https://github.com/mem0ai/mem0/pull/5729))
|
||||
- **Vector Stores:** Deep-copy Redis `DEFAULT_FIELDS` so instances keep distinct dims ([#5633](https://github.com/mem0ai/mem0/pull/5633))
|
||||
- **Vector Stores:** Pass the required `vectors` arg in Vertex AI `list()` and similarity search ([#5627](https://github.com/mem0ai/mem0/pull/5627))
|
||||
- **Vector Stores:** Return `None` from Redis `get()` for missing IDs ([#5625](https://github.com/mem0ai/mem0/pull/5625))
|
||||
- **Vector Stores:** Drop a stray `print` in Weaviate `list_cols` ([#5637](https://github.com/mem0ai/mem0/pull/5637))
|
||||
- **Graph:** Keep distinct entities that share a substring prefix ([#5630](https://github.com/mem0ai/mem0/pull/5630))
|
||||
- **Client:** Check the HTTP status before parsing the ping response in `_validate_api_key` ([#5639](https://github.com/mem0ai/mem0/pull/5639))
|
||||
- **Server:** Fetch filtered dashboard memories beyond the default page ([#5753](https://github.com/mem0ai/mem0/pull/5753))
|
||||
- **Server:** Return 404/400 instead of 502 for not-found and invalid input ([#5634](https://github.com/mem0ai/mem0/pull/5634))
|
||||
- **Server:** Return 404 instead of 500 for a malformed API key id on revoke ([#5640](https://github.com/mem0ai/mem0/pull/5640))
|
||||
- **Server:** Use `127.0.0.1` in the dashboard healthcheck to avoid IPv6 localhost resolution ([#5612](https://github.com/mem0ai/mem0/pull/5612))
|
||||
|
||||
**Improvements:**
|
||||
- **Vector Stores:** Batch BM25 sparse encoding in Qdrant insert ([#5592](https://github.com/mem0ai/mem0/pull/5592))
|
||||
|
||||
**Security:**
|
||||
- **Vector Stores:** Sanitize Milvus and Baidu filter values to prevent expression injection ([#5746](https://github.com/mem0ai/mem0/pull/5746))
|
||||
- **Vector Stores:** Reject dict filter values in MongoDB to prevent NoSQL operator injection ([#5748](https://github.com/mem0ai/mem0/pull/5748))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-17" description="v2.0.7">
|
||||
|
||||
**New Features:**
|
||||
@@ -1011,6 +1070,31 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to-
|
||||
|
||||
<Tab title="TypeScript">
|
||||
|
||||
<Update label="2026-06-24" description="v3.0.11">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory (OSS):** Align entity extraction with Python by reducing generic entity noise, preserving useful topic phrases, and exact-deduplicating entity links before semantic matching ([#5829](https://github.com/mem0ai/mem0/pull/5829))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-24" description="v3.0.10">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory (OSS):** Guard against malformed `image_url` entries in `parseVisionMessages` to prevent crashes ([#5631](https://github.com/mem0ai/mem0/pull/5631))
|
||||
- **Memory (OSS):** Return `attributedTo` from `get()`, `search()`, and `getAll()` ([#5675](https://github.com/mem0ai/mem0/pull/5675))
|
||||
- **Memory (OSS):** Preserve message roles in the extraction input so assistant facts aren't attributed to the user ([#5643](https://github.com/mem0ai/mem0/pull/5643))
|
||||
- **Memory (OSS):** Reject empty or blank messages in `Memory.add()` to prevent hallucinated memories ([#5545](https://github.com/mem0ai/mem0/pull/5545))
|
||||
- **Memory (OSS):** Check `message.role` instead of `content` when detecting system messages ([#3921](https://github.com/mem0ai/mem0/pull/3921))
|
||||
- **LLMs:** Honor the configured `baseURL` in `AnthropicLLM` ([#5740](https://github.com/mem0ai/mem0/pull/5740))
|
||||
- **Client:** Preserve `customCategories` names through key conversion ([#5741](https://github.com/mem0ai/mem0/pull/5741))
|
||||
- **Client:** Prevent hallucinated memories on an empty messages payload ([#5613](https://github.com/mem0ai/mem0/pull/5613))
|
||||
- **Client:** Preserve user metadata keys across the case-conversion round-trip ([#5515](https://github.com/mem0ai/mem0/pull/5515))
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Upgrade `form-data` to `>=4.0.6` across pnpm workspaces to remediate CVE-2026-12143 ([#5618](https://github.com/mem0ai/mem0/pull/5618))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-17" description="v3.0.9">
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "FastEmbed"
|
||||
description: "Configure FastEmbed as an embedding provider in Mem0 to generate embeddings locally using ONNX-based models without a GPU."
|
||||
---
|
||||
|
||||
You can use FastEmbed to run embedding models locally in Mem0. FastEmbed is an ONNX-based embedding library that runs efficiently on CPU without requiring a GPU or an external API key.
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install fastembed
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "your_api_key" # For LLM
|
||||
|
||||
config = {
|
||||
"embedder": {
|
||||
"provider": "fastembed",
|
||||
"config": {
|
||||
"model": "thenlper/gte-large"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
m.add(messages, user_id="john")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring FastEmbed embedder:
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `model` | The name of the FastEmbed model to use | `thenlper/gte-large` |
|
||||
| `embedding_dims` | Dimensions of the embedding model (auto-derived from the model if not set) | `None` |
|
||||
@@ -7,7 +7,8 @@ To use DeepSeek LLM models, you have to set the `DEEPSEEK_API_KEY` environment v
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
@@ -36,6 +37,32 @@ messages = [
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import { Memory } from 'mem0ai/oss';
|
||||
|
||||
const config = {
|
||||
llm: {
|
||||
provider: 'deepseek',
|
||||
config: {
|
||||
apiKey: process.env.DEEPSEEK_API_KEY || '',
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.2,
|
||||
maxTokens: 2000,
|
||||
top_p: 1.0,
|
||||
},
|
||||
},
|
||||
};
|
||||
const memory = new Memory(config);
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
];
|
||||
await memory.add(messages, { userId: 'alice', metadata: { category: 'movies' } });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
You can also configure the API base URL in the config:
|
||||
|
||||
```python
|
||||
|
||||
@@ -4,9 +4,12 @@ description: "Use LiteLLM as an LLM provider in Mem0 to access over 100 language
|
||||
---
|
||||
[Litellm](https://litellm.vercel.app/docs/) is compatible with over 100 large language models (LLMs), all using a standardized input/output format. You can explore the [available models](https://litellm.vercel.app/docs/providers) to use with Litellm. Ensure you set the `API_KEY` for the model you choose to use.
|
||||
|
||||
In the TypeScript SDK, run LiteLLM as a [proxy server](https://docs.litellm.ai/docs/simple_proxy) (an OpenAI-compatible endpoint) and point Mem0 at it via `LITELLM_API_BASE` (defaults to `http://localhost:4000`).
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
@@ -33,6 +36,33 @@ messages = [
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import { Memory } from 'mem0ai/oss';
|
||||
|
||||
// Point Mem0 at your LiteLLM proxy. apiKey defaults to "sk-anything"
|
||||
// (the proxy handles real auth); baseURL defaults to http://localhost:4000.
|
||||
const config = {
|
||||
llm: {
|
||||
provider: 'litellm',
|
||||
config: {
|
||||
apiKey: process.env.LITELLM_API_KEY || 'sk-anything',
|
||||
baseURL: process.env.LITELLM_API_BASE || 'http://localhost:4000',
|
||||
model: 'gpt-5-mini',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const memory = new Memory(config);
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
];
|
||||
await memory.add(messages, { userId: 'alice', metadata: { category: 'movies' } });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Config
|
||||
|
||||
All available parameters for the `litellm` config are present in [Master List of All Params in Config](../config).
|
||||
@@ -7,7 +7,8 @@ To use MiniMax LLM models, you have to set the `MINIMAX_API_KEY` environment var
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
@@ -36,9 +37,37 @@ messages = [
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import { Memory } from 'mem0ai/oss';
|
||||
|
||||
const config = {
|
||||
llm: {
|
||||
provider: 'minimax',
|
||||
config: {
|
||||
apiKey: process.env.MINIMAX_API_KEY || '',
|
||||
model: 'MiniMax-M2.7',
|
||||
temperature: 0.2,
|
||||
maxTokens: 2000,
|
||||
topP: 1.0,
|
||||
},
|
||||
},
|
||||
};
|
||||
const memory = new Memory(config);
|
||||
const messages = [
|
||||
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
|
||||
{ role: "assistant", content: "How about thriller movies? They can be quite engaging." },
|
||||
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
|
||||
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." },
|
||||
];
|
||||
await memory.add(messages, { userId: 'alice', metadata: { category: 'movies' } });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
You can also configure the API base URL in the config:
|
||||
|
||||
```python
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
config = {
|
||||
"llm": {
|
||||
"provider": "minimax",
|
||||
@@ -51,6 +80,20 @@ config = {
|
||||
}
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
const config = {
|
||||
llm: {
|
||||
provider: 'minimax',
|
||||
config: {
|
||||
model: 'MiniMax-M2.7',
|
||||
baseURL: 'https://your-custom-endpoint.com',
|
||||
apiKey: 'your-api-key', // alternatively to using the environment variable
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Config
|
||||
|
||||
All available parameters for the `minimax` config are present in [Master List of All Params in Config](../config).
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
---
|
||||
title: LLM as Reranker
|
||||
description: "Use any LLM as a flexible reranker in Mem0 with custom prompts and domain-specific scoring logic."
|
||||
---
|
||||
|
||||
<Warning>
|
||||
**This page has been superseded.** Please see [LLM Reranker](/components/rerankers/models/llm_reranker) for the complete and up-to-date documentation on using LLMs for reranking.
|
||||
</Warning>
|
||||
|
||||
LLM-based reranker provides maximum flexibility by using any Large Language Model to score document relevance. This approach allows for custom prompts and domain-specific scoring logic.
|
||||
|
||||
## Supported LLM Providers
|
||||
|
||||
Any LLM provider supported by Mem0 can be used for reranking:
|
||||
|
||||
- **OpenAI**: GPT-4, GPT-3.5-turbo, etc.
|
||||
- **Anthropic**: Claude models
|
||||
- **Together**: Open-source models
|
||||
- **Groq**: Fast inference
|
||||
- **Ollama**: Local models
|
||||
- And more...
|
||||
|
||||
## Configuration
|
||||
|
||||
```python Python
|
||||
from mem0 import Memory
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "my_memories",
|
||||
"path": "./chroma_db"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini"
|
||||
}
|
||||
},
|
||||
"reranker": {
|
||||
"provider": "llm",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"provider": "openai",
|
||||
"api_key": "your-openai-api-key", # or set OPENAI_API_KEY
|
||||
"top_k": 5,
|
||||
"temperature": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memory = Memory.from_config(config)
|
||||
```
|
||||
|
||||
## Custom Scoring Prompt
|
||||
|
||||
You can provide a custom prompt for relevance scoring:
|
||||
|
||||
```python Python
|
||||
custom_prompt = """You are a relevance scoring assistant. Rate how well this document answers the query.
|
||||
|
||||
Query: "{query}"
|
||||
Document: "{document}"
|
||||
|
||||
Score from 0.0 to 1.0 where:
|
||||
- 1.0: Perfect match, directly answers the query
|
||||
- 0.8-0.9: Highly relevant, good match
|
||||
- 0.6-0.7: Moderately relevant, partial match
|
||||
- 0.4-0.5: Slightly relevant, limited useful information
|
||||
- 0.0-0.3: Not relevant or no useful information
|
||||
|
||||
Provide only a single numerical score between 0.0 and 1.0."""
|
||||
|
||||
config["reranker"]["config"]["scoring_prompt"] = custom_prompt
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
# Set API key
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||
|
||||
# Initialize memory with LLM reranker
|
||||
config = {
|
||||
"vector_store": {"provider": "chroma"},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-4o-mini"}},
|
||||
"reranker": {
|
||||
"provider": "llm",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"provider": "openai",
|
||||
"temperature": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memory = Memory.from_config(config)
|
||||
|
||||
# Add memories
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm learning Python programming"},
|
||||
{"role": "user", "content": "I find object-oriented programming challenging"},
|
||||
{"role": "user", "content": "I love hiking in national parks"}
|
||||
]
|
||||
|
||||
memory.add(messages, user_id="david")
|
||||
|
||||
# Search with LLM reranking
|
||||
results = memory.search("What programming topics is the user studying?", filters={"user_id": "david"})
|
||||
|
||||
for result in results['results']:
|
||||
print(f"Memory: {result['memory']}")
|
||||
print(f"Vector Score: {result['score']:.3f}")
|
||||
print(f"Rerank Score: {result['rerank_score']:.3f}")
|
||||
print()
|
||||
```
|
||||
|
||||
```text Output
|
||||
Memory: I'm learning Python programming
|
||||
Vector Score: 0.856
|
||||
Rerank Score: 0.920
|
||||
|
||||
Memory: I find object-oriented programming challenging
|
||||
Vector Score: 0.782
|
||||
Rerank Score: 0.850
|
||||
```
|
||||
|
||||
## Domain-Specific Scoring
|
||||
|
||||
Create specialized scoring for your domain:
|
||||
|
||||
```python Python
|
||||
medical_prompt = """You are a medical relevance expert. Score how relevant this medical record is to the clinical query.
|
||||
|
||||
Clinical Query: "{query}"
|
||||
Medical Record: "{document}"
|
||||
|
||||
Consider:
|
||||
- Clinical relevance and accuracy
|
||||
- Patient safety implications
|
||||
- Diagnostic value
|
||||
- Treatment relevance
|
||||
|
||||
Score from 0.0 to 1.0. Provide only the numerical score."""
|
||||
|
||||
config = {
|
||||
"reranker": {
|
||||
"provider": "llm",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"provider": "openai",
|
||||
"scoring_prompt": medical_prompt,
|
||||
"temperature": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple LLM Providers
|
||||
|
||||
Use different LLM providers for reranking:
|
||||
|
||||
```python Python
|
||||
# Using Anthropic Claude
|
||||
anthropic_config = {
|
||||
"reranker": {
|
||||
"provider": "llm",
|
||||
"config": {
|
||||
"model": "claude-3-haiku-20240307",
|
||||
"provider": "anthropic",
|
||||
"temperature": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Using local Ollama model
|
||||
ollama_config = {
|
||||
"reranker": {
|
||||
"provider": "llm",
|
||||
"config": {
|
||||
"model": "llama2:7b",
|
||||
"provider": "ollama",
|
||||
"temperature": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Parameters
|
||||
|
||||
| Parameter | Description | Type | Default |
|
||||
|-----------|-------------|------|---------|
|
||||
| `model` | LLM model to use for scoring | `str` | `"gpt-4o-mini"` |
|
||||
| `provider` | LLM provider name | `str` | `"openai"` |
|
||||
| `api_key` | API key for the LLM provider | `str` | `None` |
|
||||
| `top_k` | Maximum documents to return | `int` | `None` |
|
||||
| `temperature` | Temperature for LLM generation | `float` | `0.0` |
|
||||
| `max_tokens` | Maximum tokens for LLM response | `int` | `100` |
|
||||
| `scoring_prompt` | Custom prompt template | `str` | Default prompt |
|
||||
|
||||
## Advantages
|
||||
|
||||
- **Maximum Flexibility**: Custom prompts for any use case
|
||||
- **Domain Expertise**: Leverage LLM knowledge for specialized domains
|
||||
- **Interpretability**: Understand scoring through prompt engineering
|
||||
- **Multi-criteria**: Score based on multiple relevance factors
|
||||
|
||||
## Considerations
|
||||
|
||||
- **Latency**: Higher latency than specialized rerankers
|
||||
- **Cost**: LLM API costs per reranking operation
|
||||
- **Consistency**: May have slight variations in scoring
|
||||
- **Prompt Engineering**: Requires careful prompt design
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Temperature**: Use 0.0 for consistent scoring
|
||||
2. **Prompt Design**: Be specific about scoring criteria
|
||||
3. **Token Efficiency**: Keep prompts concise to reduce costs
|
||||
4. **Caching**: Cache results for repeated queries when possible
|
||||
5. **Fallback**: Handle API errors gracefully
|
||||
@@ -21,49 +21,47 @@ from mem0 import Memory
|
||||
load_dotenv()
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "pgvector",
|
||||
"config": {
|
||||
"connection_string": os.environ["DATABASE_URL"],
|
||||
"collection_name": "memories",
|
||||
"embedding_model_dims": 1536,
|
||||
"hnsw": True,
|
||||
},
|
||||
},
|
||||
"vector_store": {
|
||||
"provider": "pgvector",
|
||||
"config": {
|
||||
"connection_string": os.environ["DATABASE_URL"],
|
||||
"collection_name": "memories",
|
||||
"embedding_model_dims": 1536,
|
||||
"hnsw": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."},
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."},
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
|
||||
results = m.search(
|
||||
"What movies should I recommend?",
|
||||
filters={"user_id": "alice"},
|
||||
"What movies should I recommend?",
|
||||
filters={"user_id": "alice"},
|
||||
)
|
||||
|
||||
print(results)
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
```typescript TypeScript
|
||||
import "dotenv/config";
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const databaseUrl = new URL(process.env.DATABASE_URL!);
|
||||
|
||||
const m = new Memory({
|
||||
vectorStore: {
|
||||
provider: "pgvector",
|
||||
config: {
|
||||
user: decodeURIComponent(databaseUrl.username),
|
||||
password: decodeURIComponent(databaseUrl.password),
|
||||
host: databaseUrl.hostname,
|
||||
port: Number(databaseUrl.port || 5432),
|
||||
dbname: databaseUrl.pathname.slice(1) || "neondb",
|
||||
connectionString: process.env.DATABASE_URL!,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
collectionName: "memories",
|
||||
dimension: 1536,
|
||||
embeddingModelDims: 1536,
|
||||
@@ -89,7 +87,8 @@ const results = await m.search("What movies should I recommend?", {
|
||||
});
|
||||
|
||||
console.log(results);
|
||||
```
|
||||
````
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## SQL Migration
|
||||
@@ -116,20 +115,19 @@ DATABASE_URL=postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neond
|
||||
| `sslmode` | PostgreSQL SSL mode. Use `require` for Neon. | Driver default |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
The current Mem0 TypeScript `pgvector` adapter takes individual Postgres fields,
|
||||
so parse `DATABASE_URL` before creating `Memory`.
|
||||
Use the Neon `DATABASE_URL` directly with `connectionString`. Set `ssl` if your runtime needs an explicit TLS config object.
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| -------------------- | ---------------------------------------------- | -------------- |
|
||||
| `connectionString` | Neon Postgres connection string. | Required |
|
||||
| `ssl` | Optional TLS settings passed directly to `pg`. | Driver default |
|
||||
| `collectionName` | Name for the vector collection. | `memories` |
|
||||
| `dimension` | Vector dimension for Mem0 config. | Auto-detected |
|
||||
| `embeddingModelDims` | Embedding model dimensions for table creation. | Required |
|
||||
| `hnsw` | Enables HNSW indexing. | `false` |
|
||||
|
||||
**TLS note:** `ssl: true` is sufficient for most Neon connections since Neon uses valid certificates. Use `ssl: { rejectUnauthorized: false }` only when connecting through Neon's connection pooler on certain edge runtimes (e.g. Cloudflare Workers) that require it, or when your environment does not trust the Neon CA chain.
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `user` | Database user. | Required |
|
||||
| `password` | Database password. | Required |
|
||||
| `host` | Database host. | Required |
|
||||
| `port` | Database port. | `5432` |
|
||||
| `dbname` | Database name. | `vector_store` |
|
||||
| `collectionName` | Name for the vector collection. | `memories` |
|
||||
| `dimension` | Vector dimension for Mem0 config. | Auto-detected |
|
||||
| `embeddingModelDims` | Embedding model dimensions for table creation. | Required |
|
||||
| `hnsw` | Enables HNSW indexing. | `false` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -56,6 +56,30 @@ config = {
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `collection_name` | string | required | Name of the OpenSearch index |
|
||||
| `host` | string | required | OpenSearch endpoint URL |
|
||||
| `port` | int | 9200 | Port number |
|
||||
| `http_auth` | object | None | Authentication credentials (e.g., AWSV4SignerAuth) |
|
||||
| `embedding_model_dims` | int | 1536 | Dimension of embedding vectors |
|
||||
| `use_ssl` | bool | False | Enable SSL/TLS connection |
|
||||
| `verify_certs` | bool | False | Verify SSL certificates |
|
||||
| `auto_refresh` | bool | False | Automatically refresh index after insert. OpenSearch refreshes every ~1 second by default, so this is rarely needed. |
|
||||
|
||||
<Note>
|
||||
The defaults above match a local OpenSearch instance. The AWS OpenSearch Serverless
|
||||
example earlier on this page intentionally overrides them with `port=443`, `use_ssl=True`,
|
||||
and `verify_certs=True`, which are required when connecting to a Serverless collection.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
For **AWS OpenSearch Serverless**, keep `auto_refresh=False` (the default).
|
||||
The `indices.refresh()` API is not supported on Serverless collections.
|
||||
</Note>
|
||||
|
||||
### Add Memories
|
||||
|
||||
```python
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "pgvector"
|
||||
description: "Use pgvector as a vector store in Mem0 for PostgreSQL-based vector similarity search with open-source simplicity."
|
||||
---
|
||||
|
||||
[pgvector](https://github.com/pgvector/pgvector) is an open-source vector similarity search extension for Postgres. After connecting to Postgres, run `CREATE EXTENSION IF NOT EXISTS vector;` to create the vector extension.
|
||||
|
||||
### Usage
|
||||
@@ -14,41 +15,38 @@ from mem0 import Memory
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx"
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "pgvector",
|
||||
"config": {
|
||||
"user": "test",
|
||||
"password": "123",
|
||||
"host": "127.0.0.1",
|
||||
"port": "5432",
|
||||
}
|
||||
}
|
||||
"vector_store": {
|
||||
"provider": "pgvector",
|
||||
"config": {
|
||||
"user": "test",
|
||||
"password": "123",
|
||||
"host": "127.0.0.1",
|
||||
"port": "5432",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."},
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
```typescript TypeScript
|
||||
import { Memory } from 'mem0ai/oss';
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const config = {
|
||||
vectorStore: {
|
||||
provider: 'pgvector',
|
||||
provider: "pgvector",
|
||||
config: {
|
||||
collectionName: 'memories',
|
||||
collectionName: "memories",
|
||||
embeddingModelDims: 1536,
|
||||
user: 'test',
|
||||
password: '123',
|
||||
host: '127.0.0.1',
|
||||
port: 5432,
|
||||
dbname: 'vector_store', // Optional; TypeScript OSS defaults to `vector_store` when omitted
|
||||
connectionString: "postgresql://test:123@localhost:5432/vector_store",
|
||||
diskann: false, // Optional, requires pgvectorscale extension
|
||||
hnsw: false, // Optional, for HNSW indexing
|
||||
},
|
||||
@@ -57,37 +55,44 @@ const config = {
|
||||
|
||||
const memory = new Memory(config);
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
|
||||
{ role: "assistant", content: "How about thriller movies? They can be quite engaging." },
|
||||
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
|
||||
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." },
|
||||
];
|
||||
|
||||
await memory.add(messages, { userId: "alice", metadata: { category: "movies" } });
|
||||
```
|
||||
````
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring pgvector:
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `dbname` | The name of the database | `postgres` |
|
||||
| `collection_name` | The name of the collection | `mem0` |
|
||||
| `embedding_model_dims` | Dimensions of the embedding model | `1536` |
|
||||
| `user` | User name to connect to the database | `None` |
|
||||
| `password` | Password to connect to the database | `None` |
|
||||
| `host` | The host where the Postgres server is running | `None` |
|
||||
| `port` | The port where the Postgres server is running | `None` |
|
||||
| `diskann` | Whether to use diskann for vector similarity search (requires pgvectorscale) | `True` |
|
||||
| `hnsw` | Whether to use hnsw for vector similarity search | `False` |
|
||||
| `sslmode` | SSL mode for PostgreSQL connection (e.g., 'require', 'prefer', 'disable') | `None` |
|
||||
| `connection_string` | PostgreSQL connection string (overrides individual connection parameters) | `None` |
|
||||
| `connection_pool` | psycopg2 connection pool object (overrides connection string and individual parameters) | `None` |
|
||||
| Parameter | SDK | Description | Default Value |
|
||||
| -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `connectionString` | TypeScript OSS | PostgreSQL connection string for direct connections. When set, Mem0 connects to the target database directly and skips the bootstrap `postgres` database flow. | `None` |
|
||||
| `ssl` | TypeScript OSS | SSL option passed directly to `pg`, either `true` or an SSL config object, for both `connectionString` and split-field connections. | `None` |
|
||||
| `dbname` | TypeScript OSS | Split-field database name. This is only used when `connectionString` is absent. | `vector_store` |
|
||||
| `collectionName` | TypeScript OSS | Collection name. | `memories` |
|
||||
| `embeddingModelDims` | TypeScript OSS | Dimensions of the embedding model. | Required |
|
||||
| `user` | TypeScript OSS + Python | Database user for split-field connections. | `None` |
|
||||
| `password` | TypeScript OSS + Python | Database password for split-field connections. | `None` |
|
||||
| `host` | TypeScript OSS + Python | Database host for split-field connections. | `None` |
|
||||
| `port` | TypeScript OSS + Python | Database port for split-field connections. | `None` |
|
||||
| `diskann` | TypeScript OSS + Python | Whether to use DiskANN for vector similarity search, requires pgvectorscale. | `False` |
|
||||
| `hnsw` | TypeScript OSS + Python | Whether to use HNSW for vector similarity search. | TypeScript OSS: `False`, Python: `True` |
|
||||
| `connection_string` | Python only | PostgreSQL connection string, overrides individual connection parameters. | `None` |
|
||||
| `sslmode` | Python only | SSL mode for PostgreSQL connections, such as `require`, `prefer`, or `disable`. | `None` |
|
||||
| `connection_pool` | Python only | psycopg connection pool object, overrides connection string and individual connection parameters. | `None` |
|
||||
|
||||
**Note (TypeScript OSS):** If you omit `dbname`, the TypeScript client uses the database name `vector_store`. Python defaults to `postgres` for `dbname`, as in the table above.
|
||||
**TypeScript OSS:** Use `connectionString` plus optional `ssl` for managed Postgres setups. If you omit `connectionString`, Mem0 falls back to split fields and uses `dbname`, `user`, `password`, `host`, `port`, and optional `ssl`.
|
||||
|
||||
**Python:** The Python SDK uses snake_case keys such as `connection_string`, `sslmode`, `collection_name`, and `embedding_model_dims`.
|
||||
|
||||
**Python connection priority**:
|
||||
|
||||
**Note**: The connection parameters have the following priority:
|
||||
1. `connection_pool` (highest priority)
|
||||
2. `connection_string`
|
||||
3. Individual connection parameters (`user`, `password`, `host`, `port`, `sslmode`)
|
||||
3. Individual connection parameters (`user`, `password`, `host`, `port`, `sslmode`)
|
||||
|
||||
@@ -345,7 +345,7 @@ When evaluating memory systems, keep these considerations in mind:
|
||||
<Card title="Research" icon="flask" href="https://mem0.ai/research">
|
||||
Published research papers and technical reports
|
||||
</Card>
|
||||
<Card title="Blog Post" icon="newspaper" href="https://mem0.ai/blog/new-algorithm">
|
||||
<Card title="Blog Post" icon="newspaper" href="https://mem0.ai/blog/the-token-efficient-memory-algorithm-now-has-temporal-reasoning">
|
||||
Detailed writeup of the new algorithm design and results
|
||||
</Card>
|
||||
<Card title="Platform Migration" icon="arrow-right" href="/migration/platform-v2-to-v3">
|
||||
|
||||
@@ -21,7 +21,7 @@ Adding memory is how Mem0 captures useful details from a conversation so your ag
|
||||
- **Messages** – The ordered list of user/assistant turns you send to `add`.
|
||||
- **Infer** – Controls whether Mem0 extracts structured memories (`infer=True`, default) or stores raw messages.
|
||||
- **Metadata** – Optional filters (e.g., `{"category": "movie_recommendations"}`) that improve retrieval later.
|
||||
- **User / Session identifiers** – `user_id`, `agent_id`, or `run_id` that scope the memory for future searches.
|
||||
- **User / Session identifiers** – `user_id`, `agent_id`, `app_id`, or `run_id` that scope the memory for future searches.
|
||||
|
||||
## How does it work?
|
||||
|
||||
@@ -30,22 +30,22 @@ Mem0 offers two flows:
|
||||
- **Mem0 Platform** – Fully managed API with dashboard and scaling.
|
||||
- **Mem0 Open Source** – Local SDK that you run in your own environment.
|
||||
|
||||
Both flows take the same payload and pass it through the same pipeline.
|
||||
Both flows take the same payload and add memories through an additive pipeline.
|
||||
|
||||
<Steps>
|
||||
<Step title="Information extraction">
|
||||
Mem0 sends the messages through an LLM that pulls out key facts, decisions, or preferences to remember.
|
||||
</Step>
|
||||
<Step title="Conflict resolution">
|
||||
Existing memories are checked for duplicates or contradictions so the latest truth wins.
|
||||
<Step title="Additive storage">
|
||||
New memories are added without overwriting or deleting existing memories.
|
||||
</Step>
|
||||
<Step title="Storage">
|
||||
The resulting memories land in managed vector storage so future searches return them quickly.
|
||||
<Step title="Retrieval">
|
||||
Future searches rank the most relevant memories for the query.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Warning>
|
||||
Duplicate protection only runs during that conflict-resolution step when you let Mem0 infer memories (`infer=True`, the default). If you switch to `infer=False`, Mem0 stores your payload exactly as provided, so duplicates will land. Mixing both modes for the same fact will save it twice.
|
||||
When you switch to `infer=False`, Mem0 stores your payload exactly as provided, so duplicates can land. Mixing both modes for the same fact can save it twice.
|
||||
</Warning>
|
||||
|
||||
You trigger this pipeline with a single `add` call—no manual orchestration needed.
|
||||
@@ -80,13 +80,13 @@ const messages = [
|
||||
];
|
||||
|
||||
await client.add(messages, {
|
||||
user_id: "alice",
|
||||
userId: "alice",
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Info icon="check">
|
||||
Expect a `memory_id` (or list of IDs) in the response. Check the Mem0 dashboard to confirm the new entry under the correct user.
|
||||
Expect a `status: "PENDING"` response with an `event_id`. Poll `GET /v1/event/{event_id}/` to confirm completion.
|
||||
</Info>
|
||||
|
||||
## Add with Mem0 Open Source
|
||||
@@ -138,7 +138,7 @@ const result = memory.add(messages, {
|
||||
</Tip>
|
||||
|
||||
<Warning>
|
||||
If you do choose `infer=False`, keep it consistent. Raw inserts skip conflict resolution, so a later `infer=True` call with the same content will create a second memory instead of updating the first.
|
||||
If you do choose `infer=False`, keep it consistent. Raw inserts skip inference, so a later `infer=True` call with the same content can create a second memory.
|
||||
</Warning>
|
||||
|
||||
## When Should You Add Memory?
|
||||
@@ -167,7 +167,7 @@ For full list of supported fields, required formats, and advanced options, see t
|
||||
|
||||
| Capability | Mem0 Platform | Mem0 OSS |
|
||||
| --- | --- | --- |
|
||||
| Conflict resolution | Automatic with dashboard visibility | SDK handles merges locally; you control storage |
|
||||
| Add behavior | ADD-only; memories accumulate | ADD-only; you control storage |
|
||||
| Rate limits | Managed quotas per workspace | Limited by your hardware and provider APIs |
|
||||
| Dashboard visibility | Yes — inspect memories visually | Inspect via CLI, logs, or custom UI |
|
||||
|
||||
|
||||
+12
-2
@@ -273,7 +273,8 @@
|
||||
"components/embedders/models/lmstudio",
|
||||
"components/embedders/models/together",
|
||||
"components/embedders/models/langchain",
|
||||
"components/embedders/models/aws_bedrock"
|
||||
"components/embedders/models/aws_bedrock",
|
||||
"components/embedders/models/fastembed"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -441,7 +442,7 @@
|
||||
"integrations/flowise",
|
||||
"integrations/langchain-tools",
|
||||
"integrations/agentops",
|
||||
"integrations/keywords",
|
||||
"integrations/respan",
|
||||
"integrations/raycast"
|
||||
]
|
||||
}
|
||||
@@ -533,6 +534,8 @@
|
||||
"api-reference/organization/get-org",
|
||||
"api-reference/organization/get-org-members",
|
||||
"api-reference/organization/add-org-member",
|
||||
"api-reference/organization/update-org-member",
|
||||
"api-reference/organization/remove-org-member",
|
||||
"api-reference/organization/delete-org"
|
||||
]
|
||||
},
|
||||
@@ -545,6 +548,9 @@
|
||||
"api-reference/project/get-project",
|
||||
"api-reference/project/get-project-members",
|
||||
"api-reference/project/add-project-member",
|
||||
"api-reference/project/update-project",
|
||||
"api-reference/project/update-project-member",
|
||||
"api-reference/project/remove-project-member",
|
||||
"api-reference/project/delete-project"
|
||||
]
|
||||
},
|
||||
@@ -624,6 +630,10 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/components/rerankers/models/llm",
|
||||
"destination": "/components/rerankers/models/llm_reranker"
|
||||
},
|
||||
{
|
||||
"source": "/migration/breaking-changes",
|
||||
"destination": "/"
|
||||
|
||||
@@ -309,19 +309,21 @@ Here are the available integrations for Mem0:
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Keywords AI"
|
||||
title="Respan"
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.07513 1.1863C9.21663 1.07722 9.39144 1.01009 9.56624 1.01009C9.83261 1.01009 10.0823 1.12756 10.2405 1.33734L15.0101 7.4964V12.4136L16.4335 13.8401C16.7582 14.1673 16.7582 14.7043 16.4335 15.0316C16.1089 15.3588 15.5762 15.3588 15.2515 15.0316L13.3453 13.1016V8.07538L8.92529 2.36944V2.36105C8.64228 2.00024 8.70887 1.4716 9.07513 1.1863ZM18.976 14.4133C18.8344 14.3778 18.7003 14.3042 18.5894 14.1925L16.9163 12.5059C16.7249 12.3129 16.6416 12.0528 16.6749 11.8094V6.88385H16.6499L11.8553 0.691225C11.7282 0.529117 11.6716 0.333133 11.6803 0.140562C11.134 0.0481292 10.5726 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20C13.9387 20 17.3456 17.7229 18.976 14.4133Z" fill="currentColor"></path>
|
||||
<path d="M2.00635 190.234V9.76584H53.3558V29.5101H26.7223V170.562H53.3558V190.234H2.00635Z" fill="currentColor"></path>
|
||||
<path d="M120.692 160.902C116.383 160.902 112.691 159.387 109.612 156.357C106.535 153.327 105.02 149.633 105.067 145.277C105.02 141.016 106.535 137.37 109.612 134.34C112.691 131.309 116.383 129.794 120.692 129.794C124.859 129.794 128.481 131.309 131.559 134.34C134.684 137.37 136.27 141.016 136.317 145.277C136.27 148.166 135.512 150.793 134.045 153.161C132.624 155.528 130.73 157.422 128.362 158.842C126.042 160.216 123.486 160.902 120.692 160.902Z" fill="currentColor"></path>
|
||||
<path d="M197.993 9.76584V190.234H146.643V170.562H173.278V29.5101H146.643V9.76584H197.993Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
}
|
||||
href="/integrations/keywords"
|
||||
href="/integrations/respan"
|
||||
>
|
||||
Build AI applications with persistent memory and comprehensive LLM observability.
|
||||
</Card>
|
||||
|
||||
@@ -73,7 +73,7 @@ client = MemoryClient()
|
||||
# Define the agent
|
||||
agent = Agent(
|
||||
name="Personal Agent",
|
||||
model=OpenAIChat(id="gpt-4"),
|
||||
model=OpenAIChat(id="gpt-5-mini"),
|
||||
description="You are a helpful personal agent that helps me with day to day activities."
|
||||
"You can process both text and images.",
|
||||
markdown=True
|
||||
|
||||
@@ -65,6 +65,7 @@ The plugin uses the same shell scripts as Claude Code, Cursor, and Codex — hoo
|
||||
| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message |
|
||||
| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools |
|
||||
| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Stop** | `Stop` | Stores a session summary when the session ends |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY')
|
||||
memory_client = MemoryClient()
|
||||
agent = ConversableAgent(
|
||||
"chatbot",
|
||||
llm_config={"config_list": [{"model": "gpt-4", "api_key": OPENAI_API_KEY}]},
|
||||
llm_config={"config_list": [{"model": "gpt-5-mini", "api_key": OPENAI_API_KEY}]},
|
||||
code_execution_config=False,
|
||||
human_input_mode="NEVER",
|
||||
)
|
||||
@@ -99,7 +99,7 @@ For more complex scenarios, you can create multiple agents:
|
||||
manager = ConversableAgent(
|
||||
"manager",
|
||||
system_message="You are a manager who helps in resolving complex customer issues.",
|
||||
llm_config={"config_list": [{"model": "gpt-4", "api_key": OPENAI_API_KEY}]},
|
||||
llm_config={"config_list": [{"model": "gpt-5-mini", "api_key": OPENAI_API_KEY}]},
|
||||
human_input_mode="NEVER"
|
||||
)
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ Add the Mem0 MCP server directly with a single command:
|
||||
npx mcp-add \
|
||||
--name mem0-mcp \
|
||||
--type http \
|
||||
--url "https://mcp.mem0.ai/mcp" \
|
||||
--url "https://mcp.mem0.ai/mcp/" \
|
||||
--clients "claude code"
|
||||
```
|
||||
|
||||
@@ -138,11 +138,13 @@ When installed via the plugin marketplace, Mem0 hooks into Claude Code's lifecyc
|
||||
|
||||
| Hook | Event | What it does |
|
||||
|------|-------|-------------|
|
||||
| **Setup** | `Setup` | Installs the mem0 SDK and dependencies (runs on init and maintenance) |
|
||||
| **Session start** | `SessionStart` | Loads prior memories and displays status banner |
|
||||
| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message; skips short prompts |
|
||||
| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls |
|
||||
| **Pre-tool (3 handlers)** | `PreToolUse` | Blocks MEMORY.md writes; enforces `user_id`/`app_id` on mem0 tool calls; scans files being read for relevant memory context |
|
||||
| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Pre-compact** | `PreCompact` | Stores a session summary before context compaction |
|
||||
| **Stop** | `Stop` | Stores a session summary when the session ends |
|
||||
| **Pre-compact** | `PreCompact` | Stores a summary before the context is compacted |
|
||||
|
||||
## Example Workflow
|
||||
|
||||
|
||||
+24
-10
@@ -41,7 +41,17 @@ Install the full plugin including MCP server, lifecycle hooks, and SDK skill.
|
||||
codex plugin marketplace add mem0ai/mem0
|
||||
```
|
||||
|
||||
2. Restart Codex, open the Plugin Directory, browse the **Mem0 Plugins** marketplace, and install **Mem0**.
|
||||
2. Install the plugin:
|
||||
|
||||
```bash
|
||||
codex plugin add mem0@mem0-plugins
|
||||
```
|
||||
|
||||
Or, in the app: restart Codex, open the Plugin Directory, browse the **Mem0 Plugins** marketplace, and install **Mem0**.
|
||||
|
||||
<Note>
|
||||
Step 1 is required for the app UI. Mem0 isn't in OpenAI's curated directory yet, so **without `codex plugin marketplace add`, Mem0 won't appear in the Codex app's Plugin Directory** — searching for it returns nothing. Adding the marketplace surfaces it (under **Created by you**) and makes it installable.
|
||||
</Note>
|
||||
|
||||
<Info>
|
||||
Do not combine with Option B. The plugin manifest auto-registers the `mem0` MCP server, so adding both will create a duplicate registration.
|
||||
@@ -49,27 +59,30 @@ Install the full plugin including MCP server, lifecycle hooks, and SDK skill.
|
||||
|
||||
### Option B — Direct MCP
|
||||
|
||||
The fastest way to connect Codex to Mem0 — no plugin, no marketplace. Add to `~/.codex/config.toml`:
|
||||
The fastest way to connect Codex to Mem0 — no plugin, no marketplace. Add the MCP server with a single command:
|
||||
|
||||
```bash
|
||||
codex mcp add mem0 --url https://mcp.mem0.ai/mcp/ --bearer-token-env-var MEM0_API_KEY
|
||||
```
|
||||
|
||||
Or add it manually to `~/.codex/config.toml`:
|
||||
|
||||
```toml
|
||||
[mcp_servers.mem0]
|
||||
url = "https://mcp.mem0.ai/mcp"
|
||||
url = "https://mcp.mem0.ai/mcp/"
|
||||
bearer_token_env_var = "MEM0_API_KEY"
|
||||
```
|
||||
|
||||
Make sure `MEM0_API_KEY` is exported in the shell you launch Codex from, then restart Codex.
|
||||
|
||||
<Info>
|
||||
Codex's `codex mcp add` CLI only supports stdio MCP servers. Because Mem0's MCP is HTTP/streamable, you configure it by editing `config.toml` directly (or via the **Plugins → Connect to a custom MCP → Streamable HTTP** UI in the Codex app).
|
||||
</Info>
|
||||
|
||||
This gives you the MCP tools but not the lifecycle hooks or SDK skill.
|
||||
|
||||
### Managing the Plugin
|
||||
|
||||
```bash
|
||||
codex plugin marketplace upgrade # pull latest plugin versions
|
||||
codex plugin marketplace remove mem0-plugins # unregister the marketplace
|
||||
codex plugin remove mem0@mem0-plugins # uninstall the plugin (keeps the marketplace)
|
||||
codex plugin marketplace remove mem0-plugins # unregister the marketplace entirely
|
||||
```
|
||||
|
||||
To update, run `codex plugin marketplace upgrade` to pull the latest from the Mem0 repo.
|
||||
@@ -110,9 +123,10 @@ When installed via the plugin marketplace, Mem0 hooks into Codex's lifecycle to
|
||||
|------|-------|-------------|
|
||||
| **Session start** | `SessionStart` | Loads prior memories and displays status banner |
|
||||
| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message |
|
||||
| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls |
|
||||
| **Pre-tool (3 handlers)** | `PreToolUse` | Blocks MEMORY.md writes; enforces `user_id`/`app_id` on mem0 tool calls; scans files being read for relevant memory context |
|
||||
| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Pre-compact** | `PreCompact` | Stores a session summary before context compaction |
|
||||
| **Stop** | `Stop` | Stores a session summary when the session ends |
|
||||
| **Pre-compact** | `PreCompact` | Stores a summary before the context is compacted |
|
||||
|
||||
## Example Workflow
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ The fastest way to get started. Click the link below to install the Mem0 MCP ser
|
||||
npx mcp-add \
|
||||
--name mem0-mcp \
|
||||
--type http \
|
||||
--url "https://mcp.mem0.ai/mcp" \
|
||||
--url "https://mcp.mem0.ai/mcp/" \
|
||||
--clients "cursor"
|
||||
```
|
||||
|
||||
@@ -108,9 +108,10 @@ When installed via the Cursor Marketplace, Mem0 hooks into Cursor's lifecycle:
|
||||
|------|-------|-------------|
|
||||
| **Session start** | `sessionStart` | Loads prior memories and displays status banner |
|
||||
| **User prompt** | `beforeSubmitPrompt` | Searches relevant memories before each message; skips short prompts |
|
||||
| **Pre-tool (2 handlers)** | `preToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls |
|
||||
| **Pre-tool (3 handlers)** | `preToolUse` | Blocks MEMORY.md writes; enforces `user_id`/`app_id` on mem0 tool calls; scans files being read for relevant memory context |
|
||||
| **Post-tool (2 handlers)** | `postToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Pre-compact** | `preCompact` | Stores a session summary before context compaction |
|
||||
| **Stop** | `stop` | Stores a session summary when the session ends |
|
||||
| **Pre-compact** | `preCompact` | Stores a summary before the context is compacted |
|
||||
|
||||
## Example Workflow
|
||||
|
||||
|
||||
@@ -100,12 +100,12 @@ This section:
|
||||
Initialize both the ElevenLabs and Mem0 clients:
|
||||
|
||||
```python
|
||||
# Initialize ElevenLabs client
|
||||
client = ElevenLabs(api_key=API_KEY)
|
||||
# Initialize ElevenLabs client
|
||||
client = ElevenLabs(api_key=API_KEY)
|
||||
|
||||
# Initialize memory client and tools
|
||||
client_tools = ClientTools()
|
||||
mem0_client = AsyncMemoryClient()
|
||||
# Initialize memory client and tools
|
||||
client_tools = ClientTools()
|
||||
mem0_client = AsyncMemoryClient()
|
||||
```
|
||||
|
||||
Here we:
|
||||
@@ -118,36 +118,36 @@ Here we:
|
||||
Define the two key memory functions that will be registered as tools:
|
||||
|
||||
```python
|
||||
# Define memory-related functions for the agent
|
||||
async def add_memories(parameters):
|
||||
"""Add a message to the memory store"""
|
||||
message = parameters.get("message")
|
||||
await mem0_client.add(
|
||||
messages=message,
|
||||
user_id=USER_ID
|
||||
)
|
||||
return "Memory added successfully"
|
||||
# Define memory-related functions for the agent
|
||||
async def add_memories(parameters):
|
||||
"""Add a message to the memory store"""
|
||||
message = parameters.get("message")
|
||||
await mem0_client.add(
|
||||
messages=message,
|
||||
user_id=USER_ID
|
||||
)
|
||||
return "Memory added successfully"
|
||||
|
||||
async def retrieve_memories(parameters):
|
||||
"""Retrieve relevant memories based on the input message"""
|
||||
message = parameters.get("message")
|
||||
async def retrieve_memories(parameters):
|
||||
"""Retrieve relevant memories based on the input message"""
|
||||
message = parameters.get("message")
|
||||
|
||||
# For Platform API, user_id goes in filters
|
||||
filters = {"user_id": USER_ID}
|
||||
# For Platform API, user_id goes in filters
|
||||
filters = {"user_id": USER_ID}
|
||||
|
||||
# Search for relevant memories using the message as a query
|
||||
results = await mem0_client.search(
|
||||
query=message,
|
||||
filters=filters
|
||||
)
|
||||
# Search for relevant memories using the message as a query
|
||||
results = await mem0_client.search(
|
||||
query=message,
|
||||
filters=filters
|
||||
)
|
||||
|
||||
# Extract and join the memory texts
|
||||
memories = ' '.join([result["memory"] for result in results.get('results', [])])
|
||||
print("[ Memories ]", memories)
|
||||
# Extract and join the memory texts
|
||||
memories = ' '.join([result["memory"] for result in results.get('results', [])])
|
||||
print("[ Memories ]", memories)
|
||||
|
||||
if memories:
|
||||
return memories
|
||||
return "No memories found"
|
||||
if memories:
|
||||
return memories
|
||||
return "No memories found"
|
||||
```
|
||||
|
||||
These functions:
|
||||
@@ -171,9 +171,9 @@ These functions:
|
||||
Register the memory functions with the ElevenLabs ClientTools system:
|
||||
|
||||
```python
|
||||
# Register the memory functions as tools for the agent
|
||||
client_tools.register("addMemories", add_memories, is_async=True)
|
||||
client_tools.register("retrieveMemories", retrieve_memories, is_async=True)
|
||||
# Register the memory functions as tools for the agent
|
||||
client_tools.register("addMemories", add_memories, is_async=True)
|
||||
client_tools.register("retrieveMemories", retrieve_memories, is_async=True)
|
||||
```
|
||||
|
||||
This allows the ElevenLabs agent to:
|
||||
@@ -186,19 +186,19 @@ This allows the ElevenLabs agent to:
|
||||
Configure the conversation with ElevenLabs:
|
||||
|
||||
```python
|
||||
# Initialize the conversation
|
||||
conversation = Conversation(
|
||||
client,
|
||||
AGENT_ID,
|
||||
# Assume auth is required when API_KEY is set
|
||||
requires_auth=bool(API_KEY),
|
||||
audio_interface=DefaultAudioInterface(),
|
||||
client_tools=client_tools,
|
||||
callback_agent_response=lambda response: print(f"Agent: {response}"),
|
||||
callback_agent_response_correction=lambda original, corrected: print(f"Agent: {original} -> {corrected}"),
|
||||
callback_user_transcript=lambda transcript: print(f"User: {transcript}"),
|
||||
# callback_latency_measurement=lambda latency: print(f"Latency: {latency}ms"),
|
||||
)
|
||||
# Initialize the conversation
|
||||
conversation = Conversation(
|
||||
client,
|
||||
AGENT_ID,
|
||||
# Assume auth is required when API_KEY is set
|
||||
requires_auth=bool(API_KEY),
|
||||
audio_interface=DefaultAudioInterface(),
|
||||
client_tools=client_tools,
|
||||
callback_agent_response=lambda response: print(f"Agent: {response}"),
|
||||
callback_agent_response_correction=lambda original, corrected: print(f"Agent: {original} -> {corrected}"),
|
||||
callback_user_transcript=lambda transcript: print(f"User: {transcript}"),
|
||||
# callback_latency_measurement=lambda latency: print(f"Latency: {latency}ms"),
|
||||
)
|
||||
```
|
||||
|
||||
This sets up the conversation with:
|
||||
@@ -217,16 +217,16 @@ This sets up the conversation with:
|
||||
Start and manage the conversation:
|
||||
|
||||
```python
|
||||
# Start the conversation
|
||||
print(f"Starting conversation with user_id: {USER_ID}")
|
||||
conversation.start_session()
|
||||
# Start the conversation
|
||||
print(f"Starting conversation with user_id: {USER_ID}")
|
||||
conversation.start_session()
|
||||
|
||||
# Handle Ctrl+C to gracefully end the session
|
||||
signal.signal(signal.SIGINT, lambda sig, frame: conversation.end_session())
|
||||
# Handle Ctrl+C to gracefully end the session
|
||||
signal.signal(signal.SIGINT, lambda sig, frame: conversation.end_session())
|
||||
|
||||
# Wait for the conversation to end and get the conversation ID
|
||||
conversation_id = conversation.wait_for_session_end()
|
||||
print(f"Conversation ID: {conversation_id}")
|
||||
# Wait for the conversation to end and get the conversation ID
|
||||
conversation_id = conversation.wait_for_session_end()
|
||||
print(f"Conversation ID: {conversation_id}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -445,4 +445,3 @@ By integrating ElevenLabs Conversational AI with Mem0, you can create voice agen
|
||||
Create voice-first AI applications
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
||||
+164
-37
@@ -1,35 +1,42 @@
|
||||
---
|
||||
title: Hermes Agent
|
||||
description: "Add long-term memory to Hermes agents using Mem0 as a pluggable memory provider with automatic background sync and zero-latency prefetch."
|
||||
description: "Add long-term memory to Hermes agents with Mem0, on managed Mem0 Cloud or fully self-hosted (OSS), with automatic background sync and zero-latency prefetch."
|
||||
---
|
||||
|
||||
Add long-term memory to [Hermes Agent](https://github.com/NousResearch/hermes-agent) — a self-improving AI agent CLI by Nous Research. Hermes has a pluggable memory system, and Mem0 is one of the supported providers. Once enabled, Mem0 automatically learns facts from your conversations and surfaces relevant ones before each turn — all without slowing down the chat.
|
||||
Add long-term memory to [Hermes Agent](https://github.com/NousResearch/hermes-agent), a self-improving AI agent CLI by Nous Research. Hermes has a pluggable memory system, and Mem0 is one of the supported providers. Once enabled, Mem0 learns facts from your conversations and surfaces relevant ones before each turn, without slowing down the chat.
|
||||
|
||||
## Overview
|
||||
You can run Mem0 in two ways:
|
||||
|
||||
Hermes runs a built-in memory system (file-based `MEMORY.md` and `USER.md`) alongside one external provider. When Mem0 is active, it works additively with the built-in system at three key moments in every conversation turn:
|
||||
- **Platform mode** (default): managed Mem0 Cloud. Add your API key and you are ready.
|
||||
- **OSS mode**: fully self-hosted with your own LLM, embedder, and vector store. No data leaves your machine.
|
||||
|
||||
### 1. Before the Agent Responds (Prefetch)
|
||||
## How It Works
|
||||
|
||||
When you send a message, Hermes checks if it already has cached Mem0 search results from the previous turn. If so, those memories are injected into the system prompt so the LLM can see them. This is **zero-latency** — no waiting for an API call.
|
||||
Hermes runs a built-in memory system (file-based `MEMORY.md` and `USER.md`) alongside one external provider. When Mem0 is active, it works additively with the built-in system at three points in every conversation turn.
|
||||
|
||||
### 2. After the Agent Responds (Sync)
|
||||
### 1. Before the agent responds (prefetch)
|
||||
|
||||
Once the LLM finishes responding, Hermes sends the `(user message, assistant response)` pair to Mem0's API in a **background thread**. Mem0's server-side LLM automatically extracts facts (e.g., "user prefers Python", "user works at Acme Corp") — you don't have to tell it what to remember.
|
||||
When you send a message, Hermes checks for cached Mem0 search results from the previous turn. If they exist, those memories are injected into the system prompt so the model can see them. This is zero-latency, with no waiting on an API call.
|
||||
|
||||
### 3. Background Prefetch for Next Turn
|
||||
### 2. After the agent responds (sync)
|
||||
|
||||
At the same time as sync, Hermes kicks off a background search on Mem0 to pre-load relevant memories for the next turn. By the time you type your next message, the memories are already cached.
|
||||
Once the model finishes, Hermes sends the `(user message, assistant response)` pair to Mem0 in a background thread. Mem0 extracts facts automatically (for example, "user prefers Python" or "user works at Acme Corp"), so you never have to tell it what to remember. Each write is tagged with the gateway channel it came from.
|
||||
|
||||
### 3. Background prefetch for the next turn
|
||||
|
||||
At the same time, Hermes runs a background search to pre-load relevant memories for your next message. By the time you type, the results are already cached.
|
||||
|
||||
## Agent Tools
|
||||
|
||||
When Mem0 is active, the LLM gets three extra tools it can call during conversations:
|
||||
When Mem0 is active, the model gets five tools it can call during a conversation:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `mem0_profile` | Fetch all stored memories about the user |
|
||||
| `mem0_search` | Semantic search through memories (supports optional reranking via `rerank` and `top_k` parameters) |
|
||||
| `mem0_conclude` | Store a specific fact verbatim — uses `infer=False` so no server-side LLM extraction happens |
|
||||
| Tool | Description | Parameters |
|
||||
|------|-------------|------------|
|
||||
| `mem0_list` | List all stored memories, for a full overview | `page`, `page_size` (default 100, max 200) |
|
||||
| `mem0_search` | Semantic search by meaning, ranked by relevance | `query` (required), `top_k` (default 10, max 50), `rerank` (default `true`, Platform mode only) |
|
||||
| `mem0_add` | Store a fact verbatim, with no LLM extraction | `content` (required) |
|
||||
| `mem0_update` | Update a memory's text by ID | `memory_id`, `text` (both required) |
|
||||
| `mem0_delete` | Delete a memory by ID | `memory_id` (required) |
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -40,17 +47,19 @@ curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scri
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
The `mem0ai` Python package is automatically installed when you enable the Mem0 provider — no manual pip install needed.
|
||||
The `mem0ai` package is installed automatically when you enable the Mem0 provider, so there is no manual pip step. OSS providers may need extra packages (for example `qdrant-client`, `psycopg2-binary`, or `ollama`), which the setup flow installs for you when you pick them.
|
||||
|
||||
## Setup
|
||||
## Platform Setup
|
||||
|
||||
### Option 1: Interactive Setup Wizard (Recommended)
|
||||
Platform mode uses managed Mem0 Cloud and is the fastest way to start.
|
||||
|
||||
### Option 1: Interactive wizard (recommended)
|
||||
|
||||
```bash
|
||||
hermes memory setup
|
||||
```
|
||||
|
||||
Select **mem0** as the provider and enter your Mem0 API key when prompted. The wizard writes your config to `~/.hermes/mem0.json`.
|
||||
Select **mem0**, choose **Platform**, and paste your API key when prompted. The wizard writes the non-secret settings to `~/.hermes/mem0.json` and keeps the key in `~/.hermes/.env`.
|
||||
|
||||
<Note>Get your API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-hermes" rel="nofollow">app.mem0.ai</a>.</Note>
|
||||
|
||||
@@ -68,33 +77,151 @@ memory:
|
||||
provider: mem0
|
||||
```
|
||||
|
||||
That's it — Mem0 runs automatically from this point.
|
||||
That's it. Mem0 runs automatically from here.
|
||||
|
||||
## Configuration Options
|
||||
## OSS (Self-Hosted) Setup
|
||||
|
||||
Configuration is stored in `~/.hermes/mem0.json`. Values can also be set via environment variables.
|
||||
OSS mode runs Mem0 entirely on your own infrastructure: your LLM, your embedder, and your vector store. No data is sent to Mem0 Cloud, and no Mem0 API key is required.
|
||||
|
||||
| Key | Env Variable | Default | Description |
|
||||
|-----|-------------|---------|-------------|
|
||||
| `api_key` | `MEM0_API_KEY` | — | **Required.** Mem0 Platform API key |
|
||||
| `user_id` | `MEM0_USER_ID` | `hermes-user` | User identifier for scoping memories |
|
||||
| `agent_id` | `MEM0_AGENT_ID` | `hermes` | Agent identifier |
|
||||
| `rerank` | — | `true` | Enable reranking for memory recall |
|
||||
### Interactive
|
||||
|
||||
```bash
|
||||
hermes memory setup
|
||||
# Select "mem0", then "Open Source (self-hosted)"
|
||||
# Follow the prompts for LLM, embedder, and vector store
|
||||
```
|
||||
|
||||
### With flags
|
||||
|
||||
```bash
|
||||
hermes memory setup mem0 --mode oss \
|
||||
--oss-llm openai --oss-llm-key sk-... \
|
||||
--oss-vector qdrant
|
||||
```
|
||||
|
||||
### Supported providers
|
||||
|
||||
| Component | Providers |
|
||||
|-----------|-----------|
|
||||
| LLM | `openai` (default model `gpt-5-mini`), `ollama` (local, default `llama3.1:8b`) |
|
||||
| Embedder | `openai` (default `text-embedding-3-small`), `ollama` (local, default `nomic-embed-text`) |
|
||||
| Vector store | `qdrant` (local path or server), `pgvector` |
|
||||
|
||||
### Flag reference
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--mode` | `platform` or `oss` |
|
||||
| `--oss-llm` | LLM provider (`openai` or `ollama`, default `openai`) |
|
||||
| `--oss-llm-key` | LLM API key (for `openai`) |
|
||||
| `--oss-llm-model` | Override the LLM model |
|
||||
| `--oss-llm-url` | LLM base URL (for `ollama` or a custom endpoint) |
|
||||
| `--oss-embedder` | Embedder provider (default `openai`) |
|
||||
| `--oss-embedder-key` | Embedder API key |
|
||||
| `--oss-vector` | Vector store (`qdrant` or `pgvector`, default `qdrant`) |
|
||||
| `--oss-vector-path` | Local Qdrant storage path |
|
||||
| `--oss-vector-host`, `--oss-vector-port` | PGVector or remote Qdrant host and port |
|
||||
| `--oss-vector-user`, `--oss-vector-password`, `--oss-vector-dbname` | PGVector connection details |
|
||||
| `--user-id` | Canonical user identifier |
|
||||
| `--dry-run` | Preview the resolved config without writing it |
|
||||
|
||||
## Switching Modes
|
||||
|
||||
You can move between Platform and OSS at any time. Run the setup command again, or edit `~/.hermes/mem0.json` directly.
|
||||
|
||||
```bash
|
||||
# Platform to OSS
|
||||
hermes memory setup mem0 --mode oss --oss-llm-key sk-...
|
||||
|
||||
# OSS to Platform
|
||||
hermes memory setup mem0 --mode platform --api-key sk-...
|
||||
|
||||
# Preview without writing anything
|
||||
hermes memory setup mem0 --mode oss --oss-llm-key sk-... --dry-run
|
||||
```
|
||||
|
||||
A self-hosted `~/.hermes/mem0.json` looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "oss",
|
||||
"oss": {
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-5-mini"}},
|
||||
"embedder": {"provider": "openai", "config": {"model": "text-embedding-3-small"}},
|
||||
"vector_store": {"provider": "qdrant", "config": {"path": "~/.hermes/mem0_qdrant"}}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Behavioral settings live in `~/.hermes/mem0.json` and are written for you by `hermes memory setup`. Only the secret `MEM0_API_KEY` belongs in `~/.hermes/.env`.
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `mode` | `platform` | `platform` (Mem0 Cloud) or `oss` (self-hosted) |
|
||||
| `api_key` | none | Mem0 Platform API key, required in Platform mode. Stored in `.env` as `MEM0_API_KEY` |
|
||||
| `user_id` | `hermes-user` | Identifier that scopes memories. See cross-channel behavior below |
|
||||
| `agent_id` | `hermes` | Agent identifier attached to writes |
|
||||
| `rerank` | `true` | Rerank search results for relevance (Platform mode only) |
|
||||
|
||||
### Cross-channel memories
|
||||
|
||||
Hermes can run from the CLI and from gateways like Telegram, Slack, and Discord. The `user_id` setting controls how memories are scoped across them:
|
||||
|
||||
- **Set a `user_id`** and it applies to every gateway, so one person gets a single merged memory store no matter where they talk to the agent.
|
||||
- **Leave it unset** (or at the default `hermes-user`) and each gateway uses its own native id, keeping per-platform memories separate.
|
||||
|
||||
Either way, every write is tagged with `metadata.channel` (for example `telegram` or `cli`), so per-channel views are still possible at query time.
|
||||
|
||||
|
||||
## Reliability
|
||||
|
||||
- **Circuit Breaker** — If Mem0's API fails 5 times in a row, Hermes stops calling it for 2 minutes, then retries. The agent keeps working fine without memory during that time.
|
||||
- **Non-blocking** — All Mem0 API calls happen in background daemon threads. A slow or failed API call never blocks your conversation.
|
||||
- **Thread-safe** — The Mem0 client uses lazy initialization with locking, safe for concurrent access.
|
||||
- **Circuit breaker**: if Mem0 fails five times in a row, Hermes pauses calls for two minutes, then retries. The agent keeps working without memory during that window. Expected client errors, like a 404 on a missing memory id, do not count toward tripping the breaker.
|
||||
- **Non-blocking**: every Mem0 call runs in a background daemon thread, so a slow or failed call never blocks your conversation.
|
||||
- **Thread-safe**: the client uses lazy initialization with locking, and the background sync and prefetch threads are guarded so concurrent gateway messages cannot produce duplicate memories.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Mem0 temporarily unavailable"
|
||||
|
||||
The circuit breaker tripped after five consecutive failures and resets after two minutes.
|
||||
|
||||
- **Platform mode**: check your API key and internet connection.
|
||||
- **OSS mode**: make sure your vector store (Qdrant or PGVector) is running and reachable.
|
||||
|
||||
### OSS: vector store connection refused
|
||||
|
||||
```bash
|
||||
# Local Qdrant: confirm the storage path is writable
|
||||
ls -la ~/.hermes/mem0_qdrant
|
||||
|
||||
# Qdrant server: confirm it is reachable
|
||||
curl http://localhost:6333/healthz
|
||||
|
||||
# PGVector: confirm PostgreSQL is accepting connections
|
||||
pg_isready -h localhost -p 5432
|
||||
```
|
||||
|
||||
### OSS: Ollama not reachable
|
||||
|
||||
```bash
|
||||
curl http://localhost:11434/api/tags
|
||||
```
|
||||
|
||||
### Memories not appearing
|
||||
|
||||
- `mem0_add` stores text verbatim with no extraction. Ordinary conversation turns are extracted automatically by the background sync.
|
||||
- Search is semantic, so try a broader query.
|
||||
- Confirm `user_id` is the same across sessions (check `~/.hermes/mem0.json`).
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Zero-Latency Recall** — Memories are prefetched in the background and cached, ready before you type
|
||||
2. **Server-side Extraction** — Mem0's API automatically extracts and deduplicates facts from each exchange
|
||||
3. **Non-blocking** — All API calls run in background daemon threads
|
||||
4. **Fault Tolerant** — Circuit breaker ensures the agent works even if Mem0 is temporarily unreachable
|
||||
5. **Additive Memory** — Works alongside Hermes' built-in file-based memory system (MEMORY.md, USER.md)
|
||||
1. **Two ways to run**: managed Platform or fully self-hosted OSS, switchable at any time.
|
||||
2. **Zero-latency recall**: memories are prefetched in the background and cached before you type.
|
||||
3. **Automatic extraction**: Mem0 extracts and deduplicates facts from each exchange for you.
|
||||
4. **Non-blocking and fault tolerant**: background threads plus a circuit breaker keep the agent responsive even when Mem0 is unreachable.
|
||||
5. **Additive memory**: works alongside Hermes' built-in file memory (`MEMORY.md`, `USER.md`).
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="OpenClaw Integration" icon={<svg width="24" height="24" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="m153.5 173.5q24.62 1.46 46 13.5 12.11 8.1 17.5 21.5 0.74 2.45 0.5 5 0.09 0.81 1 1 1.48-4.9 1-10 5.04 10.48 1.5 22-9.81 27.86-35.5 42.5-26.17 14.97-56 19.5-2.77-0.4-2 1 2.86 1.27 6 1 25.64 1.53 48.5-10 0.34 10.08 2 20 1.08 5.76 5 10 1 1.5 0 3-31.11 20.84-68.5 17.5-23.7-5.7-32.5-28.5-4.39-9.18-3.5-19 15.41 6.23 32 4.5-20.68-6.39-39-18-34.81-27.22-12.5-65.5 11.84-14.83 29-23 4.21 7.66 11.5 12.5 3 1 6 0-26.04-34.62-29-78-0.13-8.46 2-16.5 1 6.5 2 13 3.43 39.53 24.5 73 2.03 2.28 4.5 4 0.5-1.25 1-2.5-1.27-6.54-5-12 0.5-0.75 1-1.5 9.72-3.43 20-4 0.55 10.34 8 17.5 1.94 0.74 4 0.5-17.8-64.6 16.5-122 0.98-1.79 1.5 0-28.21 56.64-13.5 118 1.08 1.43 2.5 0.5 2.21-4.98 2-10.5z" fill="currentColor"/><path fill-rule="evenodd" d="m454.5 97.5q-1.33 11.18-8.5 20-21.81 26.28-55.5 32-1.11-0.2-2 0.5 2.31 2.82 5.5 4.5 1 2 0 4-9.56 11.3-19.5 20 19.71-8.72 31-27 2.68-0.43 5 1-14.24 30.97-48 36.5-9.93 1.71-20 1.5-6.8-0.48-13 1 5.81 6.92 14 11-10.78 16.03-27 26.5 27.16-7.4 38-33.5 4.34 1.35 9 1-9.08 23.84-33 33.5-18.45 6.41-38 7 22.59 8.92 45-1 12.05-5.52 24-11 9.01-1.79 17 2.5 5.28-4.38 11-8 12.8-6.07 27-5 0 0.5 0 1-19.34 2.69-34 15.5 0.5 0.25 1 0.5 17.79-8.09 36-15 2.71-0.79 5-2 2.5-1 5-2 5.53-4.04 11-8 11.7-4.18 24-6.5 7.78-1.36 15 1.5-2.97 18.45-13.5 34-34.92 49.37-94.5 62.5-59.27 12.45-108-23-15.53-12.52-21.5-31.5-2.47-14.26 4-27-3.15 24.41 14 42-4.92-10.28-7-22-1.97-17.63 7-33 47.28-69.5 125.5-100 15.86-3.42 32-5.5 18.63-1.47 37 1.5z" fill="currentColor"/><path fill-rule="evenodd" d="m231.5 238.5q1.31-0.2 2 1-3.13 28.62 15 51-16.25 6.75-27-7.5-1-1-2 0 14.73 29.34 46 18.5 1.79 0.52 0 1.5-37.63 16.82-50.5-22.5-5.1-26.48 16.5-42z" fill="currentColor"/><path fill-rule="evenodd" d="m203.5 266.5q1.31-0.2 2 1-2.48 22.08 12 39-6.99 1.35-14 0.5 4.59 4.08 10 7-8.71 0.28-14.5-6.5-16.98-22.76 4.5-41z" fill="currentColor"/><path fill-rule="evenodd" d="m58.5 284.5q9.6-2.17 14.5 6 5.15 14.18-1 28-11.05-13.14-27.5-17.5 5.15-9.9 14-16.5z" fill="currentColor"/><path fill-rule="evenodd" d="m56.5 313.5q3.43 5.43 8 10-4.88 0.44-8 4-1.11-0.2-2 0.5 28.91 1.65 38 28.5 0.45 3.16-1 6-11.02-7.01-23-12.5-4.75-3.75-9.5-7.5 1.47 7.42 7 13 8.34 27.18 32 43 0.99 2.41-1.5 3.5-40.25 5.58-66.5-25.5-15.67-22.01-8-48 10.46-23.87 34.5-15z" fill="currentColor"/><path fill-rule="evenodd" d="m198.5 319.5q1.44 0.68 2.5 2 2.41 8.23 6 16 1.2 2.64-0.5 5-30.65 21.41-68 18.5-25.16-6.17-32.5-30.5 6.96 4.99 15.5 6.5 8.99 0.75 18 0.5 16.25 2.38 32-2.5 15.9-3.94 27-15.5z" fill="currentColor"/><path fill-rule="evenodd" d="m239.5 342.5q7.02-0.25 14 0.5 4.46 1.06 8 3.5-5.2 2.35-10 5.5-3.88 4.65-9 7.5-9.89-3.09-9.5-13 2.36-3.63 6.5-4z" fill="currentColor"/><path fill-rule="evenodd" d="m214.5 349.5q5.96 7.2 13.5 13 1 1 0 2-28.58 23.34-65.5 20.5-18.15-4.24-27.5-19.5 1.13 0.94 2.5 1.5 14.7 1.42 29-1.5 26.57-0.52 48-16z" fill="currentColor"/><path fill-rule="evenodd" d="m302.5 373.5q0.21 2.44-2 3.5-28.69 7.6-50.5-12.5-0.06-6.71 6.5-9 4.45-0.75 9-1 22.26 2.27 37 19z" fill="currentColor"/><path fill-rule="evenodd" d="m232.5 365.5q17.6 6.19 10.5 23-10.6 10.42-25.5 11.5-25.94 3.21-49-9 36.75-1.65 64-25.5z" fill="currentColor"/><path fill-rule="evenodd" d="m113.5 367.5q7.7-0.01 9.5 7-9.69 7.19-18.5 15.5-7.23 5.76-5.5-3.5 3.12-12.84 14.5-19z" fill="currentColor"/><path fill-rule="evenodd" d="m126.5 380.5q7.88-0.4 12 6.5-8.5 7.25-17 14.5-5.62-12.55 5-21z" fill="currentColor"/><path fill-rule="evenodd" d="m283.5 385.5q3.22 2.95 7 5.5 2.8 4.03 6 7.5 0.42 2.77-2 4-15.5-9.75-31-19.5-1.79-0.98 0-1.5 9.96 2.49 20 4z" fill="currentColor"/></svg>} href="/integrations/openclaw">
|
||||
|
||||
@@ -98,20 +98,9 @@ add_result = add_tool.invoke(add_input)
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"memory": "Name is Alex",
|
||||
"event": "ADD"
|
||||
},
|
||||
{
|
||||
"memory": "Is a vegetarian",
|
||||
"event": "ADD"
|
||||
},
|
||||
{
|
||||
"memory": "Is allergic to nuts",
|
||||
"event": "ADD"
|
||||
}
|
||||
]
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "3a1b2c3d-4e5f-6789-abcd-ef0123456789"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -173,23 +162,25 @@ result = search_tool.invoke(search_input)
|
||||
```
|
||||
|
||||
```json Output
|
||||
[
|
||||
{
|
||||
"id": "1a75e827-7eca-45ea-8c5c-cfd43299f061",
|
||||
"memory": "Name is Alex",
|
||||
"user_id": "alex",
|
||||
"hash": "d0fccc8fa47f7a149ee95750c37bb0ca",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
},
|
||||
"categories": [
|
||||
"personal_details"
|
||||
],
|
||||
"created_at": "2024-11-27T16:53:43.276872-08:00",
|
||||
"updated_at": "2024-11-27T16:53:43.276885-08:00",
|
||||
"score": 0.3810526501504994
|
||||
}
|
||||
]
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "1a75e827-7eca-45ea-8c5c-cfd43299f061",
|
||||
"memory": "Name is Alex",
|
||||
"user_id": "alex",
|
||||
"hash": "d0fccc8fa47f7a149ee95750c37bb0ca",
|
||||
"metadata": {
|
||||
"food": "vegan"
|
||||
},
|
||||
"categories": [
|
||||
"personal_details"
|
||||
],
|
||||
"created_at": "2024-11-27T16:53:43.276872-08:00",
|
||||
"updated_at": "2024-11-27T16:53:43.276885-08:00",
|
||||
"score": 0.3810526501504994
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ load_dotenv()
|
||||
# MEM0_API_KEY = 'your-mem0-key' # Replace with your actual Mem0 API key
|
||||
|
||||
# Initialize LangChain and Mem0
|
||||
llm = ChatOpenAI(model="gpt-4")
|
||||
llm = ChatOpenAI(model="gpt-5-mini")
|
||||
mem0 = MemoryClient()
|
||||
```
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ async def websocket_endpoint(websocket: WebSocket):
|
||||
# LLM for response generation
|
||||
llm = OpenAILLMService(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
model="gpt-3.5-turbo",
|
||||
model="gpt-5-mini",
|
||||
system_prompt="You are a helpful assistant that remembers past conversations."
|
||||
)
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
---
|
||||
title: Keywords AI
|
||||
description: "Combine Mem0 persistent memory with Keywords AI observability for tracked, cost-optimized AI applications."
|
||||
title: Respan
|
||||
description: "Combine Mem0 persistent memory with Respan observability for tracked, cost-optimized AI applications."
|
||||
---
|
||||
|
||||
Build AI applications with persistent memory and comprehensive LLM observability by integrating Mem0 with Keywords AI.
|
||||
Build AI applications with persistent memory and comprehensive LLM observability by integrating Mem0 with Respan.
|
||||
|
||||
## Overview
|
||||
|
||||
Mem0 is a self-improving memory layer for LLM applications, enabling personalized AI experiences that save costs and delight users. Keywords AI provides complete LLM observability.
|
||||
Mem0 is a self-improving memory layer for LLM applications, enabling personalized AI experiences that save costs and delight users. Respan (formerly Keywords AI) provides complete LLM observability.
|
||||
|
||||
Combining Mem0 with Keywords AI allows you to:
|
||||
Combining Mem0 with Respan allows you to:
|
||||
1. Add persistent memory to your AI applications
|
||||
2. Track interactions across sessions
|
||||
3. Monitor memory usage and retrieval with Keywords AI observability
|
||||
3. Monitor memory usage and retrieval with Respan observability
|
||||
4. Optimize token usage and reduce costs
|
||||
|
||||
<Note>
|
||||
You can get your Mem0 API key from the <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=integration-keywords" rel="nofollow">Mem0 dashboard</a>.
|
||||
You can get your Mem0 API key from the <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=integration-respan" rel="nofollow">Mem0 dashboard</a>.
|
||||
</Note>
|
||||
|
||||
## Setup and Configuration
|
||||
@@ -24,7 +24,7 @@ You can get your Mem0 API key from the <a href="https://app.mem0.ai/?utm_source=
|
||||
Install the necessary libraries:
|
||||
|
||||
```bash
|
||||
pip install mem0ai keywordsai-sdk
|
||||
pip install mem0ai openai
|
||||
```
|
||||
|
||||
Set up your environment variables:
|
||||
@@ -34,13 +34,13 @@ import os
|
||||
|
||||
# Set your API keys
|
||||
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
|
||||
os.environ["KEYWORDSAI_API_KEY"] = "your-keywords-api-key"
|
||||
os.environ["KEYWORDSAI_BASE_URL"] = "https://api.keywordsai.co/api/"
|
||||
os.environ["RESPAN_API_KEY"] = "your-respan-api-key"
|
||||
os.environ["RESPAN_BASE_URL"] = "https://api.respan.ai/api/"
|
||||
```
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
Here's a simple example of using Mem0 with Keywords AI:
|
||||
Here's a simple example of using Mem0 with Respan:
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
@@ -48,17 +48,17 @@ import os
|
||||
|
||||
# Configuration
|
||||
api_key = os.getenv("MEM0_API_KEY")
|
||||
keywordsai_api_key = os.getenv("KEYWORDSAI_API_KEY")
|
||||
base_url = os.getenv("KEYWORDSAI_BASE_URL") # "https://api.keywordsai.co/api/"
|
||||
respan_api_key = os.getenv("RESPAN_API_KEY")
|
||||
base_url = os.getenv("RESPAN_BASE_URL") # "https://api.respan.ai/api/"
|
||||
|
||||
# Set up Mem0 with Keywords AI as the LLM provider
|
||||
# Set up Mem0 with Respan as the LLM provider
|
||||
config = {
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-5-mini",
|
||||
"temperature": 0.0,
|
||||
"api_key": keywordsai_api_key,
|
||||
"api_key": respan_api_key,
|
||||
"openai_base_url": base_url,
|
||||
},
|
||||
}
|
||||
@@ -79,7 +79,7 @@ print(result)
|
||||
|
||||
## Advanced Integration with OpenAI SDK
|
||||
|
||||
For more advanced use cases, you can integrate Keywords AI with Mem0 through the OpenAI SDK:
|
||||
For more advanced use cases, you can integrate Respan with Mem0 through the OpenAI SDK:
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
@@ -88,8 +88,8 @@ import json
|
||||
|
||||
# Initialize client
|
||||
client = OpenAI(
|
||||
api_key=os.environ.get("KEYWORDSAI_API_KEY"),
|
||||
base_url=os.environ.get("KEYWORDSAI_BASE_URL"),
|
||||
api_key=os.environ.get("RESPAN_API_KEY"),
|
||||
base_url=os.environ.get("RESPAN_BASE_URL"),
|
||||
)
|
||||
|
||||
# Sample conversation messages
|
||||
@@ -118,18 +118,18 @@ response = client.chat.completions.create(
|
||||
print(json.dumps(response.model_dump(), indent=4))
|
||||
```
|
||||
|
||||
For detailed information on this integration, refer to the official [Keywords AI Mem0 integration documentation](https://docs.keywordsai.co/integration/development-frameworks/mem0).
|
||||
For detailed information on this integration, refer to the official [Respan Mem0 integration documentation](https://www.respan.ai/docs/integrations/mem0).
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Memory Integration**: Store and retrieve relevant information from past interactions
|
||||
2. **LLM Observability**: Track memory usage and retrieval patterns with Keywords AI
|
||||
2. **LLM Observability**: Track memory usage and retrieval patterns with Respan
|
||||
3. **Session Persistence**: Maintain context across multiple user sessions
|
||||
4. **Cost Optimization**: Reduce token usage through efficient memory retrieval
|
||||
|
||||
## Conclusion
|
||||
|
||||
Integrating Mem0 with Keywords AI provides a powerful combination for building AI applications with persistent memory and comprehensive observability. This integration enables more personalized user experiences while providing insights into your application's memory usage.
|
||||
Integrating Mem0 with Respan provides a powerful combination for building AI applications with persistent memory and comprehensive observability. This integration enables more personalized user experiences while providing insights into your application's memory usage.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="OpenAI Agents SDK" icon="cube" href="/integrations/openai-agents-sdk">
|
||||
@@ -139,4 +139,3 @@ Integrating Mem0 with Keywords AI provides a powerful combination for building A
|
||||
Monitor agent performance with AgentOps
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -26,12 +26,12 @@ Install the SDK provider and AI SDK:
|
||||
npm install @mem0/vercel-ai-provider ai@^6
|
||||
```
|
||||
|
||||
### Peer Dependencies
|
||||
### Dependencies
|
||||
|
||||
`@mem0/vercel-ai-provider` v3.0.0 requires:
|
||||
- `ai` v6+ (`^6.0.199`)
|
||||
- `@ai-sdk/provider` v3+ (`^3.0.10`)
|
||||
- Provider packages at v3+: `@ai-sdk/openai@^3`, `@ai-sdk/anthropic@^3`, `@ai-sdk/google@^3`, `@ai-sdk/groq@^3`, `@ai-sdk/cohere@^3`
|
||||
`@mem0/vercel-ai-provider` bundles `ai`, all `@ai-sdk/*` provider packages, and `@ai-sdk/provider` as regular dependencies — you do **not** need to install them separately. The install command above (`npm install @mem0/vercel-ai-provider ai@^6`) is sufficient.
|
||||
|
||||
The only true peer dependency is `zod` (optional):
|
||||
- `zod` v3+ (`^3.0.0`) — required only if you use Zod schemas in tool definitions
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -305,6 +305,8 @@ These options can be passed per-request when creating a model instance:
|
||||
| `rerank` | `boolean` | Enable reranking of results |
|
||||
| `page` | `number` | Page number for pagination |
|
||||
| `page_size` | `number` | Results per page |
|
||||
| `mem0ApiKey` | `string` | Mem0 API key; overrides the `MEM0_API_KEY` env var |
|
||||
| `host` | `string` | Custom Mem0 API base URL for self-hosted deployments |
|
||||
|
||||
## Key Features
|
||||
|
||||
@@ -312,6 +314,7 @@ These options can be passed per-request when creating a model instance:
|
||||
- `retrieveMemories()`: Retrieves memory context for prompts as a formatted system prompt string.
|
||||
- `getMemories()`: Get memories from your profile in array format.
|
||||
- `addMemories()`: Adds user memories to enhance contextual responses.
|
||||
- `searchMemories()`: Searches memories and returns the raw results array (semantic search rather than the full retrieval pipeline).
|
||||
|
||||
## Migrating from v2.x
|
||||
|
||||
|
||||
+8
-4
@@ -286,7 +286,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
- [Dify](https://docs.mem0.ai/integrations/dify) [Both]: Use when the user is on Dify LLMOps.
|
||||
- [Flowise](https://docs.mem0.ai/integrations/flowise) [Both]: Use when the user is on Flowise no-code.
|
||||
- [AgentOps](https://docs.mem0.ai/integrations/agentops) [Both]: Use when tracking agent observability with memory metadata.
|
||||
- [Keywords AI](https://docs.mem0.ai/integrations/keywords) [Both]: Use when monitoring with Keywords AI.
|
||||
- [Respan](https://docs.mem0.ai/integrations/respan) [Both]: Use when monitoring Mem0 with Respan (formerly Keywords AI) LLM observability.
|
||||
- [Raycast](https://docs.mem0.ai/integrations/raycast) [Both]: Use when the user wants quick memory access via Raycast.
|
||||
|
||||
## Cookbooks
|
||||
@@ -367,6 +367,8 @@ All API Reference docs describe Mem0 Platform REST endpoints (requires API key).
|
||||
- [Get Organization](https://docs.mem0.ai/api-reference/organization/get-org) [Platform]: Use when fetching one org.
|
||||
- [Get Organization Members](https://docs.mem0.ai/api-reference/organization/get-org-members) [Platform]: Use when listing org members.
|
||||
- [Add Organization Member](https://docs.mem0.ai/api-reference/organization/add-org-member) [Platform]: Use when inviting a member to an org.
|
||||
- [Update Organization Member](https://docs.mem0.ai/api-reference/organization/update-org-member) [Platform]: Use when updating an org member's role.
|
||||
- [Remove Organization Member](https://docs.mem0.ai/api-reference/organization/remove-org-member) [Platform]: Use when removing a member from an organization.
|
||||
- [Delete Organization](https://docs.mem0.ai/api-reference/organization/delete-org) [Platform]: Use when removing an org.
|
||||
|
||||
### Projects
|
||||
@@ -375,6 +377,9 @@ All API Reference docs describe Mem0 Platform REST endpoints (requires API key).
|
||||
- [Get Project](https://docs.mem0.ai/api-reference/project/get-project) [Platform]: Use when fetching one project.
|
||||
- [Get Project Members](https://docs.mem0.ai/api-reference/project/get-project-members) [Platform]: Use when listing project members.
|
||||
- [Add Project Member](https://docs.mem0.ai/api-reference/project/add-project-member) [Platform]: Use when inviting a member to a project.
|
||||
- [Update Project](https://docs.mem0.ai/api-reference/project/update-project) [Platform]: Use when updating project settings.
|
||||
- [Update Project Member](https://docs.mem0.ai/api-reference/project/update-project-member) [Platform]: Use when updating a project member's role.
|
||||
- [Remove Project Member](https://docs.mem0.ai/api-reference/project/remove-project-member) [Platform]: Use when removing a member from a project.
|
||||
- [Delete Project](https://docs.mem0.ai/api-reference/project/delete-project) [Platform]: Use when removing a project.
|
||||
|
||||
### Webhooks
|
||||
@@ -460,6 +465,7 @@ Everything below is OSS-only provider configuration. Skip this entire section wh
|
||||
- [LM Studio Embeddings](https://docs.mem0.ai/components/embedders/models/lmstudio) [OSS]: Use when embeddings run through LM Studio.
|
||||
- [Together Embeddings](https://docs.mem0.ai/components/embedders/models/together) [OSS]: Use when embeddings run on Together.
|
||||
- [LangChain Embeddings](https://docs.mem0.ai/components/embedders/models/langchain) [OSS]: Use when embeddings are wrapped behind a LangChain adapter.
|
||||
- [FastEmbed](https://docs.mem0.ai/components/embedders/models/fastembed) [OSS]: Use when embeddings run locally via FastEmbed (ONNX).
|
||||
|
||||
### Vector Databases [OSS]
|
||||
- [Vector Database Overview](https://docs.mem0.ai/components/vectordbs/overview) [OSS]: Use when choosing a vector store.
|
||||
@@ -498,7 +504,5 @@ Everything below is OSS-only provider configuration. Skip this entire section wh
|
||||
- [Custom Reranker Prompts](https://docs.mem0.ai/components/rerankers/custom-prompts) [OSS]: Use when rewriting reranker prompts.
|
||||
- [Cohere Reranker](https://docs.mem0.ai/components/rerankers/models/cohere) [OSS]: Use for Cohere Rerank.
|
||||
- [Sentence Transformer Reranker](https://docs.mem0.ai/components/rerankers/models/sentence_transformer) [OSS]: Use for local cross-encoder rerankers.
|
||||
- [Hugging Face Reranker](https://docs.mem0.ai/components/rerankers/models/huggingface) [OSS]: Use for HF-hosted reranker models.
|
||||
- [LLM Reranker (prompt)](https://docs.mem0.ai/components/rerankers/models/llm) [OSS]: Use when the reranker is a prompted LLM (config guide).
|
||||
- [LLM Reranker](https://docs.mem0.ai/components/rerankers/models/llm_reranker) [OSS]: Use when the reranker is a prompted LLM (implementation reference).
|
||||
- [Hugging Face Reranker](https://docs.mem0.ai/components/rerankers/models/huggingface) [OSS]: Use for HF-hosted reranker models.- [LLM Reranker](https://docs.mem0.ai/components/rerankers/models/llm_reranker) [OSS]: Use when the reranker is a prompted LLM (implementation reference).
|
||||
- [Zero Entropy Reranker](https://docs.mem0.ai/components/rerankers/models/zero_entropy) [OSS]: Use for the Zero Entropy reranker.
|
||||
|
||||
@@ -62,7 +62,8 @@ def add(
|
||||
filters: dict = None,
|
||||
output_format: str = None, # ❌ REMOVED
|
||||
version: str = None # ❌ REMOVED
|
||||
) -> Union[List[dict], dict]
|
||||
) -> Union[List[dict], dict]:
|
||||
...
|
||||
```
|
||||
|
||||
#### v1.0.0 Signature
|
||||
@@ -76,7 +77,8 @@ def add(
|
||||
metadata: dict = None,
|
||||
filters: dict = None,
|
||||
infer: bool = True # ✅ NEW: Control memory inference
|
||||
) -> dict # Always returns dict with "results" key
|
||||
) -> dict: # Always returns dict with "results" key
|
||||
...
|
||||
```
|
||||
|
||||
#### Changes Summary
|
||||
@@ -147,7 +149,8 @@ def search(
|
||||
filters: dict = None, # Basic key-value only
|
||||
output_format: str = None, # ❌ REMOVED
|
||||
version: str = None # ❌ REMOVED
|
||||
) -> Union[List[dict], dict]
|
||||
) -> Union[List[dict], dict]:
|
||||
...
|
||||
```
|
||||
|
||||
#### v1.0.0 Signature
|
||||
@@ -161,7 +164,8 @@ def search(
|
||||
limit: int = 100,
|
||||
filters: dict = None, # ✅ ENHANCED: Advanced operators
|
||||
rerank: bool = True # ✅ NEW: Reranking support
|
||||
) -> dict # Always returns dict with "results" key
|
||||
) -> dict: # Always returns dict with "results" key
|
||||
...
|
||||
```
|
||||
|
||||
#### Enhanced Filtering
|
||||
@@ -216,7 +220,8 @@ def get_all(
|
||||
filters: dict = None,
|
||||
output_format: str = None, # ❌ REMOVED
|
||||
version: str = None # ❌ REMOVED
|
||||
) -> Union[List[dict], dict]
|
||||
) -> Union[List[dict], dict]:
|
||||
...
|
||||
```
|
||||
|
||||
#### v1.0.0 Signature
|
||||
@@ -227,7 +232,8 @@ def get_all(
|
||||
agent_id: str = None,
|
||||
run_id: str = None,
|
||||
filters: dict = None # ✅ ENHANCED: Advanced operators
|
||||
) -> dict # Always returns dict with "results" key
|
||||
) -> dict: # Always returns dict with "results" key
|
||||
...
|
||||
```
|
||||
|
||||
### update() Method
|
||||
@@ -239,7 +245,8 @@ def update(
|
||||
self,
|
||||
memory_id: str,
|
||||
data: str
|
||||
) -> dict
|
||||
) -> dict:
|
||||
...
|
||||
```
|
||||
|
||||
### delete() Method
|
||||
@@ -250,7 +257,8 @@ def update(
|
||||
def delete(
|
||||
self,
|
||||
memory_id: str
|
||||
) -> dict
|
||||
) -> dict:
|
||||
...
|
||||
```
|
||||
|
||||
### delete_all() Method
|
||||
@@ -344,7 +352,7 @@ config = {
|
||||
### New Configuration Options
|
||||
|
||||
#### Reranker Configuration
|
||||
```python
|
||||
```text
|
||||
# Cohere reranker
|
||||
"reranker": {
|
||||
"provider": "cohere",
|
||||
@@ -563,4 +571,4 @@ results = m.search(
|
||||
|
||||
<Info>
|
||||
Use this reference to systematically update your codebase. Test each change thoroughly before deploying to production.
|
||||
</Info>
|
||||
</Info>
|
||||
|
||||
@@ -62,7 +62,7 @@ These changes produce a **+20 point improvement on LoCoMo** (71.4 → 91.6) and
|
||||
|---|---|---|---|
|
||||
| Constructor | `MemoryClient(api_key, org_id, project_id)` | `MemoryClient(api_key)` | Remove `org_id`, `project_id` from constructor |
|
||||
| Method options | `client.add(messages, **kwargs)` | `client.add(messages, options=AddMemoryOptions(...))` | Use typed option classes (or `**kwargs` still works) |
|
||||
| Removed params | `api_version`, `output_format`, `async_mode`, `filter_memories`, `expiration_date`, `keyword_search`, `force_add_only`, `batch_size`, `immutable`, `includes`, `excludes`, `enable_graph`, `org_name`, `project_name` | — | Remove from all calls |
|
||||
| Removed params | `api_version`, `output_format`, `async_mode`, `filter_memories`, `keyword_search`, `force_add_only`, `batch_size`, `immutable`, `includes`, `excludes`, `enable_graph`, `org_name`, `project_name` | — | Remove from all calls |
|
||||
|
||||
### TypeScript Client SDK
|
||||
|
||||
@@ -70,7 +70,7 @@ These changes produce a **+20 point improvement on LoCoMo** (71.4 → 91.6) and
|
||||
|---|---|---|---|
|
||||
| Constructor | `new MemoryClient({ apiKey, organizationId, projectId })` | `new MemoryClient({ apiKey })` | Remove `organizationId`, `projectId`, `organizationName`, `projectName` |
|
||||
| All params | snake_case: `user_id`, `agent_id`, `top_k` | camelCase: `userId`, `agentId`, `topK` | Rename all params to camelCase |
|
||||
| Removed params | `api_version`, `output_format`, `async_mode`, `enable_graph`, `org_id`, `project_id`, `org_name`, `project_name`, `filter_memories`, `batch_size`, `force_add_only`, `immutable`, `expiration_date`, `includes`, `excludes`, `keyword_search` | — | Remove from all calls |
|
||||
| Removed params | `api_version`, `output_format`, `async_mode`, `enable_graph`, `org_id`, `project_id`, `org_name`, `project_name`, `filter_memories`, `batch_size`, `force_add_only`, `immutable`, `includes`, `excludes`, `keyword_search` | — | Remove from all calls |
|
||||
| Output format enum | `OutputFormat.V1`, `OutputFormat.V1_1` | Removed | v1.1 is now always used |
|
||||
| API version enum | `API_VERSION.V1`, `API_VERSION.V2` | Removed | Handled internally |
|
||||
|
||||
@@ -423,7 +423,7 @@ These parameters have been removed across all SDKs. Remove them from your code:
|
||||
|
||||
**All methods:** `api_version`, `output_format`, `async_mode`, `org_name`, `project_name`, `org_id`, `project_id`
|
||||
|
||||
**add():** `enable_graph`, `immutable`, `expiration_date`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search`
|
||||
**add():** `enable_graph`, `immutable`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search`
|
||||
|
||||
**search():** `enable_graph`
|
||||
|
||||
@@ -437,7 +437,7 @@ These parameters have been removed across all SDKs. Remove them from your code:
|
||||
|
||||
**All methods:** `OutputFormat` enum, `API_VERSION` enum
|
||||
|
||||
**add():** `enable_graph` / `enableGraph`, `async_mode` / `asyncMode`, `output_format` / `outputFormat`, `immutable`, `expiration_date` / `expirationDate`, `filter_memories` / `filterMemories`, `batch_size` / `batchSize`, `force_add_only` / `forceAddOnly`, `includes`, `excludes`, `keyword_search` / `keywordSearch`
|
||||
**add():** `enable_graph` / `enableGraph`, `async_mode` / `asyncMode`, `output_format` / `outputFormat`, `immutable`, `filter_memories` / `filterMemories`, `batch_size` / `batchSize`, `force_add_only` / `forceAddOnly`, `includes`, `excludes`, `keyword_search` / `keywordSearch`
|
||||
|
||||
**search():** `enable_graph` / `enableGraph`
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ client.add(messages, user_id="alice")
|
||||
# async_mode and output_format removed (async by default, v1.1 always)
|
||||
```
|
||||
|
||||
**Removed parameters:** `org_id`, `project_id`, `api_version`, `output_format`, `async_mode`, `enable_graph`, `immutable`, `expiration_date`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search`, `org_name`, `project_name`
|
||||
**Removed parameters:** `org_id`, `project_id`, `api_version`, `output_format`, `async_mode`, `enable_graph`, `immutable`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search`, `org_name`, `project_name`
|
||||
|
||||
### TypeScript Client SDK
|
||||
|
||||
@@ -242,7 +242,7 @@ await client.search("query", {
|
||||
});
|
||||
```
|
||||
|
||||
**Removed:** `OutputFormat` enum, `API_VERSION` enum, `organizationId`, `projectId`, `organizationName`, `projectName`, `enableGraph`, `asyncMode`, `outputFormat`, `immutable`, `expirationDate`, `filterMemories`, `batchSize`, `forceAddOnly`, `includes`, `excludes`, `keywordSearch`
|
||||
**Removed:** `OutputFormat` enum, `API_VERSION` enum, `organizationId`, `projectId`, `organizationName`, `projectName`, `enableGraph`, `asyncMode`, `outputFormat`, `immutable`, `filterMemories`, `batchSize`, `forceAddOnly`, `includes`, `excludes`, `keywordSearch`
|
||||
|
||||
<Info>
|
||||
For the full list of parameter changes across all SDKs, see the [OSS migration guide](/migration/oss-v2-to-v3#removed-parameters-reference).
|
||||
|
||||
@@ -130,7 +130,7 @@ memory = Memory.from_config_file("config.yaml")
|
||||
</Tabs>
|
||||
|
||||
<Info icon="check">
|
||||
Run `memory.add(["Remember my favorite cafe in Tokyo."], user_id="alex")` and then `memory.search("favorite cafe", filters={"user_id": "alex"})`. You should see the Qdrant collection populate and the reranker mark the memory as a top hit.
|
||||
Run `memory.add("Remember my favorite cafe in Tokyo.", user_id="alex")` and then `memory.search("favorite cafe", filters={"user_id": "alex"})`. You should see the Qdrant collection populate and the reranker mark the memory as a top hit.
|
||||
</Info>
|
||||
|
||||
## Tune component settings
|
||||
|
||||
@@ -18,7 +18,7 @@ icon: "bolt"
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Working in TypeScript? The Node SDK still uses synchronous calls—use `Memory` there and rely on Python’s `AsyncMemory` when you need awaited operations.
|
||||
Working in TypeScript? The OSS `Memory` class in the Node SDK (`mem0ai/oss`) is also fully async — every method returns a `Promise` and must be `await`ed. Python’s `AsyncMemory` serves the same purpose within Python async frameworks like FastAPI. Both runtimes support awaited memory operations; choose the SDK that matches your language.
|
||||
</Note>
|
||||
|
||||
## Feature anatomy
|
||||
|
||||
@@ -165,8 +165,7 @@ await memory.add("Yesterday, I ordered a laptop, the order id is 12345", { userI
|
||||
{"memory": "Ordered a laptop", "event": "ADD"},
|
||||
{"memory": "Order ID: 12345", "event": "ADD"},
|
||||
{"memory": "Order placed yesterday", "event": "ADD"}
|
||||
],
|
||||
"relations": []
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -188,8 +187,7 @@ await memory.add("I like going to hikes", { userId: "user123" });
|
||||
|
||||
```json Output
|
||||
{
|
||||
"results": [],
|
||||
"relations": []
|
||||
"results": []
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -41,6 +41,14 @@ Multimodal support lets Mem0 extract facts from images alongside regular text. A
|
||||
|
||||
## Configure it
|
||||
|
||||
<Warning>
|
||||
You must set `enable_vision: True` in your LLM config for image content to be processed. Without it, image turns are silently dropped and no vision memories are created. Example:
|
||||
```python
|
||||
config = {"llm": {"provider": "openai", "config": {"enable_vision": True, "vision_details": "auto"}}}
|
||||
client = Memory.from_config(config)
|
||||
```
|
||||
</Warning>
|
||||
|
||||
### Add image messages from URLs
|
||||
|
||||
<CodeGroup>
|
||||
@@ -66,7 +74,7 @@ client.add(messages, user_id="alice")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
import { Memory } from "mem0ai";
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const client = new Memory();
|
||||
|
||||
@@ -123,7 +131,7 @@ client.add(messages, user_id="alice")
|
||||
|
||||
```ts TypeScript
|
||||
import fs from "fs";
|
||||
import { Memory } from "mem0ai";
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
function encodeImage(imagePath: string) {
|
||||
const buffer = fs.readFileSync(imagePath);
|
||||
@@ -226,7 +234,7 @@ client.add(messages, user_id="user123")
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from mem0 import Memory
|
||||
from mem0.exceptions import InvalidImageError, FileSizeError
|
||||
from mem0.exceptions import ValidationError
|
||||
|
||||
client = Memory()
|
||||
|
||||
@@ -242,16 +250,14 @@ try:
|
||||
client.add(messages, user_id="user123")
|
||||
print("Image processed successfully")
|
||||
|
||||
except InvalidImageError:
|
||||
print("Invalid image format or corrupted file")
|
||||
except FileSizeError:
|
||||
print("Image file too large")
|
||||
except ValidationError as exc:
|
||||
print(f"Image validation error: {exc}")
|
||||
except Exception as exc:
|
||||
print(f"Unexpected error: {exc}")
|
||||
```
|
||||
|
||||
```ts TypeScript
|
||||
import { Memory } from "mem0ai";
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const client = new Memory();
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-api-key",
|
||||
"top_k": 5
|
||||
}
|
||||
@@ -150,7 +150,7 @@ config = {
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-api-key"
|
||||
}
|
||||
},
|
||||
|
||||
+23
-1
@@ -1779,6 +1779,11 @@
|
||||
"type": "object",
|
||||
"description": "Entity and metadata filters. Must include at least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`).",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"show_expired": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When true, include memories whose `expiration_date` has passed. Expired memories are hidden by default."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1977,6 +1982,12 @@
|
||||
"additionalProperties": true,
|
||||
"description": "User-supplied metadata to attach to each extracted memory."
|
||||
},
|
||||
"expiration_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"nullable": true,
|
||||
"description": "Optional expiration date in YYYY-MM-DD format. After this date, memories are hidden from search and get-all unless `show_expired` is true."
|
||||
},
|
||||
"custom_instructions": {
|
||||
"type": "string",
|
||||
"description": "Project-level instructions that guide extraction for this call."
|
||||
@@ -2094,6 +2105,11 @@
|
||||
"description": "Entity and metadata filters. Must include at least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`). Supports `AND`, `OR`, `NOT`, and comparison operators (`in`, `gte`, `lte`, `gt`, `lt`, `contains`, `icontains`, `ne`).",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"show_expired": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When true, include memories whose `expiration_date` has passed. Expired memories are hidden by default."
|
||||
},
|
||||
"top_k": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
@@ -2432,6 +2448,12 @@
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"description": "Additional metadata associated with the memory"
|
||||
},
|
||||
"expiration_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"nullable": true,
|
||||
"description": "Expiration date in YYYY-MM-DD format, or null to clear the expiration date."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6256,4 +6278,4 @@
|
||||
}
|
||||
},
|
||||
"x-original-swagger-version": "2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ results_without_criteria = client.search(
|
||||
### Compare Results
|
||||
|
||||
### Search Results (with Criteria)
|
||||
```python
|
||||
```text
|
||||
[
|
||||
{"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day", "score": 0.666, ...},
|
||||
{"memory": "User finally has time to draw something after a long time", "score": 0.616, ...},
|
||||
@@ -128,7 +128,7 @@ results_without_criteria = client.search(
|
||||
```
|
||||
|
||||
### Search Results (without Criteria)
|
||||
```python
|
||||
```text
|
||||
[
|
||||
{"memory": "User is happy today", "score": 0.607, ...},
|
||||
{"memory": "User feels refreshed and ready to take on anything on a beautiful sunny day", "score": 0.512, ...},
|
||||
|
||||
@@ -190,7 +190,7 @@ messages = [
|
||||
client.add(messages, user_id='alice')
|
||||
```
|
||||
|
||||
```python Memories with categories
|
||||
```text Memories with categories
|
||||
# Following categories will be created for the memories added
|
||||
Sometimes draws and sketches in free time (hobbies)
|
||||
Is quite athletic (sports)
|
||||
|
||||
@@ -108,10 +108,10 @@ print(response)
|
||||
|
||||
```javascript JavaScript
|
||||
// Basic Export request
|
||||
const filters = {"user_id": "alice"};
|
||||
const basicFilters = {"user_id": "alice"};
|
||||
const response = await client.createMemoryExport({
|
||||
schema: json_schema,
|
||||
filters: filters
|
||||
filters: basicFilters
|
||||
});
|
||||
|
||||
// Export with custom instructions and additional filters
|
||||
@@ -124,16 +124,16 @@ const export_instructions = `
|
||||
`;
|
||||
|
||||
// For create operation, using only user_id filter as requested
|
||||
const filters = {
|
||||
const exportFilters = {
|
||||
"AND": [
|
||||
{"user_id": "alex"},
|
||||
{"created_at": {"gte": "2024-01-01"}}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const responseWithInstructions = await client.createMemoryExport({
|
||||
schema: json_schema,
|
||||
filters: filters,
|
||||
filters: exportFilters,
|
||||
exportInstructions: export_instructions
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"ai": "^4.1.46",
|
||||
"ai": "^5.0.52",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"js-cookie": "^3.0.6",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"ai": "4.1.42",
|
||||
"ai": "^5.0.52",
|
||||
"buffer": "^6.0.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"ai": "4.1.42",
|
||||
"ai": "^5.0.52",
|
||||
"buffer": "^6.0.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
All notable changes to the Mem0 plugin will be documented in this file.
|
||||
|
||||
## 0.2.11 — Session-summary metadata fix + rerank auto-injected context by default
|
||||
|
||||
> Versions: Claude Code / Cursor / Codex `0.2.11`; Antigravity `0.1.3`. All four editors share `scripts/`, so the fix below applies to every editor.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`files_touched` was double-JSON-encoded in session summaries (`scripts/capture_session_summary.py`):** the Stop-hook summary set `metadata["files_touched"] = json.dumps(files[:20])` — a pre-serialized JSON string — and then serialized the whole request body again with `json.dumps(body)`. The stored memory therefore carried an escaped string blob (`"[\"mem0/memory/main.py\", \"src/client/index.ts\"]"`) instead of a real array, so file paths surfaced as backslash- and slash-heavy escaped text when those memories were returned by `search_memories`/`get_memories` and shown in Claude Code, Cursor, Codex, and Antigravity. The fix stores the list directly (`metadata["files_touched"] = files[:20]`) so the body is encoded exactly once. New `tests/test_capture_session_summary.py` asserts the posted body contains a JSON array and no escaped-string artifact.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Auto-injected memory context is now reranked by default (`scripts/_search.py`, `scripts/file_context.py`, `scripts/on_bash_output.sh`, `scripts/on_user_prompt.sh`):** the REST search endpoint does not rerank when `rerank` is omitted, so hook-injected context (file-context, bash-error lookup, session-resume prefetch) was ordered by raw vector similarity and the single most relevant memory could fall outside the injected `top_k` window. A new `should_rerank()` helper turns reranking on for every auto-injection path; the extra ~150–200 ms stays within the hook's curl budget. Opt out with `MEM0_RERANK=0` (also accepts `false`/`no`/`off`). (#5690)
|
||||
|
||||
## 0.2.10 — Accurate per-editor telemetry attribution
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "mem0",
|
||||
"name": "mem0",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.",
|
||||
"author": { "name": "Mem0", "email": "support@mem0.ai" },
|
||||
"publisher": "mem0ai",
|
||||
|
||||
@@ -7,12 +7,31 @@ All pre-fetch hooks use this instead of duplicating urllib boilerplate.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
SEARCH_URL = "https://api.mem0.ai/v3/memories/search/"
|
||||
SEARCH_TIMEOUT = 5
|
||||
|
||||
|
||||
def should_rerank() -> bool:
|
||||
"""Whether auto-injection searches should request Platform reranking.
|
||||
|
||||
The REST search endpoint does not rerank when ``rerank`` is omitted, so
|
||||
auto-injected context is ordered by raw vector similarity and the single
|
||||
most relevant memory can fall outside the injected top_k window. We default
|
||||
reranking ON for the hook-driven injection path (the extra ~150-200ms is
|
||||
well within the hook's curl budget) and let users opt out via MEM0_RERANK.
|
||||
|
||||
MEM0_RERANK is read case-insensitively; ``0``, ``false``, ``no``, and
|
||||
``off`` disable reranking. Anything else (including unset) enables it.
|
||||
"""
|
||||
raw = os.environ.get("MEM0_RERANK")
|
||||
if raw is None:
|
||||
return True
|
||||
return raw.strip().lower() not in ("0", "false", "no", "off", "")
|
||||
|
||||
|
||||
def _do_search(api_key: str, payload: dict) -> list[dict]:
|
||||
body = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
|
||||
@@ -173,7 +173,7 @@ def store_summary(
|
||||
if branch:
|
||||
metadata["branch"] = branch
|
||||
if files:
|
||||
metadata["files_touched"] = json.dumps(files[:20])
|
||||
metadata["files_touched"] = files[:20]
|
||||
|
||||
body = {
|
||||
"messages": [{"role": "user", "content": summary_prompt}],
|
||||
|
||||
@@ -23,7 +23,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _formatting import TYPE_ICONS, format_age
|
||||
from _identity import resolve_api_key, resolve_user_id
|
||||
from _project import resolve_project_id
|
||||
from _search import search_memories
|
||||
from _search import search_memories, should_rerank
|
||||
|
||||
FILE_READ_GATE_MIN_BYTES = 1500
|
||||
MAX_RESULTS = 5
|
||||
@@ -93,6 +93,7 @@ def search_file_context(
|
||||
api_key, user_id, project_id, query,
|
||||
top_k=MAX_RESULTS, threshold=0.3,
|
||||
global_search=global_search,
|
||||
rerank=should_rerank(),
|
||||
)
|
||||
|
||||
results = results[:MAX_RESULTS]
|
||||
|
||||
@@ -77,15 +77,16 @@ RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_QUERY="$ERROR_QUERY" MEM0_SEARCH_
|
||||
python3 -c "
|
||||
import os, sys
|
||||
sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
|
||||
from _search import search_memories, format_results_for_context
|
||||
from _search import search_memories, format_results_for_context, should_rerank
|
||||
|
||||
api_key = os.environ.get('MEM0_API_KEY', '')
|
||||
user_id = os.environ.get('MEM0_SEARCH_USER', 'default')
|
||||
project_id = os.environ.get('MEM0_PROJECT_ID', 'unknown')
|
||||
query = os.environ.get('MEM0_SEARCH_QUERY', '')
|
||||
rerank = should_rerank()
|
||||
|
||||
r1 = search_memories(api_key, user_id, project_id, query, metadata_type='anti_pattern', top_k=3)
|
||||
r2 = search_memories(api_key, user_id, project_id, query, metadata_type='bug_fix', top_k=3)
|
||||
r1 = search_memories(api_key, user_id, project_id, query, metadata_type='anti_pattern', top_k=3, rerank=rerank)
|
||||
r2 = search_memories(api_key, user_id, project_id, query, metadata_type='bug_fix', top_k=3, rerank=rerank)
|
||||
|
||||
seen = set()
|
||||
combined = []
|
||||
|
||||
@@ -120,14 +120,15 @@ if [ -n "$HAS_RESUME" ]; then
|
||||
RESUME_RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_USER="$USER_ID" python3 -c "
|
||||
import os, sys
|
||||
sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
|
||||
from _search import search_memories, format_results_for_context
|
||||
from _search import search_memories, format_results_for_context, should_rerank
|
||||
|
||||
api_key = os.environ.get('MEM0_API_KEY', '')
|
||||
user_id = os.environ.get('MEM0_SEARCH_USER', 'default')
|
||||
project_id = os.environ.get('MEM0_PROJECT_ID', 'unknown')
|
||||
rerank = should_rerank()
|
||||
|
||||
state = search_memories(api_key, user_id, project_id, 'session state current task', metadata_type='session_state', top_k=3)
|
||||
decisions = search_memories(api_key, user_id, project_id, 'recent decisions and learnings', metadata_type='decision', top_k=3)
|
||||
state = search_memories(api_key, user_id, project_id, 'session state current task', metadata_type='session_state', top_k=3, rerank=rerank)
|
||||
decisions = search_memories(api_key, user_id, project_id, 'recent decisions and learnings', metadata_type='decision', top_k=3, rerank=rerank)
|
||||
|
||||
all_r = state + decisions
|
||||
seen = set()
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Regression tests for capture_session_summary.py request body construction.
|
||||
|
||||
Guards against the double-JSON-encoding bug where ``files_touched`` was stored
|
||||
as a pre-serialized JSON string and then encoded a second time with the rest of
|
||||
the request body — surfacing as escaped, slash-heavy blobs in the memories shown
|
||||
inside Claude Code / Cursor / Codex / Antigravity (all four editors share this
|
||||
script).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class _FakeResp:
|
||||
status = 200
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_):
|
||||
return False
|
||||
|
||||
|
||||
def _capture_request_body(monkeypatch):
|
||||
"""Patch urlopen so store_summary posts nowhere; capture the request body."""
|
||||
import capture_session_summary as css
|
||||
|
||||
captured: dict = {}
|
||||
|
||||
def fake_urlopen(req, timeout=0):
|
||||
captured["raw"] = req.data.decode("utf-8")
|
||||
captured["body"] = json.loads(captured["raw"])
|
||||
return _FakeResp()
|
||||
|
||||
monkeypatch.setattr(css.urllib.request, "urlopen", fake_urlopen)
|
||||
return captured, css
|
||||
|
||||
|
||||
def test_files_touched_is_json_array_not_double_encoded(monkeypatch):
|
||||
"""files_touched must be a real JSON array, encoded exactly once."""
|
||||
captured, css = _capture_request_body(monkeypatch)
|
||||
files = ["mem0/memory/main.py", "src/client/index.ts"]
|
||||
|
||||
css.store_summary(
|
||||
api_key="test-key",
|
||||
summary_prompt="did some work",
|
||||
user_id="u1",
|
||||
session_id="s1",
|
||||
project_id="p1",
|
||||
branch="main",
|
||||
files=files,
|
||||
)
|
||||
|
||||
files_touched = captured["body"]["metadata"]["files_touched"]
|
||||
assert isinstance(files_touched, list), (
|
||||
"files_touched must be a JSON array, not a double-encoded string; "
|
||||
f"got {type(files_touched).__name__}: {files_touched!r}"
|
||||
)
|
||||
assert files_touched == files
|
||||
# The file paths must not appear as an escaped JSON string inside the body.
|
||||
assert '\\"' not in captured["raw"]
|
||||
|
||||
|
||||
def test_files_touched_omitted_when_no_files(monkeypatch):
|
||||
"""No files touched -> no files_touched key (unchanged behaviour)."""
|
||||
captured, css = _capture_request_body(monkeypatch)
|
||||
|
||||
css.store_summary(
|
||||
api_key="test-key",
|
||||
summary_prompt="did some work",
|
||||
user_id="u1",
|
||||
session_id="s1",
|
||||
project_id="p1",
|
||||
branch="main",
|
||||
files=[],
|
||||
)
|
||||
|
||||
assert "files_touched" not in captured["body"]["metadata"]
|
||||
@@ -101,6 +101,67 @@ def test_search_memories_no_api_key_returns_empty():
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_search_memories_omits_rerank_by_default():
|
||||
"""Regression for #5684: rerank must not be sent unless requested."""
|
||||
from _search import search_memories
|
||||
|
||||
captured_body = {}
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
captured_body.update(json.loads(req.data.decode()))
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({"results": []}).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
search_memories("key", "user", "proj", "query")
|
||||
|
||||
assert "rerank" not in captured_body
|
||||
|
||||
|
||||
def test_search_memories_forwards_rerank_true():
|
||||
"""Regression for #5684: rerank=True must reach the request body so the
|
||||
REST endpoint actually reranks (it does not rerank when omitted)."""
|
||||
from _search import search_memories
|
||||
|
||||
captured_body = {}
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
captured_body.update(json.loads(req.data.decode()))
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({"results": []}).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
search_memories("key", "user", "proj", "query", rerank=True)
|
||||
|
||||
assert captured_body.get("rerank") is True
|
||||
|
||||
|
||||
def test_should_rerank_defaults_true(monkeypatch):
|
||||
"""Regression for #5684: auto-injection reranks by default."""
|
||||
from _search import should_rerank
|
||||
|
||||
monkeypatch.delenv("MEM0_RERANK", raising=False)
|
||||
assert should_rerank() is True
|
||||
|
||||
|
||||
def test_should_rerank_opt_out_values(monkeypatch):
|
||||
from _search import should_rerank
|
||||
|
||||
for falsey in ("0", "false", "False", "NO", "off", ""):
|
||||
monkeypatch.setenv("MEM0_RERANK", falsey)
|
||||
assert should_rerank() is False, falsey
|
||||
|
||||
for truthy in ("1", "true", "yes", "on"):
|
||||
monkeypatch.setenv("MEM0_RERANK", truthy)
|
||||
assert should_rerank() is True, truthy
|
||||
|
||||
|
||||
def test_format_results_for_context():
|
||||
from _search import format_results_for_context
|
||||
|
||||
|
||||
@@ -64,13 +64,15 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"form-data@<4.0.6": ">=4.0.6",
|
||||
"protobufjs@<7.5.5": "^7.5.5",
|
||||
"vite": "^8.0.5",
|
||||
"langsmith@<0.6.0": "^0.6.0",
|
||||
"picomatch@<2.3.2": "^2.3.2",
|
||||
"@qdrant/js-client-rest": "^1.18.0",
|
||||
"uuid@<11.1.1": ">=11.1.1",
|
||||
"esbuild": ">=0.28.1"
|
||||
"esbuild": ">=0.28.1",
|
||||
"undici@<6.27.0": ">=6.27.0 <8.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+12
-10
@@ -5,6 +5,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
form-data@<4.0.6: '>=4.0.6'
|
||||
protobufjs@<7.5.5: ^7.5.5
|
||||
vite: ^8.0.5
|
||||
langsmith@<0.6.0: ^0.6.0
|
||||
@@ -12,6 +13,7 @@ overrides:
|
||||
'@qdrant/js-client-rest': ^1.18.0
|
||||
uuid@<11.1.1: '>=11.1.1'
|
||||
esbuild: '>=0.28.1'
|
||||
undici@<6.27.0: '>=6.27.0 <8.0.0'
|
||||
|
||||
importers:
|
||||
|
||||
@@ -1109,8 +1111,8 @@ packages:
|
||||
form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
form-data@4.0.6:
|
||||
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
formdata-node@4.4.1:
|
||||
@@ -1995,9 +1997,9 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@6.26.0:
|
||||
resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==}
|
||||
engines: {node: '>=18.17'}
|
||||
undici@7.28.0:
|
||||
resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
@@ -2508,7 +2510,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@qdrant/openapi-typescript-fetch': 1.2.6
|
||||
typescript: 5.9.3
|
||||
undici: 6.26.0
|
||||
undici: 7.28.0
|
||||
|
||||
'@qdrant/openapi-typescript-fetch@1.2.6': {}
|
||||
|
||||
@@ -2728,7 +2730,7 @@ snapshots:
|
||||
'@types/node-fetch@2.6.13':
|
||||
dependencies:
|
||||
'@types/node': 22.19.20
|
||||
form-data: 4.0.5
|
||||
form-data: 4.0.6
|
||||
|
||||
'@types/node@18.19.130':
|
||||
dependencies:
|
||||
@@ -2870,7 +2872,7 @@ snapshots:
|
||||
axios@1.17.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
form-data: 4.0.6
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
@@ -3131,7 +3133,7 @@ snapshots:
|
||||
|
||||
form-data-encoder@1.7.2: {}
|
||||
|
||||
form-data@4.0.5:
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
@@ -4062,7 +4064,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@6.26.0: {}
|
||||
undici@7.28.0: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ onlyBuiltDependencies:
|
||||
- protobufjs
|
||||
|
||||
overrides:
|
||||
"form-data@<4.0.6": ">=4.0.6"
|
||||
"protobufjs@<7.5.5": "^7.5.5"
|
||||
"vite": "^8.0.5"
|
||||
"langsmith@<0.6.0": "^0.6.0"
|
||||
@@ -20,3 +21,4 @@ overrides:
|
||||
"@qdrant/js-client-rest": "^1.18.0"
|
||||
"uuid@<11.1.1": ">=11.1.1"
|
||||
"esbuild": ">=0.28.1"
|
||||
"undici@<6.27.0": ">=6.27.0 <8.0.0"
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
safePath,
|
||||
normalizeModuleUrlToPath,
|
||||
loadSkill,
|
||||
loadTriagePrompt,
|
||||
loadCompactTriagePrompt,
|
||||
} from "./skill-loader.ts";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// safePath — path containment
|
||||
@@ -74,6 +76,31 @@ describe("loadSkill path traversal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeModuleUrlToPath", () => {
|
||||
it("normalizes raw Windows paths before fileURLToPath conversion", () => {
|
||||
const rawWindowsMetaUrl = "C:\\Users\\example\\openclaw\\index.ts";
|
||||
const result = normalizeModuleUrlToPath(rawWindowsMetaUrl);
|
||||
// Assert the decoded property directly rather than reconstructing via the function body
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result).not.toContain("%5C");
|
||||
});
|
||||
|
||||
it("leaves already-correct file URLs unchanged", () => {
|
||||
const fileMetaUrl = "file:///C:/Users/example/openclaw/index.ts";
|
||||
const expected = fileURLToPath(fileMetaUrl);
|
||||
|
||||
expect(normalizeModuleUrlToPath(fileMetaUrl)).toBe(expected);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"passes POSIX absolute paths through unchanged",
|
||||
() => {
|
||||
const posixPath = "/home/user/openclaw/index.ts";
|
||||
expect(normalizeModuleUrlToPath(posixPath)).toBe(posixPath);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("loadCompactTriagePrompt", () => {
|
||||
it("keeps the core triage instructions without inlining the full skill body", () => {
|
||||
const prompt = loadCompactTriagePrompt();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import type { SkillsConfig, CategoryConfig } from "./types.ts";
|
||||
import { readText, exists } from "./fs-safe.ts";
|
||||
|
||||
@@ -84,6 +84,14 @@ function parseSkillFile(content: string): ParsedSkill {
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal — exported for testing only */
|
||||
export function normalizeModuleUrlToPath(moduleUrl: string): string {
|
||||
const normalizedUrl = moduleUrl.startsWith("file:")
|
||||
? moduleUrl
|
||||
: pathToFileURL(moduleUrl).toString();
|
||||
return fileURLToPath(normalizedUrl);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Skill Loader
|
||||
// ============================================================================
|
||||
@@ -96,7 +104,7 @@ function resolveSkillsDir(): string {
|
||||
|
||||
// Strategy 1: import.meta.url (works in native ESM)
|
||||
try {
|
||||
const metaDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const metaDir = path.dirname(normalizeModuleUrlToPath(import.meta.url));
|
||||
candidates.push(path.join(metaDir, "skills"));
|
||||
candidates.push(path.join(metaDir, "..", "skills"));
|
||||
} catch {
|
||||
|
||||
@@ -71,8 +71,11 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"form-data@<4.0.6": ">=4.0.6",
|
||||
"uuid@<11.1.1": ">=11.1.1",
|
||||
"esbuild": ">=0.28.1"
|
||||
"esbuild": ">=0.28.1",
|
||||
"undici@<6.27.0": ">=6.27.0 <8.0.0",
|
||||
"undici@>=8.0.0 <8.5.0": ">=8.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-14
@@ -5,8 +5,11 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
form-data@<4.0.6: '>=4.0.6'
|
||||
uuid@<11.1.1: '>=11.1.1'
|
||||
esbuild: '>=0.28.1'
|
||||
undici@<6.27.0: '>=6.27.0 <8.0.0'
|
||||
undici@>=8.0.0 <8.5.0: '>=8.5.0'
|
||||
|
||||
importers:
|
||||
|
||||
@@ -1368,8 +1371,8 @@ packages:
|
||||
form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
form-data@4.0.6:
|
||||
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
formdata-node@4.4.1:
|
||||
@@ -2349,12 +2352,12 @@ packages:
|
||||
undici-types@7.24.6:
|
||||
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
||||
|
||||
undici@6.26.0:
|
||||
resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==}
|
||||
engines: {node: '>=18.17'}
|
||||
undici@7.28.0:
|
||||
resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
undici@8.3.0:
|
||||
resolution: {integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==}
|
||||
undici@8.5.0:
|
||||
resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==}
|
||||
engines: {node: '>=22.19.0'}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
@@ -2937,7 +2940,7 @@ snapshots:
|
||||
minimatch: 10.2.5
|
||||
proper-lockfile: 4.1.2
|
||||
typebox: 1.1.38
|
||||
undici: 8.3.0
|
||||
undici: 8.5.0
|
||||
yaml: 2.9.0
|
||||
optionalDependencies:
|
||||
'@mariozechner/clipboard': 0.3.9
|
||||
@@ -3200,7 +3203,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@qdrant/openapi-typescript-fetch': 1.2.6
|
||||
typescript: 6.0.3
|
||||
undici: 6.26.0
|
||||
undici: 7.28.0
|
||||
|
||||
'@qdrant/openapi-typescript-fetch@1.2.6': {}
|
||||
|
||||
@@ -3474,7 +3477,7 @@ snapshots:
|
||||
'@types/node-fetch@2.6.13':
|
||||
dependencies:
|
||||
'@types/node': 25.9.2
|
||||
form-data: 4.0.5
|
||||
form-data: 4.0.6
|
||||
|
||||
'@types/node@18.19.130':
|
||||
dependencies:
|
||||
@@ -3598,7 +3601,7 @@ snapshots:
|
||||
axios@1.17.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
form-data: 4.0.6
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
@@ -3891,7 +3894,7 @@ snapshots:
|
||||
|
||||
form-data-encoder@1.7.2: {}
|
||||
|
||||
form-data@4.0.5:
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
@@ -4892,9 +4895,9 @@ snapshots:
|
||||
|
||||
undici-types@7.24.6: {}
|
||||
|
||||
undici@6.26.0: {}
|
||||
undici@7.28.0: {}
|
||||
|
||||
undici@8.3.0: {}
|
||||
undici@8.5.0: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
|
||||
@@ -2,5 +2,8 @@ packages:
|
||||
- '.'
|
||||
|
||||
overrides:
|
||||
"form-data@<4.0.6": ">=4.0.6"
|
||||
"uuid@<11.1.1": ">=11.1.1"
|
||||
"esbuild": ">=0.28.1"
|
||||
"undici@<6.27.0": ">=6.27.0 <8.0.0"
|
||||
"undici@>=8.0.0 <8.5.0": ">=8.5.0"
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
],
|
||||
"overrides": {
|
||||
"glob@>=10.2.0 <10.5.0": "^10.5.0",
|
||||
"js-yaml@<=4.1.1": ">=4.2.0",
|
||||
"minimatch@<3.1.3": "^3.1.3",
|
||||
"minimatch@>=5.0.0 <5.1.8": "^5.1.8",
|
||||
"minimatch@>=9.0.0 <9.0.7": "^9.0.7",
|
||||
|
||||
+9
-23
@@ -6,6 +6,7 @@ settings:
|
||||
|
||||
overrides:
|
||||
glob@>=10.2.0 <10.5.0: ^10.5.0
|
||||
js-yaml@<=4.1.1: '>=4.2.0'
|
||||
minimatch@<3.1.3: ^3.1.3
|
||||
minimatch@>=5.0.0 <5.1.8: ^5.1.8
|
||||
minimatch@>=9.0.0 <9.0.7: ^9.0.7
|
||||
@@ -793,8 +794,8 @@ packages:
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
babel-jest@29.7.0:
|
||||
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
|
||||
@@ -1025,11 +1026,6 @@ packages:
|
||||
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
eventsource-parser@3.1.0:
|
||||
resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -1351,8 +1347,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@3.14.2:
|
||||
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
|
||||
js-yaml@4.2.0:
|
||||
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.1.0:
|
||||
@@ -1654,9 +1650,6 @@ packages:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2226,7 +2219,7 @@ snapshots:
|
||||
camelcase: 5.3.1
|
||||
find-up: 4.1.0
|
||||
get-package-type: 0.1.0
|
||||
js-yaml: 3.14.2
|
||||
js-yaml: 4.2.0
|
||||
resolve-from: 5.0.0
|
||||
|
||||
'@istanbuljs/schema@0.1.6': {}
|
||||
@@ -2605,9 +2598,7 @@ snapshots:
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
argparse@2.0.1: {}
|
||||
|
||||
babel-jest@29.7.0(@babel/core@7.29.7):
|
||||
dependencies:
|
||||
@@ -2857,8 +2848,6 @@ snapshots:
|
||||
|
||||
escape-string-regexp@2.0.0: {}
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
eventsource-parser@3.1.0: {}
|
||||
|
||||
execa@5.1.1:
|
||||
@@ -3355,10 +3344,9 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@3.14.2:
|
||||
js-yaml@4.2.0:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
argparse: 2.0.1
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
@@ -3628,8 +3616,6 @@ snapshots:
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
@@ -7,6 +7,7 @@ onlyBuiltDependencies:
|
||||
|
||||
overrides:
|
||||
"glob@>=10.2.0 <10.5.0": "^10.5.0"
|
||||
"js-yaml@<=4.1.1": ">=4.2.0"
|
||||
"minimatch@<3.1.3": "^3.1.3"
|
||||
"minimatch@>=5.0.0 <5.1.8": "^5.1.8"
|
||||
"minimatch@>=9.0.0 <9.0.7": "^9.0.7"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "3.0.9",
|
||||
"version": "3.0.11",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -139,6 +139,7 @@
|
||||
"better-sqlite3"
|
||||
],
|
||||
"overrides": {
|
||||
"form-data@<4.0.6": ">=4.0.6",
|
||||
"picomatch@<2.3.2": "^2.3.2",
|
||||
"picomatch@>=4.0.0 <4.0.4": "^4.0.4",
|
||||
"jws@4.0.0": "4.0.1",
|
||||
@@ -155,7 +156,8 @@
|
||||
"tar-fs@>=2.0.0 <2.1.4": "^2.1.4",
|
||||
"glob@>=10.2.0 <10.5.0": "^10.5.0",
|
||||
"@modelcontextprotocol/sdk": "^1.25.4",
|
||||
"esbuild": ">=0.28.1"
|
||||
"esbuild": ">=0.28.1",
|
||||
"undici@<6.27.0": ">=6.27.0 <8.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+12
-10
@@ -5,6 +5,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
form-data@<4.0.6: '>=4.0.6'
|
||||
picomatch@<2.3.2: ^2.3.2
|
||||
picomatch@>=4.0.0 <4.0.4: ^4.0.4
|
||||
jws@4.0.0: 4.0.1
|
||||
@@ -22,6 +23,7 @@ overrides:
|
||||
glob@>=10.2.0 <10.5.0: ^10.5.0
|
||||
'@modelcontextprotocol/sdk': ^1.25.4
|
||||
esbuild: '>=0.28.1'
|
||||
undici@<6.27.0: '>=6.27.0 <8.0.0'
|
||||
|
||||
importers:
|
||||
|
||||
@@ -1554,8 +1556,8 @@ packages:
|
||||
form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
form-data@4.0.6:
|
||||
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
formdata-node@4.4.1:
|
||||
@@ -2924,9 +2926,9 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@6.26.0:
|
||||
resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==}
|
||||
engines: {node: '>=18.17'}
|
||||
undici@7.28.0:
|
||||
resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
update-browserslist-db@1.2.3:
|
||||
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
|
||||
@@ -3746,7 +3748,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@qdrant/openapi-typescript-fetch': 1.2.6
|
||||
typescript: 5.5.4
|
||||
undici: 6.26.0
|
||||
undici: 7.28.0
|
||||
|
||||
'@qdrant/openapi-typescript-fetch@1.2.6': {}
|
||||
|
||||
@@ -3972,7 +3974,7 @@ snapshots:
|
||||
'@types/node-fetch@2.6.13':
|
||||
dependencies:
|
||||
'@types/node': 22.19.21
|
||||
form-data: 4.0.5
|
||||
form-data: 4.0.6
|
||||
|
||||
'@types/node@18.19.130':
|
||||
dependencies:
|
||||
@@ -4078,7 +4080,7 @@ snapshots:
|
||||
axios@1.17.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
form-data: 4.0.6
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
@@ -4561,7 +4563,7 @@ snapshots:
|
||||
|
||||
form-data-encoder@1.7.2: {}
|
||||
|
||||
form-data@4.0.5:
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
@@ -6112,7 +6114,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@6.26.0: {}
|
||||
undici@7.28.0: {}
|
||||
|
||||
update-browserslist-db@1.2.3(browserslist@4.28.2):
|
||||
dependencies:
|
||||
|
||||
@@ -6,6 +6,7 @@ onlyBuiltDependencies:
|
||||
- better-sqlite3
|
||||
|
||||
overrides:
|
||||
"form-data@<4.0.6": ">=4.0.6"
|
||||
"picomatch@<2.3.2": "^2.3.2"
|
||||
"picomatch@>=4.0.0 <4.0.4": "^4.0.4"
|
||||
"jws@4.0.0": "4.0.1"
|
||||
@@ -23,3 +24,4 @@ overrides:
|
||||
"glob@>=10.2.0 <10.5.0": "^10.5.0"
|
||||
"@modelcontextprotocol/sdk": "^1.25.4"
|
||||
"esbuild": ">=0.28.1"
|
||||
"undici@<6.27.0": ">=6.27.0 <8.0.0"
|
||||
|
||||
@@ -253,6 +253,11 @@ export default class MemoryClient {
|
||||
messages: Array<Message>,
|
||||
options: AddMemoryOptions & Record<string, any> = {},
|
||||
): Promise<Array<Memory>> {
|
||||
// Tightly scoped validation guard to resolve #5465
|
||||
if (!messages || (Array.isArray(messages) && messages.length === 0)) {
|
||||
throw new Error("Cannot process an empty messages payload.");
|
||||
}
|
||||
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
|
||||
const payload = this._preparePayload(messages, options);
|
||||
@@ -276,19 +281,22 @@ export default class MemoryClient {
|
||||
text,
|
||||
metadata,
|
||||
timestamp,
|
||||
expirationDate,
|
||||
}: {
|
||||
text?: string;
|
||||
metadata?: Record<string, any>;
|
||||
timestamp?: number | string;
|
||||
expirationDate?: string | null;
|
||||
},
|
||||
): Promise<Array<Memory>> {
|
||||
if (
|
||||
text === undefined &&
|
||||
metadata === undefined &&
|
||||
timestamp === undefined
|
||||
timestamp === undefined &&
|
||||
expirationDate === undefined
|
||||
) {
|
||||
throw new Error(
|
||||
"At least one of text, metadata, or timestamp must be provided for update.",
|
||||
"At least one of text, metadata, timestamp, or expirationDate must be provided for update.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,6 +305,7 @@ export default class MemoryClient {
|
||||
if (text !== undefined) payload.text = text;
|
||||
if (metadata !== undefined) payload.metadata = metadata;
|
||||
if (timestamp !== undefined) payload.timestamp = timestamp;
|
||||
if (expirationDate !== undefined) payload.expiration_date = expirationDate;
|
||||
|
||||
const payloadKeys = Object.keys(payload);
|
||||
this._captureEvent("update", [payloadKeys]);
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface AddMemoryOptions extends EntityOptions {
|
||||
customCategories?: custom_categories[];
|
||||
customInstructions?: string;
|
||||
timestamp?: number;
|
||||
expirationDate?: string;
|
||||
structuredDataSchema?: Record<string, any>;
|
||||
}
|
||||
|
||||
@@ -25,6 +26,7 @@ export interface SearchMemoryOptions {
|
||||
latestOnly?: boolean;
|
||||
fields?: string[];
|
||||
categories?: string[];
|
||||
showExpired?: boolean;
|
||||
}
|
||||
|
||||
export interface GetAllMemoryOptions {
|
||||
@@ -35,6 +37,7 @@ export interface GetAllMemoryOptions {
|
||||
endDate?: string;
|
||||
latestOnly?: boolean;
|
||||
categories?: string[];
|
||||
showExpired?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteAllMemoryOptions extends EntityOptions {}
|
||||
@@ -119,6 +122,7 @@ export interface Memory {
|
||||
memoryType?: string;
|
||||
score?: number;
|
||||
metadata?: any | null;
|
||||
expirationDate?: string | null;
|
||||
owner?: string | null;
|
||||
agentId?: string | null;
|
||||
appId?: string | null;
|
||||
@@ -169,7 +173,9 @@ export interface PaginatedMemories {
|
||||
|
||||
export interface ProjectResponse {
|
||||
customInstructions?: string;
|
||||
customCategories?: string[];
|
||||
// The API returns category objects (`[{ "<name>": "<description>" }]`),
|
||||
// not bare strings (see issue #5738).
|
||||
customCategories?: custom_categories[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,16 +59,30 @@ describe("MemoryClient - add()", () => {
|
||||
expect(getFetchBody(call!).user_id).toBe("user_1");
|
||||
});
|
||||
|
||||
test("sends empty messages array without crashing", async () => {
|
||||
test("serializes expirationDate as expiration_date", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/add/", { status: 200, body: [] });
|
||||
extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add([], { userId: "u1" });
|
||||
await client.add([{ role: "user", content: "test" }], {
|
||||
userId: "u1",
|
||||
expirationDate: "2030-01-31",
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/add/", "POST");
|
||||
expect(getFetchBody(call!).messages).toEqual([]);
|
||||
expect(getFetchBody(call!).expiration_date).toBe("2030-01-31");
|
||||
});
|
||||
|
||||
test("throws an error when given an empty messages array", async () => {
|
||||
setupMockFetch();
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
|
||||
//Asserts that the validation guard catches the empty input early
|
||||
await expect(client.add([], { userId: "u1" })).rejects.toThrow(
|
||||
"Cannot process an empty messages payload.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,11 +191,26 @@ describe("MemoryClient - update()", () => {
|
||||
expect(body.timestamp).toBe(1710600000);
|
||||
});
|
||||
|
||||
test("sends expirationDate as expiration_date, including null", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v1/memories/mem_123/", {
|
||||
status: 200,
|
||||
body: createMockMemory(),
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.update("mem_123", { expirationDate: null });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/mem_123/", "PUT");
|
||||
expect(getFetchBody(call!).expiration_date).toBeNull();
|
||||
});
|
||||
|
||||
test("throws when no fields provided", async () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await expect(client.update("mem_123", {})).rejects.toThrow(
|
||||
"At least one of text, metadata, or timestamp must be provided",
|
||||
"At least one of text, metadata, timestamp, or expirationDate must be provided",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,6 +81,24 @@ describe("MemoryClient - search()", () => {
|
||||
expect(getFetchBody(call!).latest_only).toBe(true);
|
||||
});
|
||||
|
||||
test("serializes showExpired as show_expired", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/search/", {
|
||||
status: 200,
|
||||
body: { results: [] },
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", {
|
||||
filters: { user_id: "u1" },
|
||||
showExpired: true,
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).show_expired).toBe(true);
|
||||
});
|
||||
|
||||
test("passes complex OR filters through to the API body", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/search/", {
|
||||
@@ -300,4 +318,22 @@ describe("MemoryClient - getAll() entity param rejection", () => {
|
||||
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||
expect(getFetchBody(call!).latest_only).toBe(true);
|
||||
});
|
||||
|
||||
test("serializes showExpired as show_expired", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/", {
|
||||
status: 200,
|
||||
body: { results: [] },
|
||||
});
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({
|
||||
filters: { user_id: "u1" },
|
||||
showExpired: true,
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||
expect(getFetchBody(call!).show_expired).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { camelToSnakeKeys, snakeToCamelKeys } from "../utils";
|
||||
|
||||
describe("camelToSnakeKeys / snakeToCamelKeys", () => {
|
||||
it("converts SDK-defined keys between camelCase and snake_case", () => {
|
||||
expect(camelToSnakeKeys({ userId: "u1", agentId: "a1" })).toEqual({
|
||||
user_id: "u1",
|
||||
agent_id: "a1",
|
||||
});
|
||||
expect(snakeToCamelKeys({ user_id: "u1", agent_id: "a1" })).toEqual({
|
||||
userId: "u1",
|
||||
agentId: "a1",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves logical operator keys (OR/AND/NOT)", () => {
|
||||
expect(camelToSnakeKeys({ OR: [{ userId: "u1" }] })).toEqual({
|
||||
OR: [{ user_id: "u1" }],
|
||||
});
|
||||
});
|
||||
|
||||
describe("user-controlled metadata blob (issue #5055)", () => {
|
||||
it("does not camelize snake_case keys inside metadata on read", () => {
|
||||
const apiResponse = {
|
||||
id: "mem-1",
|
||||
user_id: "u1",
|
||||
metadata: { message_id: "x", some_custom_key: "y" },
|
||||
};
|
||||
|
||||
expect(snakeToCamelKeys(apiResponse)).toEqual({
|
||||
id: "mem-1",
|
||||
userId: "u1",
|
||||
metadata: { message_id: "x", some_custom_key: "y" },
|
||||
});
|
||||
});
|
||||
|
||||
it("does not snake_case camelCase keys inside metadata on write", () => {
|
||||
const payload = {
|
||||
userId: "u1",
|
||||
metadata: { messageId: "x", someCustomKey: "y" },
|
||||
};
|
||||
|
||||
expect(camelToSnakeKeys(payload)).toEqual({
|
||||
user_id: "u1",
|
||||
metadata: { messageId: "x", someCustomKey: "y" },
|
||||
});
|
||||
});
|
||||
|
||||
it("round-trips arbitrary metadata keys losslessly", () => {
|
||||
const metadata = {
|
||||
message_id: "abc",
|
||||
camelKey: 1,
|
||||
nested: { deep_snake: true, deepCamel: false },
|
||||
arr: [{ inner_key: 1 }],
|
||||
};
|
||||
|
||||
const roundTripped = snakeToCamelKeys(
|
||||
camelToSnakeKeys({ userId: "u1", metadata }),
|
||||
);
|
||||
|
||||
expect(roundTripped.metadata).toEqual(metadata);
|
||||
});
|
||||
|
||||
it("preserves metadata nested inside an array of results", () => {
|
||||
const apiResponse = {
|
||||
results: [
|
||||
{ id: "1", metadata: { message_id: "x" } },
|
||||
{ id: "2", metadata: { another_key: "z" } },
|
||||
],
|
||||
};
|
||||
|
||||
expect(snakeToCamelKeys(apiResponse)).toEqual({
|
||||
results: [
|
||||
{ id: "1", metadata: { message_id: "x" } },
|
||||
{ id: "2", metadata: { another_key: "z" } },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("user-controlled structuredDataSchema blob (issue #5055)", () => {
|
||||
it("converts the outer key but leaves user field names on write", () => {
|
||||
expect(
|
||||
camelToSnakeKeys({
|
||||
structuredDataSchema: { firstName: "string", lastName: "string" },
|
||||
}),
|
||||
).toEqual({
|
||||
// outer SDK key is snake_cased, user-defined field names are not
|
||||
structured_data_schema: { firstName: "string", lastName: "string" },
|
||||
});
|
||||
});
|
||||
|
||||
it("converts the outer key but leaves user field names on read", () => {
|
||||
expect(
|
||||
snakeToCamelKeys({
|
||||
structured_data_schema: { first_name: "string", last_name: "string" },
|
||||
}),
|
||||
).toEqual({
|
||||
structuredDataSchema: { first_name: "string", last_name: "string" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("user-controlled customCategories names (issue #5738)", () => {
|
||||
it("converts the outer key but leaves multi-word category names on write", () => {
|
||||
expect(
|
||||
camelToSnakeKeys({
|
||||
customCategories: [
|
||||
{ work_life_balance: "desc" },
|
||||
{ AIResearch: "desc" },
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
// outer SDK key is snake_cased, user-defined category names are not
|
||||
custom_categories: [
|
||||
{ work_life_balance: "desc" },
|
||||
{ AIResearch: "desc" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("converts the outer key but leaves category names verbatim on read", () => {
|
||||
expect(
|
||||
snakeToCamelKeys({
|
||||
custom_categories: [
|
||||
{ work_life_balance: "desc" },
|
||||
{ AIResearch: "desc" },
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
customCategories: [
|
||||
{ work_life_balance: "desc" },
|
||||
{ AIResearch: "desc" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("round-trips category names losslessly (write then read)", () => {
|
||||
const customCategories = [
|
||||
{ work_life_balance: "balance between work and life" },
|
||||
{ AIResearch: "artificial intelligence research" },
|
||||
];
|
||||
const roundTripped = snakeToCamelKeys(
|
||||
camelToSnakeKeys({ customCategories }),
|
||||
);
|
||||
expect(roundTripped.customCategories).toEqual(customCategories);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,9 +14,32 @@ function snakeToCamel(str: string): string {
|
||||
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys whose values are user-controlled, opaque blobs. Their nested keys must
|
||||
* be passed through verbatim — converting them would silently rewrite the
|
||||
* user's own keys and break round-trips (see issue #5055).
|
||||
*
|
||||
* The check runs against the source key, so a multi-word key must be listed in
|
||||
* both casings to be covered in both directions: the camelCase form for the
|
||||
* outbound `camelToSnakeKeys` path and the snake_case form for the inbound
|
||||
* `snakeToCamelKeys` path. `metadata` is spelled identically in both, so one
|
||||
* entry suffices; `structuredDataSchema` needs both.
|
||||
*/
|
||||
const OPAQUE_VALUE_KEYS = new Set([
|
||||
"metadata",
|
||||
"structuredDataSchema",
|
||||
"structured_data_schema",
|
||||
// Custom-category names are user-controlled keys (`[{ "<name>": "<desc>" }]`).
|
||||
// Listed in both casings so they round-trip verbatim in both directions
|
||||
// (see issue #5738; same class as `metadata`/`structuredDataSchema`).
|
||||
"customCategories",
|
||||
"custom_categories",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Recursively converts all keys of an object from camelCase to snake_case.
|
||||
* Used for converting user-facing camelCase params to API snake_case payloads.
|
||||
* Values under {@link OPAQUE_VALUE_KEYS} (e.g. `metadata`) are left untouched.
|
||||
*/
|
||||
export function camelToSnakeKeys(obj: any): any {
|
||||
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
|
||||
@@ -26,7 +49,7 @@ export function camelToSnakeKeys(obj: any): any {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
camelToSnake(key),
|
||||
camelToSnakeKeys(value),
|
||||
OPAQUE_VALUE_KEYS.has(key) ? value : camelToSnakeKeys(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -34,6 +57,7 @@ export function camelToSnakeKeys(obj: any): any {
|
||||
/**
|
||||
* Recursively converts all keys of an object from snake_case to camelCase.
|
||||
* Used for converting API snake_case responses to user-facing camelCase.
|
||||
* Values under {@link OPAQUE_VALUE_KEYS} (e.g. `metadata`) are left untouched.
|
||||
*/
|
||||
export function snakeToCamelKeys(obj: any): any {
|
||||
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
|
||||
@@ -43,7 +67,7 @@ export function snakeToCamelKeys(obj: any): any {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
snakeToCamel(key),
|
||||
snakeToCamelKeys(value),
|
||||
OPAQUE_VALUE_KEYS.has(key) ? value : snakeToCamelKeys(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export * from "./llms/ollama";
|
||||
export * from "./llms/lmstudio";
|
||||
export * from "./llms/mistral";
|
||||
export * from "./llms/langchain";
|
||||
export * from "./llms/litellm";
|
||||
export * from "./vector_stores/base";
|
||||
export * from "./vector_stores/memory";
|
||||
export * from "./vector_stores/qdrant";
|
||||
|
||||
@@ -14,7 +14,13 @@ export class AnthropicLLM implements LLM {
|
||||
if (!apiKey) {
|
||||
throw new Error("Anthropic API key is required");
|
||||
}
|
||||
this.client = new Anthropic({ apiKey });
|
||||
// Forward baseURL to the client when set so proxy/gateway users are
|
||||
// honored (parity with the OpenAI provider and the Python fix in #5626).
|
||||
const clientArgs: { apiKey: string; baseURL?: string } = { apiKey };
|
||||
if (config.baseURL) {
|
||||
clientArgs.baseURL = config.baseURL;
|
||||
}
|
||||
this.client = new Anthropic(clientArgs);
|
||||
this.model = config.model || "claude-sonnet-4-6";
|
||||
// Defaults mirror the Python provider's AnthropicConfig
|
||||
// (max_tokens=2000, temperature=0.1, top_p omitted).
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { OpenAILLM } from "./openai";
|
||||
import { LLMConfig, Message } from "../types";
|
||||
import { LLMResponse } from "./base";
|
||||
|
||||
export class LiteLLM extends OpenAILLM {
|
||||
constructor(config: LLMConfig) {
|
||||
super({
|
||||
...config,
|
||||
apiKey: config.apiKey || process.env.LITELLM_API_KEY || "sk-anything",
|
||||
baseURL:
|
||||
config.baseURL ||
|
||||
process.env.LITELLM_API_BASE ||
|
||||
"http://localhost:4000",
|
||||
model: config.model || "gpt-5-mini",
|
||||
});
|
||||
}
|
||||
|
||||
async generateResponse(
|
||||
messages: Message[],
|
||||
responseFormat?: { type: string },
|
||||
tools?: any[],
|
||||
): Promise<string | LLMResponse> {
|
||||
try {
|
||||
return await super.generateResponse(messages, responseFormat, tools);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`LiteLLM failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async generateChat(messages: Message[]): Promise<LLMResponse> {
|
||||
try {
|
||||
return await super.generateChat(messages);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`LiteLLM failed: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { OpenAILLM } from "./openai";
|
||||
import { LLMConfig, Message } from "../types";
|
||||
import { LLMResponse } from "./base";
|
||||
|
||||
export class MiniMaxLLM extends OpenAILLM {
|
||||
constructor(config: LLMConfig) {
|
||||
const apiKey = config.apiKey || process.env.MINIMAX_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("MiniMax API key is required");
|
||||
}
|
||||
super({
|
||||
...config,
|
||||
apiKey,
|
||||
baseURL:
|
||||
config.baseURL ||
|
||||
process.env.MINIMAX_API_BASE ||
|
||||
"https://api.minimax.io/v1",
|
||||
model: config.model || "MiniMax-M2.7",
|
||||
});
|
||||
}
|
||||
|
||||
async generateResponse(
|
||||
messages: Message[],
|
||||
responseFormat?: { type: string },
|
||||
tools?: any[],
|
||||
): Promise<string | LLMResponse> {
|
||||
try {
|
||||
return await super.generateResponse(messages, responseFormat, tools);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`MiniMax LLM failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async generateChat(messages: Message[]): Promise<LLMResponse> {
|
||||
try {
|
||||
return await super.generateChat(messages);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`MiniMax LLM failed: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user