Compare commits

..
269 changed files with 21901 additions and 13952 deletions
+2 -2
View File
@@ -266,7 +266,7 @@ config = MemoryConfig(
graph_store=GraphStoreConfig(provider="neo4j", config={...}), # optional
history_db_path="~/.mem0/history.db",
version="v1.1",
custom_instructions="Custom prompt...",
custom_fact_extraction_prompt="Custom prompt...",
custom_update_memory_prompt="Custom prompt..."
)
```
@@ -684,7 +684,7 @@ Conversation: {messages}
"""
config = MemoryConfig(
custom_instructions=custom_extraction_prompt
custom_fact_extraction_prompt=custom_extraction_prompt
)
memory = Memory(config)
```
+1 -10
View File
@@ -88,13 +88,6 @@ Install the sdk via pip:
pip install mem0ai
```
For enhanced hybrid search with BM25 keyword matching and entity extraction, install with NLP support:
```bash
pip install mem0ai[nlp]
python -m spacy download en_core_web_sm
```
Install sdk via npm:
```bash
npm install mem0ai
@@ -116,9 +109,7 @@ See the [CLI documentation](https://docs.mem0.ai/platform/cli) for the full comm
### Basic Usage
Mem0 requires an LLM to function, with `gpt-4.1-nano-2025-04-14` from OpenAI as the default. However, it supports a variety of LLMs; for details, refer to our [Supported LLMs documentation](https://docs.mem0.ai/components/llms/overview).
Mem0 uses `text-embedding-3-small` from OpenAI as the default embedding model. For best results with hybrid search (semantic + keyword + entity boosting), we recommend using at least [Qwen 600M](https://huggingface.co/Alibaba-NLP/gte-Qwen2-1.5B-instruct) or a comparable embedding model. See [Supported Embeddings](https://docs.mem0.ai/components/embedders/overview) for configuration details.
Mem0 requires an LLM to function, with `gpt-4.1-nano-2025-04-14 from OpenAI as the default. However, it supports a variety of LLMs; for details, refer to our [Supported LLMs documentation](https://docs.mem0.ai/components/llms/overview).
First step is to instantiate the memory:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/cli",
"version": "0.2.3",
"version": "0.2.2",
"description": "The official CLI for mem0 — the memory layer for AI agents",
"type": "module",
"bin": {
+9 -12
View File
@@ -116,7 +116,6 @@ export class PlatformBackend implements Backend {
if (opts.expires) payload.expiration_date = opts.expires;
if (opts.categories) payload.categories = opts.categories;
if (opts.enableGraph) payload.enable_graph = true;
payload.source = "CLI";
return (await this._request("POST", "/v1/memories/", {
json: payload,
@@ -177,7 +176,6 @@ export class PlatformBackend implements Backend {
if (opts.keyword) payload.keyword_search = true;
if (opts.fields) payload.fields = opts.fields;
if (opts.enableGraph) payload.enable_graph = true;
payload.source = "CLI";
const result = (await this._request("POST", "/v2/memories/search/", {
json: payload,
@@ -188,9 +186,10 @@ export class PlatformBackend implements Backend {
}
async get(memoryId: string): Promise<Record<string, unknown>> {
return (await this._request("GET", `/v1/memories/${memoryId}/`, {
params: { source: "CLI" },
})) as Record<string, unknown>;
return (await this._request("GET", `/v1/memories/${memoryId}/`)) as Record<
string,
unknown
>;
}
async listMemories(
@@ -228,7 +227,6 @@ export class PlatformBackend implements Backend {
});
if (apiFilters) payload.filters = apiFilters;
if (opts.enableGraph) payload.enable_graph = true;
payload.source = "CLI";
const result = (await this._request("POST", "/v2/memories/", {
json: payload,
@@ -247,7 +245,6 @@ export class PlatformBackend implements Backend {
const payload: Record<string, unknown> = {};
if (content) payload.text = content;
if (metadata) payload.metadata = metadata;
payload.source = "CLI";
return (await this._request("PUT", `/v1/memories/${memoryId}/`, {
json: payload,
})) as Record<string, unknown>;
@@ -258,7 +255,7 @@ export class PlatformBackend implements Backend {
opts: DeleteOptions = {},
): Promise<Record<string, unknown>> {
if (opts.all) {
const params: Record<string, string> = { source: "CLI" };
const params: Record<string, string> = {};
if (opts.userId) params.user_id = opts.userId;
if (opts.agentId) params.agent_id = opts.agentId;
if (opts.appId) params.app_id = opts.appId;
@@ -268,9 +265,10 @@ export class PlatformBackend implements Backend {
})) as Record<string, unknown>;
}
if (memoryId) {
return (await this._request("DELETE", `/v1/memories/${memoryId}/`, {
params: { source: "CLI" },
})) as Record<string, unknown>;
return (await this._request(
"DELETE",
`/v1/memories/${memoryId}/`,
)) as Record<string, unknown>;
}
throw new Error("Either memoryId or --all is required");
}
@@ -293,7 +291,6 @@ export class PlatformBackend implements Backend {
result = (await this._request(
"DELETE",
`/v2/entities/${entityType}/${entityId}/`,
{ params: { source: "CLI" } },
)) as Record<string, unknown>;
}
return result;
-14
View File
@@ -31,15 +31,10 @@ export interface DefaultsConfig {
enableGraph: boolean;
}
export interface TelemetryConfig {
anonymousId: string;
}
export interface Mem0Config {
version: number;
defaults: DefaultsConfig;
platform: PlatformConfig;
telemetry: TelemetryConfig;
}
export function createDefaultConfig(): Mem0Config {
@@ -57,9 +52,6 @@ export function createDefaultConfig(): Mem0Config {
baseUrl: DEFAULT_BASE_URL,
userEmail: "",
},
telemetry: {
anonymousId: "",
},
};
}
@@ -88,9 +80,6 @@ export function loadConfig(): Mem0Config {
config.defaults.appId = defaults.app_id ?? "";
config.defaults.runId = defaults.run_id ?? "";
config.defaults.enableGraph = defaults.enable_graph ?? false;
const telemetry = data.telemetry ?? {};
config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
}
// Environment variable overrides
@@ -130,9 +119,6 @@ export function saveConfig(config: Mem0Config): void {
base_url: config.platform.baseUrl,
user_email: config.platform.userEmail,
},
telemetry: {
anonymous_id: config.telemetry.anonymousId,
},
};
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
+5 -52
View File
@@ -9,10 +9,10 @@
*/
import { spawn } from "node:child_process";
import { createHash, randomUUID } from "node:crypto";
import { createHash } from "node:crypto";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { CONFIG_FILE, loadConfig, saveConfig } from "./config.js";
import { CONFIG_FILE, loadConfig } from "./config.js";
import { CLI_VERSION } from "./version.js";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
@@ -29,34 +29,11 @@ function isTelemetryEnabled(): boolean {
}
}
/**
* Return a persistent per-machine anonymous ID, generating one if needed.
*
* Stored in ~/.mem0/config.json under `telemetry.anonymous_id` so that
* repeat runs on the same machine share one PostHog identity instead of
* collapsing into a single shared fallback string.
*/
function getOrCreateAnonymousId(): string {
const config = loadConfig();
if (config.telemetry.anonymousId) {
return config.telemetry.anonymousId;
}
const newId = `cli-anon-${randomUUID().replace(/-/g, "")}`;
config.telemetry.anonymousId = newId;
try {
saveConfig(config);
} catch {
/* ignore persistence failure — still return the generated ID */
}
return newId;
}
/**
* Return a stable anonymous identifier for the current user.
*
* Priority: cached user_email (from /v1/ping/) > MD5(api_key) >
* persistent per-machine anonymous ID.
* Priority: cached user_email (from /v1/ping/) > MD5(api_key) > fallback.
* Matches the SDK pattern in mem0-ts/src/client/mem0.ts.
*/
function getDistinctId(): string {
try {
@@ -70,11 +47,7 @@ function getDistinctId(): string {
} catch {
/* ignore */
}
try {
return getOrCreateAnonymousId();
} catch {
return `cli-anon-${randomUUID().replace(/-/g, "")}`;
}
return "anonymous-cli";
}
/**
@@ -96,25 +69,6 @@ export function captureEvent(
const config = loadConfig();
const distinctId = preResolvedEmail || getDistinctId();
// Detect anonymous → identified transition. If a stored anonymous_id
// exists and we just resolved to a real identity, fire a one-shot
// $identify event so PostHog stitches the pre-signup history onto
// the authenticated profile. Clear the stored id so we don't re-alias.
let anonIdToAlias: string | null = null;
if (
distinctId &&
!distinctId.startsWith("cli-anon-") &&
config.telemetry.anonymousId
) {
anonIdToAlias = config.telemetry.anonymousId;
config.telemetry.anonymousId = "";
try {
saveConfig(config);
} catch {
/* ignore — alias may double-fire next run, harmless */
}
}
const payload = {
api_key: POSTHOG_API_KEY,
distinct_id: distinctId,
@@ -138,7 +92,6 @@ export function captureEvent(
mem0ApiKey: config.platform.apiKey || "",
mem0BaseUrl: config.platform.baseUrl || "https://api.mem0.ai",
configPath: CONFIG_FILE,
anonDistinctIdToAlias: anonIdToAlias,
};
const child = spawn(
-21
View File
@@ -94,19 +94,6 @@ async function sendPosthogEvent(posthogHost, payload) {
}
}
async function sendIdentifyEvent(ctx, payload, anonId) {
const identifyPayload = {
api_key: payload.api_key,
event: "$identify",
distinct_id: payload.distinct_id,
properties: {
$anon_distinct_id: anonId,
$lib: (payload.properties && payload.properties.$lib) || "posthog-node",
},
};
await sendPosthogEvent(ctx.posthogHost, identifyPayload);
}
async function main() {
const ctx = JSON.parse(process.argv[2]);
const payload = ctx.payload;
@@ -115,14 +102,6 @@ async function main() {
await resolveAndCacheEmail(ctx, payload);
}
// Fire $identify *after* email resolution so PostHog links the stored
// anonymous id directly to the final identity (email, not the api-key
// hash). The regular event is sent next so it lands under the merged
// profile.
if (ctx.anonDistinctIdToAlias) {
await sendIdentifyEvent(ctx, payload, ctx.anonDistinctIdToAlias);
}
await sendPosthogEvent(ctx.posthogHost, payload);
}
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mem0-cli"
version = "0.2.3"
version = "0.2.2"
description = "The official CLI for mem0 — the memory layer for AI agents"
readme = "README.md"
license = "Apache-2.0"
+1 -1
View File
@@ -1,3 +1,3 @@
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
__version__ = "0.2.3"
__version__ = "0.2.2"
+4 -10
View File
@@ -93,7 +93,6 @@ class PlatformBackend(Backend):
payload["categories"] = categories
if enable_graph:
payload["enable_graph"] = True
payload["source"] = "CLI"
return self._request("POST", "/v1/memories/", json=payload)
@@ -173,7 +172,6 @@ class PlatformBackend(Backend):
payload["fields"] = fields
if enable_graph:
payload["enable_graph"] = True
payload["source"] = "CLI"
result = self._request("POST", "/v2/memories/search/", json=payload)
return (
@@ -183,7 +181,7 @@ class PlatformBackend(Backend):
)
def get(self, memory_id: str) -> dict:
return self._request("GET", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
return self._request("GET", f"/v1/memories/{memory_id}/")
def list_memories(
self,
@@ -222,7 +220,6 @@ class PlatformBackend(Backend):
payload["filters"] = api_filters
if enable_graph:
payload["enable_graph"] = True
payload["source"] = "CLI"
result = self._request("POST", "/v2/memories/", json=payload, params=params)
return (
@@ -239,7 +236,6 @@ class PlatformBackend(Backend):
payload["text"] = content
if metadata:
payload["metadata"] = metadata
payload["source"] = "CLI"
return self._request("PUT", f"/v1/memories/{memory_id}/", json=payload)
def delete(
@@ -253,7 +249,7 @@ class PlatformBackend(Backend):
run_id: str | None = None,
) -> dict:
if all:
params: dict[str, str] = {"source": "CLI"}
params: dict[str, str] = {}
if user_id:
params["user_id"] = user_id
if agent_id:
@@ -264,7 +260,7 @@ class PlatformBackend(Backend):
params["run_id"] = run_id
return self._request("DELETE", "/v1/memories/", params=params)
elif memory_id:
return self._request("DELETE", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
return self._request("DELETE", f"/v1/memories/{memory_id}/")
else:
raise ValueError("Either memory_id or --all is required")
@@ -289,9 +285,7 @@ class PlatformBackend(Backend):
# Delete each provided entity via the v2 path-based endpoint
result: dict = {}
for entity_type, entity_id in entities.items():
result = self._request(
"DELETE", f"/v2/entities/{entity_type}/{entity_id}/", params={"source": "CLI"}
)
result = self._request("DELETE", f"/v2/entities/{entity_type}/{entity_id}/")
return result
def ping(self, timeout: float | None = None) -> dict:
-12
View File
@@ -39,17 +39,11 @@ class DefaultsConfig:
enable_graph: bool = False
@dataclass
class TelemetryConfig:
anonymous_id: str = ""
@dataclass
class Mem0Config:
version: int = CONFIG_VERSION
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
platform: PlatformConfig = field(default_factory=PlatformConfig)
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
SHORT_KEY_ALIASES: dict[str, str] = {
@@ -93,9 +87,6 @@ def load_config() -> Mem0Config:
config.defaults.run_id = defaults.get("run_id", "")
config.defaults.enable_graph = defaults.get("enable_graph", False)
telemetry = data.get("telemetry", {})
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
# Environment variable overrides
env_key = os.environ.get("MEM0_API_KEY")
if env_key:
@@ -146,9 +137,6 @@ def save_config(config: Mem0Config) -> None:
"base_url": config.platform.base_url,
"user_email": config.platform.user_email,
},
"telemetry": {
"anonymous_id": config.telemetry.anonymous_id,
},
}
with open(CONFIG_FILE, "w") as f:
+4 -45
View File
@@ -9,14 +9,12 @@ Disable with: MEM0_TELEMETRY=false
from __future__ import annotations
import contextlib
import hashlib
import json
import os
import platform
import subprocess
import sys
import uuid
from typing import Any
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
@@ -28,31 +26,11 @@ def _is_telemetry_enabled() -> bool:
return val not in ("false", "0", "no")
def _get_or_create_anonymous_id() -> str:
"""Return a persistent per-machine anonymous ID, generating one if needed.
Stored in ~/.mem0/config.json under `telemetry.anonymous_id` so that
repeat runs on the same machine share one PostHog identity instead of
collapsing into a single shared fallback string.
"""
from mem0_cli.config import load_config, save_config
config = load_config()
if config.telemetry.anonymous_id:
return config.telemetry.anonymous_id
new_id = f"cli-anon-{uuid.uuid4().hex}"
config.telemetry.anonymous_id = new_id
with contextlib.suppress(Exception):
save_config(config)
return new_id
def _get_distinct_id() -> str:
"""Return a stable anonymous identifier for the current user.
Priority: cached user_email (from /v1/ping/) > MD5(api_key) >
persistent per-machine anonymous ID.
Priority: cached user_email (from /v1/ping/) > MD5(api_key) > fallback.
Matches the SDK pattern in mem0/client/main.py.
"""
try:
from mem0_cli.config import load_config
@@ -64,10 +42,7 @@ def _get_distinct_id() -> str:
return hashlib.md5(config.platform.api_key.encode()).hexdigest()
except Exception:
pass
try:
return _get_or_create_anonymous_id()
except Exception:
return f"cli-anon-{uuid.uuid4().hex}"
return "anonymous-cli"
def capture_event(
@@ -86,27 +61,12 @@ def capture_event(
try:
from mem0_cli import __version__
from mem0_cli.config import CONFIG_FILE, load_config, save_config
from mem0_cli.config import CONFIG_FILE, load_config
from mem0_cli.state import is_agent_mode
config = load_config()
distinct_id = pre_resolved_email or _get_distinct_id()
# Detect anonymous → identified transition. If a stored anonymous_id
# exists and we just resolved to a real identity, fire a one-shot
# $identify event so PostHog stitches the pre-signup history onto
# the authenticated profile. Clear the stored id so we don't re-alias.
anon_id_to_alias: str | None = None
if (
distinct_id
and not distinct_id.startswith("cli-anon-")
and config.telemetry.anonymous_id
):
anon_id_to_alias = config.telemetry.anonymous_id
config.telemetry.anonymous_id = ""
with contextlib.suppress(Exception):
save_config(config)
payload = {
"api_key": POSTHOG_API_KEY,
"distinct_id": distinct_id,
@@ -132,7 +92,6 @@ def capture_event(
"mem0_api_key": config.platform.api_key or "",
"mem0_base_url": config.platform.base_url or "https://api.mem0.ai",
"config_path": str(CONFIG_FILE),
"anon_distinct_id_to_alias": anon_id_to_alias,
}
subprocess.Popen(
@@ -27,31 +27,9 @@ def main() -> None:
if ctx.get("needs_email") and ctx.get("mem0_api_key"):
_resolve_and_cache_email(ctx, payload)
# Fire $identify *after* email resolution so PostHog links the stored
# anonymous id directly to the final identity (email, not the api-key
# hash). The regular event is sent next so it lands under the merged
# profile.
anon_id = ctx.get("anon_distinct_id_to_alias")
if anon_id:
_send_identify_event(ctx, payload, anon_id)
_send_posthog_event(ctx["posthog_host"], payload)
def _send_identify_event(ctx: dict, payload: dict, anon_id: str) -> None:
"""Send a PostHog $identify event aliasing anon_id → payload['distinct_id']."""
identify_payload = {
"api_key": payload["api_key"],
"event": "$identify",
"distinct_id": payload["distinct_id"],
"properties": {
"$anon_distinct_id": anon_id,
"$lib": payload.get("properties", {}).get("$lib", "posthog-python"),
},
}
_send_posthog_event(ctx["posthog_host"], identify_payload)
def _resolve_and_cache_email(ctx: dict, payload: dict) -> None:
"""Call /v1/ping/ to get the user's email, update the payload, and cache it."""
try:
@@ -47,6 +47,8 @@ Provide at least one message or direct memory string. Most callers supply `messa
| `messages` | array | No* | Conversation turns for Mem0 to infer memories from. Each object should include `role` and `content`. |
| `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. |
| `async_mode` | boolean (default `true`) | Optional | Controls asynchronous processing. Most clients leave this enabled. |
| `output_format` | string (default `v1.1`) | Optional | Response format. `v1.1` wraps results in a `results` array. |
> \* Provide at least one `messages` entry to describe what you are storing. For scoped memories, include `user_id`. You can also attach `agent_id`, `app_id`, `run_id`, `project_id`, or `org_id` to refine ownership.
@@ -81,3 +83,20 @@ Successful requests return an array of events queued for processing. Each event
```
</CodeGroup>
## Graph relationships
Add Memories can enrich the knowledge graph on write. Set `enable_graph: true` to create entity nodes and relationships for the stored memory. Use this when you want downstream `get_all` or search calls to traverse connected entities.
<CodeGroup>
```json Graph-aware request
{
"user_id": "alice",
"messages": [
{ "role": "user", "content": "I met with Dr. Lee at General Hospital." }
],
"enable_graph": true
}
```
</CodeGroup>
The response follows the same format, and related entities become available in [Graph Memory](/platform/features/graph-memory) queries.
+2 -1
View File
@@ -62,7 +62,8 @@ To retrieve graph memory relationships between entities, pass `output_format="v1
memories = client.get_all(
filters={
"user_id": "alex"
}
},
output_format="v1.1"
)
```
+11 -4
View File
@@ -32,7 +32,7 @@ Example with the mem0 Python package:
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
client = MemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID')
```
</Tab>
@@ -41,7 +41,10 @@ client = MemoryClient(api_key="your-api-key")
```javascript
import { MemoryClient } from "mem0ai";
const client = new MemoryClient({ apiKey: "your-api-key" });
const client = new MemoryClient({
organizationId: "YOUR_ORG_ID",
projectId: "YOUR_PROJECT_ID"
});
```
</Tab>
@@ -95,6 +98,9 @@ client.project.update(
custom_instructions="..."
)
# Enable graph memory for the project
client.project.update(enable_graph=True)
# Use the input language for memory storage and retrieval
client.project.update(multilingual=True)
@@ -105,6 +111,7 @@ client.project.update(
{"personal_info": "User personal information and preferences"},
{"work_context": "Professional context and work-related information"}
],
enable_graph=True,
multilingual=True
)
```
@@ -165,11 +172,11 @@ All project methods are available in async mode:
from mem0 import AsyncMemoryClient
async def manage_project():
client = AsyncMemoryClient(api_key="your-api-key")
client = AsyncMemoryClient(org_id='YOUR_ORG_ID', project_id='YOUR_PROJECT_ID')
# All methods support async/await
project_info = await client.project.get()
await client.project.update(multilingual=True)
await client.project.update(enable_graph=True)
members = await client.project.get_members()
# To call the async function properly
-14
View File
@@ -4,20 +4,6 @@ description: "Release notes for the OpenClaw plugin and agent harness."
mode: "wide"
---
<Update label="2026-04-11" description="v1.0.6">
**Bug Fixes:**
- **Telemetry:** Replaced shared `"anonymous-openclaw"` fallback with a persistent per-machine random hash (`openclaw-anon-<uuid>`), so anonymous plugin users are counted individually in PostHog ([#4790](https://github.com/mem0ai/mem0/pull/4790))
- **Telemetry:** Added PostHog `$identify` event on first authenticated run to stitch anonymous history onto the authenticated profile ([#4790](https://github.com/mem0ai/mem0/pull/4790))
- **Telemetry:** Fixed event loss on short-lived CLI invocations — added `beforeExit` handler to flush queued events before the process exits ([#4790](https://github.com/mem0ai/mem0/pull/4790))
- **Telemetry:** Added lazy `/v1/ping/` email resolution so users who configure API key outside `mem0 init` show as their email in PostHog, not an md5 hash ([#4790](https://github.com/mem0ai/mem0/pull/4790))
- **Telemetry:** Unified CLI event prefix from `openclaw.<cmd>` to `openclaw.cli.<cmd>` on the needsSetup branch to match the authenticated branch ([#4790](https://github.com/mem0ai/mem0/pull/4790))
**Improvements:**
- **API:** Added `source: "OPENCLAW"` to all provider calls (`add`, `search`, `getAll`) across tools, CLI commands, recall, and the OSS backend adapter ([#4790](https://github.com/mem0ai/mem0/pull/4790))
</Update>
<Update label="2026-04-07" description="v1.0.5">
**Bug Fixes:**
-11
View File
@@ -1160,17 +1160,6 @@ mode: "wide"
<Tab title="CLI">
<Update label="2026-04-11" description="Python v0.2.3 / Node v0.2.3">
**Bug Fixes:**
- **Telemetry:** Replaced shared `"anonymous-cli"` fallback with a persistent per-machine random hash (`cli-anon-<uuid>`), so anonymous CLI users are counted individually in PostHog instead of collapsing into one identity ([#4789](https://github.com/mem0ai/mem0/pull/4789))
- **Telemetry:** Added PostHog `$identify` event on first authenticated run to stitch pre-signup anonymous history onto the authenticated user profile ([#4789](https://github.com/mem0ai/mem0/pull/4789))
**Improvements:**
- **API:** All API calls now include `source=CLI` in request bodies (POST/PUT) and query params (GET/DELETE) for server-side attribution ([#4789](https://github.com/mem0ai/mem0/pull/4789))
</Update>
<Update label="2026-04-06" description="Python v0.2.2 / Node v0.2.2">
**New Features:**
+1 -1
View File
@@ -15,7 +15,7 @@ Mem0 supports LangChain as a provider for vector store integration. LangChain pr
```python Python
import os
from mem0 import Memory
from langchain_chroma import Chroma
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# Initialize a LangChain vector store
-21
View File
@@ -50,25 +50,4 @@ Here are the parameters available for configuring Valkey:
| `hnsw_m` | Number of bi-directional links for HNSW | `16` |
| `hnsw_ef_construction` | Size of dynamic candidate list for HNSW | `200` |
| `hnsw_ef_runtime` | Size of dynamic candidate list for search | `10` |
| `cluster_mode` | Enable cluster mode for Valkey cluster (CME) deployments | `false` |
| `distance_metric` | Distance metric for vector similarity | `cosine` |
## Cluster Mode
To use Valkey with cluster mode enabled (CME), set `cluster_mode` to `true`:
```python
config = {
"vector_store": {
"provider": "valkey",
"config": {
"collection_name": "memories",
"valkey_url": "valkey://cluster-endpoint:6379",
"embedding_model_dims": 1536,
"cluster_mode": True
}
}
}
```
When cluster mode is enabled, the connector uses `ValkeyCluster` instead of the standalone client, which handles `MOVED`/`ASK` redirections automatically. Search queries are coordinated across all shards by the valkey-search module's built-in coordinator. See the [valkey-search documentation](https://github.com/valkey-io/valkey-search) for details on cluster mode behavior.
@@ -129,13 +129,13 @@ async def search_memories(
user_id=USER_ID,
limit=5,
threshold=0.7, # Higher threshold for more relevant results
)
# Format and return the results
if not results.get('results', []):
return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results.get('results', [])]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
```
@@ -345,13 +345,13 @@ async def search_memories(
user_id=USER_ID,
limit=5,
threshold=0.7, # Higher threshold for more relevant results
)
# Format and return the results
if not results.get('results', []):
return "I don't have any relevant memories about this topic."
memories = [f"• {result['memory']}" for result in results.get('results', [])]
return "Here's what I remember that might be relevant:\n" + "\n".join(memories)
@@ -0,0 +1,70 @@
---
title: Browser Extension Memory
description: "Add Mem0's universal memory layer to Chrome chat surfaces."
---
Enhance your AI interactions with Mem0, a Chrome extension that introduces a universal memory layer across platforms like ChatGPT, Claude, and Perplexity. Mem0 ensures seamless context sharing, making your AI experiences more personalized and efficient.
<Note>
We now support Grok! The Mem0 Chrome Extension has been updated to work with Grok, bringing the same powerful memory capabilities to your Grok conversations.
</Note>
## Features
- **Universal Memory Layer**: Share context seamlessly across ChatGPT, Claude, Perplexity, and Grok.
- **Smart Context Detection**: Automatically captures relevant information from your conversations.
- **Intelligent Memory Retrieval**: Surfaces pertinent memories at the right time.
- **One-Click Sync**: Easily synchronize with existing ChatGPT memories.
- **Memory Dashboard**: Manage all your memories in one centralized location.
## Installation
You can install the Mem0 Chrome Extension using one of the following methods:
### Method 1: Chrome Web Store Installation
1. **Download the Extension**: Open Google Chrome and navigate to the [Mem0 Chrome Extension page](https://chromewebstore.google.com/detail/mem0/onihkkbipkfeijkadecaafbgagkhglop?hl=en).
2. **Add to Chrome**: Click on the "Add to Chrome" button.
3. **Confirm Installation**: In the pop-up dialog, click "Add extension" to confirm. The Mem0 icon should now appear in your Chrome toolbar.
### Method 2: Manual Installation
1. **Download the Extension**: Clone or download the extension files from the [Mem0 Chrome Extension GitHub repository](https://github.com/mem0ai/mem0-chrome-extension).
2. **Access Chrome Extensions**: Open Google Chrome and navigate to `chrome://extensions`.
3. **Enable Developer Mode**: Toggle the "Developer mode" switch in the top right corner.
4. **Load Unpacked Extension**: Click "Load unpacked" and select the directory containing the extension files.
5. **Confirm Installation**: The Mem0 Chrome Extension should now appear in your Chrome toolbar.
## Usage
1. **Locate the Mem0 Icon**: After installation, find the Mem0 icon in your Chrome toolbar.
2. **Sign In**: Click the icon and sign in with your Google account.
3. **Interact with AI Assistants**:
- **ChatGPT and Perplexity**: Continue your conversations as usual; Mem0 operates seamlessly in the background.
- **Claude**: Click the Mem0 button or use the shortcut `Ctrl + M` to activate memory functions.
## Configuration
- **API Key**: Obtain your API key from the Mem0 Dashboard to connect the extension to the Mem0 API.
- **User ID**: This is your unique identifier in the Mem0 system. If not provided, it defaults to `chrome-extension-user`.
## Demo Video
<iframe width="700" height="400" src="https://www.youtube.com/embed/dqenCMMlfwQ?si=zhGVrkq6IS_0Jwyj" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## Privacy and Data Security
Your messages are sent to the Mem0 API for extracting and retrieving memories. Mem0 is committed to ensuring your data's privacy and security.
---
<CardGroup cols={2}>
<Card title="Build a Mem0 Companion" icon="users" href="/cookbooks/essentials/building-ai-companion">
Learn the foundations of memory-powered assistants that work across platforms.
</Card>
<Card title="Multimodal Support" icon="image" href="/platform/features/multimodal-support">
Extend your browser interactions with vision and audio memory.
</Card>
</CardGroup>
+1 -1
View File
@@ -137,7 +137,7 @@ With Mem0 and AWS services like Bedrock, OpenSearch, and Neptune Analytics, you
<Card title="Neptune Analytics with Mem0" icon="database" href="/cookbooks/integrations/neptune-analytics">
Explore graph-based memory storage with AWS Neptune Analytics.
</Card>
<Card title="Graph Memory Features" icon="sitemap" href="/open-source/features/graph-memory">
<Card title="Graph Memory Features" icon="sitemap" href="/platform/features/graph-memory">
Learn how to leverage knowledge graphs for entity relationships.
</Card>
</CardGroup>
+4 -1
View File
@@ -201,7 +201,10 @@ Here are some examples of how Mem0 can be integrated into various applications:
>
Persistent personality for Eliza agents.
</Card>
</CardGroup>
<Card title="Browser Extension Memory" icon="globe" href="/cookbooks/frameworks/chrome-extension">
Universal memory layer for Chrome.
</Card>
</CardGroup>
---
@@ -85,6 +85,7 @@ const messages = [
await client.add(messages, {
user_id: "alice",
version: "v2",
});
```
</CodeGroup>
@@ -172,6 +173,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 |
| Graph writes | Toggle per request (`enable_graph=True`) | Requires configuring a graph provider |
| 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 |
@@ -120,7 +120,7 @@ import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
client.deleteAll({ userId: "alice" })
client.deleteAll({ user_id: "alice" })
.then(result => console.log(result))
.catch(error => console.error(error));
```
@@ -162,12 +162,12 @@ import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
// Delete all memories across every user in the project
client.deleteAll({ userId: "*" })
client.deleteAll({ user_id: "*" })
.then(result => console.log(result))
.catch(error => console.error(error));
// Full project wipe — all four filters must be explicitly set to "*"
client.deleteAll({ userId: "*", agentId: "*", appId: "*", runId: "*" })
client.deleteAll({ user_id: "*", agent_id: "*", app_id: "*", run_id: "*" })
.then(result => console.log(result))
.catch(error => console.error(error));
```
+21 -48
View File
@@ -70,6 +70,7 @@
"platform/features/v2-memory-filters",
"platform/features/entity-scoped-memory",
"platform/features/async-client",
"platform/features/async-mode-default-change",
"platform/features/multimodal-support",
"platform/features/custom-categories"
]
@@ -78,6 +79,7 @@
"group": "Advanced Features",
"icon": "bolt",
"pages": [
"platform/features/graph-memory",
"platform/features/graph-threshold",
"platform/features/advanced-retrieval",
"platform/advanced-memory-operations",
@@ -92,7 +94,8 @@
"pages": [
"platform/features/direct-import",
"platform/features/memory-export",
"platform/features/timestamp"
"platform/features/timestamp",
"platform/features/expiration-date"
]
},
{
@@ -119,6 +122,8 @@
"icon": "arrow-right",
"pages": [
"migration/oss-to-platform",
"migration/v0-to-v1",
"migration/breaking-changes",
"migration/api-changes"
]
},
@@ -128,6 +133,16 @@
"pages": [
"platform/contribute"
]
},
{
"group": "Release Notes",
"icon": "rocket",
"pages": [
"changelog/highlights",
"changelog/sdk",
"changelog/platform",
"changelog/openclaw"
]
}
]
},
@@ -167,7 +182,7 @@
"open-source/features/reranker-search",
"open-source/features/async-memory",
"open-source/features/multimodal-support",
"open-source/features/custom-instructions",
"open-source/features/custom-fact-extraction-prompt",
"open-source/features/custom-update-memory-prompt",
"open-source/features/rest-api",
"open-source/features/openai_compatibility"
@@ -373,6 +388,7 @@
"cookbooks/frameworks/llamaindex-multiagent",
"cookbooks/frameworks/multimodal-retrieval",
"cookbooks/frameworks/eliza-os-character",
"cookbooks/frameworks/chrome-extension",
"cookbooks/frameworks/gemini-3-with-mem0-mcp",
"cookbooks/frameworks/mirofish-swarm-memory"
]
@@ -546,21 +562,6 @@
]
}
]
},
{
"tab": "Release Notes",
"groups": [
{
"group": "Release Notes",
"icon": "rocket",
"pages": [
"changelog/highlights",
"changelog/sdk",
"changelog/platform",
"changelog/openclaw"
]
}
]
}
]
}
@@ -611,30 +612,6 @@
]
},
"redirects": [
{
"source": "/migration/breaking-changes",
"destination": "/"
},
{
"source": "/migration/v0-to-v1",
"destination": "/"
},
{
"source": "/platform/features/expiration-date",
"destination": "/"
},
{
"source": "/platform/features/async-mode-default-change",
"destination": "/"
},
{
"source": "/open-source/features/custom-fact-extraction-prompt",
"destination": "/open-source/features/custom-instructions"
},
{
"source": "/platform/features/graph-memory",
"destination": "/open-source/features/graph-memory"
},
{
"source": "/changelog",
"destination": "/changelog/highlights"
@@ -797,11 +774,7 @@
},
{
"source": "/examples/chrome-extension",
"destination": "/cookbooks/overview"
},
{
"source": "/cookbooks/frameworks/chrome-extension",
"destination": "/cookbooks/overview"
"destination": "/cookbooks/frameworks/chrome-extension"
},
{
"source": "/examples",
@@ -841,7 +814,7 @@
},
{
"source": "/v0x/examples/chrome-extension",
"destination": "/cookbooks/overview"
"destination": "/cookbooks/frameworks/chrome-extension"
},
{
"source": "/v0x/examples/youtube-assistant",
@@ -993,7 +966,7 @@
},
{
"source": "/features/graph-memory",
"destination": "/open-source/features/graph-memory"
"destination": "/platform/features/graph-memory"
},
{
"source": "/features/:slug",
+1 -1
View File
@@ -268,7 +268,7 @@ memories = mem0.search(
{"categories": {"contains": "travel"}}
]
},
top_k=5
limit=5
)
# Configure agent with custom model settings
+2 -1
View File
@@ -16,7 +16,7 @@ Combining Mem0 with Keywords AI allows you to:
4. Optimize token usage and reduce costs
<Note>
You can get your Mem0 API key from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a>.
You can get your Mem0 API key, user_id, and org_id from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a>. These are required for proper integration.
</Note>
## Setup and Configuration
@@ -107,6 +107,7 @@ response = client.chat.completions.create(
extra_body={
"mem0_params": {
"user_id": "test_user",
"org_id": "org_1",
"api_key": os.environ.get("MEM0_API_KEY"),
"add_memories": {
"messages": messages,
+4 -1
View File
@@ -29,7 +29,10 @@ import os
os.environ["MEM0_API_KEY"] = "your-api-key"
client = MemoryClient()
client = MemoryClient(
org_id=your_org_id,
project_id=your_project_id
)
```
## Available Tools
+2 -2
View File
@@ -45,7 +45,7 @@ mem0 = MemoryClient()
@function_tool
def search_memory(query: str, user_id: str) -> str:
"""Search through past conversations and memories"""
memories = mem0.search(query, user_id=user_id, top_k=3)
memories = mem0.search(query, user_id=user_id, limit=3)
if memories and memories.get('results'):
return "\n".join([f"- {mem['memory']}" for mem in memories['results']])
return "No relevant memories found."
@@ -215,7 +215,7 @@ Customize memory behavior:
memories = mem0.search(
query="travel preferences",
user_id="alex",
top_k=5 # Number of memories to retrieve
limit=5 # Number of memories to retrieve
)
# Add metadata to memories
+1 -1
View File
@@ -155,7 +155,7 @@ openclaw mem0 stats
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `customInstructions` | `string` | *(built-in)* | Extraction prompt for memory processing |
| `customPrompt` | `string` | *(built-in)* | Extraction prompt for memory processing |
| `oss.embedder.provider` | `string` | `"openai"` | Embedding provider (`"openai"`, `"ollama"`, etc.) |
| `oss.embedder.config` | `object` | — | Provider config: `apiKey`, `model`, `baseURL` |
| `oss.vectorStore.provider` | `string` | `"memory"` | Vector store (`"memory"`, `"qdrant"`, `"chroma"`, etc.) |
+1 -1
View File
@@ -52,7 +52,7 @@ npm install @mem0/vercel-ai-provider
> **Note**: The `openai` provider is set as default. Consider using `MEM0_API_KEY` and `OPENAI_API_KEY` as environment variables for security.
> **Note**: The `mem0Config` is optional. It is used to set the global config for the Mem0 Client (eg. `user_id`, `agent_id`, `app_id`, `run_id` etc).
> **Note**: The `mem0Config` is optional. It is used to set the global config for the Mem0 Client (eg. `user_id`, `agent_id`, `app_id`, `run_id`, `org_id`, `project_id` etc).
3. Add Memories to Enhance Context:
+2 -1
View File
@@ -86,7 +86,7 @@ Key differentiators:
- [Reranker Search](https://docs.mem0.ai/open-source/features/reranker-search): Enhanced search results with reranking models
- [Async Memory](https://docs.mem0.ai/open-source/features/async-memory): Asynchronous memory operations for better performance
- [Multimodal Support](https://docs.mem0.ai/open-source/features/multimodal-support): Handle text, images, and documents in self-hosted setup
- [Custom Instructions](https://docs.mem0.ai/open-source/features/custom-instructions): Tailor information extraction for specific use cases
- [Custom Fact Extraction](https://docs.mem0.ai/open-source/features/custom-fact-extraction-prompt): Tailor information extraction for specific use cases
- [Custom Memory Update Prompt](https://docs.mem0.ai/open-source/features/custom-update-memory-prompt): Customize how memories are updated and merged
- [REST API Server](https://docs.mem0.ai/open-source/features/rest-api): FastAPI-based server with core operations and OpenAPI documentation
- [OpenAI Compatibility](https://docs.mem0.ai/open-source/features/openai_compatibility): Seamless integration with OpenAI-compatible APIs
@@ -245,6 +245,7 @@ Key differentiators:
- [LlamaIndex Multiagent](https://docs.mem0.ai/cookbooks/frameworks/llamaindex-multiagent): Multi-agent systems with shared memory
- [Multimodal Retrieval](https://docs.mem0.ai/cookbooks/frameworks/multimodal-retrieval): Memory systems handling text, images, and documents
- [Eliza OS Character](https://docs.mem0.ai/cookbooks/frameworks/eliza-os-character): Character-based AI with persistent personality
- [Chrome Extension](https://docs.mem0.ai/cookbooks/frameworks/chrome-extension): Browser extensions that remember user interactions
- [Gemini with Mem0 MCP](https://docs.mem0.ai/cookbooks/frameworks/gemini-3-with-mem0-mcp): Google Gemini integration using MCP server
- [Mirofish Swarm Memory](https://docs.mem0.ai/cookbooks/frameworks/mirofish-swarm-memory): Swarm-based multi-agent memory patterns
+2 -2
View File
@@ -319,7 +319,7 @@ config = {
"graph_store": {...},
"version": "v1.0", # ❌ v1.0 no longer supported
"history_db_path": "...",
"custom_instructions": "..."
"custom_fact_extraction_prompt": "..."
}
```
@@ -336,7 +336,7 @@ config = {
},
"version": "v1.1", # ✅ v1.1+ only
"history_db_path": "...",
"custom_instructions": "...",
"custom_fact_extraction_prompt": "...",
"custom_update_memory_prompt": "..." # ✅ NEW: Custom update prompt
}
```
+383
View File
@@ -0,0 +1,383 @@
---
title: Breaking Changes in v1.0.0
description: 'Complete list of breaking changes when upgrading from v0.x to v1.0.0 '
icon: "triangle-exclamation"
iconType: "solid"
---
<Warning>
**Important:** This page lists all breaking changes. Please review carefully before upgrading.
</Warning>
## API Version Changes
### Removed v1.0 API Support
**Breaking Change:** The v1.0 API format is completely removed and no longer supported.
#### Before (v0.x)
```python
# This was supported in v0.x
config = {
"version": "v1.0" # ❌ No longer supported
}
result = m.add(
"memory content",
user_id="alice"
)
```
#### After (v1.0.0 )
```python
# v1.1 is the minimum supported version
config = {
"version": "v1.1" # ✅ Required minimum
}
result = m.add(
"memory content",
user_id="alice"
)
```
**Error Message:**
```
ValueError: The v1.0 API format is no longer supported in mem0ai 1.0.0+.
Please use v1.1 format which returns a dict with 'results' key.
```
## Parameter Removals
### 1. version Parameter in Method Calls
**Breaking Change:** Version parameter removed from method calls.
#### Before (v0.x)
```python
result = m.add("content", user_id="alice", version="v1.0")
```
#### After (v1.0.0 )
```python
result = m.add("content", user_id="alice")
```
### 2. async_mode Parameter (Platform Client)
**Change:** For `MemoryClient` (Platform API), `async_mode` now defaults to `True` but can still be configured.
#### Before (v0.x)
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-key")
result = client.add("content", user_id="alice", async_mode=True)
result = client.add("content", user_id="alice", async_mode=False)
```
#### After (v1.0.0 )
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-key")
# async_mode now defaults to True, but you can still override it
result = client.add("content", user_id="alice") # Uses async_mode=True by default
# You can still explicitly set it to False if needed
result = client.add("content", user_id="alice", async_mode=False)
```
## Response Format Changes
### Standardized Response Structure
**Breaking Change:** All responses now return a standardized dictionary format.
#### Before (v0.x)
```python
# Could return different formats based on version configuration
result = m.add("content", user_id="alice")
# With v1.0: Returns [{"id": "...", "memory": "...", "event": "ADD"}]
# With v1.1: Returns {"results": [{"id": "...", "memory": "...", "event": "ADD"}]}
```
#### After (v1.0.0 )
```python
# Always returns standardized format
result = m.add("content", user_id="alice")
# Always returns: {"results": [{"id": "...", "memory": "...", "event": "ADD"}]}
# Access results consistently
for memory in result["results"]:
print(memory["memory"])
```
## Configuration Changes
### Version Configuration
**Breaking Change:** Default API version changed.
#### Before (v0.x)
```python
# v1.0 was supported
config = {
"version": "v1.0" # ❌ No longer supported
}
```
#### After (v1.0.0 )
```python
# v1.1 is minimum, v1.1 is default
config = {
"version": "v1.1" # ✅ Minimum supported
}
# Or omit for default
config = {
# version defaults to v1.1
}
```
### Memory Configuration
**Breaking Change:** Some configuration options have changed defaults.
#### Before (v0.x)
```python
from mem0 import Memory
# Default configuration in v0.x
m = Memory() # Used default settings suitable for v0.x
```
#### After (v1.0.0 )
```python
from mem0 import Memory
# Default configuration optimized for v1.0.0
m = Memory() # Uses v1.1+ optimized defaults
# Explicit configuration recommended
config = {
"version": "v1.1",
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
}
}
m = Memory.from_config(config)
```
## Method Signature Changes
### Search Method
**Enhanced but backward compatible:**
#### Before (v0.x)
```python
results = m.search(
"query",
user_id="alice",
filters={"key": "value"} # Simple key-value only
)
```
#### After (v1.0.0 )
```python
# Basic usage remains the same
results = m.search("query", user_id="alice")
# Enhanced filtering available (optional)
results = m.search(
"query",
user_id="alice",
filters={
"AND": [
{"key": "value"},
{"score": {"gte": 0.8}}
]
},
rerank=True # New parameter
)
```
## Error Handling Changes
### New Error Types
**Breaking Change:** More specific error types and messages.
#### Before (v0.x)
```python
try:
result = m.add("content", user_id="alice", version="v1.0")
except Exception as e:
print(f"Generic error: {e}")
```
#### After (v1.0.0 )
```python
try:
result = m.add("content", user_id="alice")
except ValueError as e:
if "v1.0 API format is no longer supported" in str(e):
# Handle version error specifically
print("Please upgrade your code to use v1.1+ format")
else:
print(f"Value error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
```
### Validation Changes
**Breaking Change:** Stricter parameter validation.
#### Before (v0.x)
```python
# Some invalid parameters might have been ignored
result = m.add(
"content",
user_id="alice",
invalid_param="ignored" # Might have been silently ignored
)
```
#### After (v1.0.0 )
```python
# Strict validation - unknown parameters cause errors
try:
result = m.add(
"content",
user_id="alice",
invalid_param="value" # ❌ Will raise TypeError
)
except TypeError as e:
print(f"Invalid parameter: {e}")
```
## Import Changes
### No Breaking Changes in Imports
**Good News:** Import statements remain the same.
```python
# These imports work in both v0.x and v1.0.0
from mem0 import Memory, AsyncMemory
from mem0 import MemoryConfig
```
## Dependency Changes
### Minimum Python Version
**Potential Breaking Change:** Check Python version requirements.
#### Before (v0.x)
- Python 3.8+ supported
#### After (v1.0.0 )
- Python 3.9+ required (check current requirements)
### Package Dependencies
**Breaking Change:** Some dependencies updated with potential breaking changes.
```bash
# Check for conflicts after upgrade
pip install --upgrade mem0ai
pip check # Verify no dependency conflicts
```
## Data Migration
### Database Schema
**Good News:** No database schema changes required.
- Existing memories remain compatible
- No data migration required
- Vector store data unchanged
### Memory Format
**Good News:** Memory storage format unchanged.
- Existing memories work with v1.0.0
- Search continues to work with old memories
- No re-indexing required
## Testing Changes
### Test Updates Required
**Breaking Change:** Update tests for new response format.
#### Before (v0.x)
```python
def test_add_memory():
result = m.add("content", user_id="alice")
assert isinstance(result, list) # ❌ No longer true
assert len(result) > 0
```
#### After (v1.0.0 )
```python
def test_add_memory():
result = m.add("content", user_id="alice")
assert isinstance(result, dict) # ✅ Always dict
assert "results" in result # ✅ Always has results key
assert len(result["results"]) > 0
```
## Rollback Considerations
### Safe Rollback Process
If you need to rollback:
```bash
# 1. Rollback package
pip install mem0ai==0.1.20 # Last stable v0.x
# 2. Revert code changes
git checkout previous_commit
# 3. Test functionality
python test_mem0_functionality.py
```
### Data Safety
- **Safe:** Memories stored in v0.x format work with v1.0.0
- **Safe:** Rollback doesn't lose data
- **Safe:** Vector store data remains intact
## Next Steps
1. **Review all breaking changes** in your codebase
2. **Update method calls** to remove deprecated parameters
3. **Update response handling** to use standardized format
4. **Test thoroughly** with your existing data
5. **Update error handling** for new error types
<CardGroup cols={2}>
<Card title="Migration Guide" icon="arrow-right" href="/migration/v0-to-v1">
Step-by-step migration instructions
</Card>
<Card title="API Changes" icon="code" href="/migration/api-changes">
Complete API reference changes
</Card>
</CardGroup>
<Warning>
**Need Help?** If you encounter issues during migration, check our [GitHub Discussions](https://github.com/mem0ai/mem0/discussions) or community support channels.
</Warning>
+6 -10
View File
@@ -81,10 +81,6 @@ client = MemoryClient(api_key="m0-...")
**Critical Change**: Platform uses v2 endpoints that require filtering parameters to be nested inside a `filters` dictionary.
</Warning>
<Note>
The `limit` parameter has been removed in favor of `top_k` across all SDKs. Update any code using `limit=` to use `top_k=` instead.
</Note>
| Method | Open Source | Platform |
| ------ | ----------- | -------- |
| `search()` | `m.search(query, user_id="alex")` | `client.search(query, filters={"user_id": "alex"})` |
@@ -125,18 +121,18 @@ Note: `add()` and `delete()` methods remain unchanged. The `update()` method is
<CodeGroup>
```python Open Source (Old)
# Get all memories for a user
memories = m.get_all(user_id="alex", top_k=10)
memories = m.get_all(user_id="alex", limit=10)
# Get memories with pagination
memories = m.get_all(user_id="alex", top_k=5, offset=10)
memories = m.get_all(user_id="alex", limit=5, offset=10)
```
```python Platform (New)
# Get all memories for a user
memories = client.get_all(filters={"user_id": "alex"}, top_k=10)
memories = client.get_all(filters={"user_id": "alex"}, limit=10)
# Get memories with pagination
memories = client.get_all(filters={"user_id": "alex"}, top_k=5, offset=10)
memories = client.get_all(filters={"user_id": "alex"}, limit=5, offset=10)
```
</CodeGroup>
</Accordion>
@@ -287,7 +283,7 @@ The Platform introduces powerful capabilities not available in OSS:
"user preferences",
filters={"user_id": "alex"},
rerank=True, # Platform exclusive
top_k=5
limit=5
)
# Search with keyword expansion
@@ -339,7 +335,7 @@ The Platform introduces powerful capabilities not available in OSS:
{"timestamp": {"gte": "2024-01-01"}}
]
},
top_k=100
limit=100
)
# Monitor usage patterns
+481
View File
@@ -0,0 +1,481 @@
---
title: Migrating from v0.x to v1.0.0
description: 'Complete guide to upgrade your Mem0 implementation to version 1.0.0 '
icon: "arrow-right"
iconType: "solid"
---
<Warning>
**Breaking Changes Ahead!** Mem0 1.0.0 introduces several breaking changes. Please read this guide carefully before upgrading.
</Warning>
## Overview
Mem0 1.0.0 is a major release that modernizes the API, improves performance, and adds powerful new features. This guide will help you migrate your existing v0.x implementation to the new version.
## Key Changes Summary
| Feature | v0.x | v1.0.0 | Migration Required |
|---------|------|-------------|-------------------|
| API Version | v1.0 supported | v1.0 **removed**, v1.1+ only | ✅ Yes |
| Async Mode (Platform Client) | Optional/manual | Defaults to `True`, configurable | ⚠️ Partial |
| Metadata Filtering | Basic | Enhanced with operators | ⚠️ Optional |
| Reranking | Not available | Full support | ⚠️ Optional |
## Step-by-Step Migration
### 1. Update Installation
```bash
# Update to the latest version
pip install --upgrade mem0ai
```
### 2. Remove Deprecated Parameters
#### Before (v0.x)
```python
from mem0 import Memory
# These parameters are no longer supported
m = Memory()
result = m.add(
"I love pizza",
user_id="alice",
version="v1.0" # ❌ REMOVED
)
```
#### After (v1.0.0 )
```python
from mem0 import Memory
# Clean, simplified API
m = Memory()
result = m.add(
"I love pizza",
user_id="alice"
# version parameter removed
)
```
### 3. Update Configuration
#### Before (v0.x)
```python
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"version": "v1.0" # ❌ No longer supported
}
m = Memory.from_config(config)
```
#### After (v1.0.0 )
```python
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"version": "v1.1" # ✅ v1.1 is the minimum supported version
}
m = Memory.from_config(config)
```
### 4. Handle Response Format Changes
#### Before (v0.x)
```python
# Response could be a list or dict depending on version
result = m.add("I love coffee", user_id="alice")
if isinstance(result, list):
# Handle list format
for item in result:
print(item["memory"])
else:
# Handle dict format
print(result["results"])
```
#### After (v1.0.0 )
```python
# Response is always a standardized dict with "results" key
result = m.add("I love coffee", user_id="alice")
# Always access via "results" key
for item in result["results"]:
print(item["memory"])
```
### 5. Update Search Operations
#### Before (v0.x)
```python
# Basic search
results = m.search("What do I like?", user_id="alice")
# With filters
results = m.search(
"What do I like?",
user_id="alice",
filters={"category": "food"}
)
```
#### After (v1.0.0 )
```python
# Same basic search API
results = m.search("What do I like?", user_id="alice")
# Enhanced filtering with operators (optional upgrade)
results = m.search(
"What do I like?",
user_id="alice",
filters={
"AND": [
{"category": "food"},
{"rating": {"gte": 8}}
]
}
)
# New: Reranking support (optional)
results = m.search(
"What do I like?",
user_id="alice",
rerank=True # Requires reranker configuration
)
```
### 6. Platform Client async_mode Default Changed
**Change:** For `MemoryClient`, the `async_mode` parameter now defaults to `True` for better performance.
#### Before (v0.x)
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-key")
# Had to explicitly set async_mode
result = client.add("I enjoy hiking", user_id="alice", async_mode=True)
```
#### After (v1.0.0 )
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-key")
# async_mode now defaults to True (best performance)
result = client.add("I enjoy hiking", user_id="alice")
# You can still override if needed for synchronous processing
result = client.add("I enjoy hiking", user_id="alice", async_mode=False)
```
## Configuration Migration
### Basic Configuration
#### Before (v0.x)
```python
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-3.5-turbo",
"api_key": "your-key"
}
},
"version": "v1.0"
}
```
#### After (v1.0.0 )
```python
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-3.5-turbo",
"api_key": "your-key"
}
},
"version": "v1.1", # Minimum supported version
# New optional features
"reranker": {
"provider": "cohere",
"config": {
"model": "rerank-english-v3.0",
"api_key": "your-cohere-key"
}
}
}
```
### Enhanced Features (Optional)
```python
# Take advantage of new features
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4",
"api_key": "your-key"
}
},
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small",
"api_key": "your-key"
}
},
"reranker": {
"provider": "sentence_transformer",
"config": {
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2"
}
},
"version": "v1.1"
}
```
## Error Handling Migration
### Before (v0.x)
```python
try:
result = m.add("memory", user_id="alice", version="v1.0")
except Exception as e:
print(f"Error: {e}")
```
### After (v1.0.0 )
```python
try:
result = m.add("memory", user_id="alice")
except ValueError as e:
if "v1.0 API format is no longer supported" in str(e):
print("Please upgrade your code to use v1.1+ format")
else:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
```
## Testing Your Migration
### 1. Basic Functionality Test
```python
def test_basic_functionality():
m = Memory()
# Test add
result = m.add("I love testing", user_id="test_user")
assert "results" in result
assert len(result["results"]) > 0
# Test search
search_results = m.search("testing", user_id="test_user")
assert "results" in search_results
# Test get_all
all_memories = m.get_all(user_id="test_user")
assert "results" in all_memories
print("✅ Basic functionality test passed")
test_basic_functionality()
```
### 2. Enhanced Features Test
```python
def test_enhanced_features():
config = {
"reranker": {
"provider": "sentence_transformer",
"config": {
"model": "cross-encoder/ms-marco-MiniLM-L-6-v2"
}
}
}
m = Memory.from_config(config)
# Test reranking
m.add("I love advanced features", user_id="test_user")
results = m.search("features", user_id="test_user", rerank=True)
assert "results" in results
# Test enhanced filtering
results = m.search(
"features",
user_id="test_user",
filters={"user_id": {"eq": "test_user"}}
)
assert "results" in results
print("✅ Enhanced features test passed")
test_enhanced_features()
```
## Common Migration Issues
### Issue 1: Version Error
**Error:**
```
ValueError: The v1.0 API format is no longer supported in mem0ai 1.0.0+
```
**Solution:**
```python
# Remove version parameters or set to v1.1+
config = {
# ... other config
"version": "v1.1" # or remove entirely for default
}
```
### Issue 2: Response Format Error
**Error:**
```
KeyError: 'results'
```
**Solution:**
```python
# Always access response via "results" key
result = m.add("memory", user_id="alice")
memories = result["results"] # Not result directly
```
### Issue 3: Parameter Error
**Error:**
```
TypeError: add() got an unexpected keyword argument 'output_format'
```
**Solution:**
```python
# Remove deprecated parameters
result = m.add(
"memory",
user_id="alice"
# Remove: version
)
```
## Rollback Plan
If you encounter issues during migration:
### 1. Immediate Rollback
```bash
# Downgrade to last v0.x version
pip install mem0ai==0.1.20 # Replace with your last working version
```
### 2. Gradual Migration
```python
# Test both versions side by side
import mem0_v0 # Your old version
import mem0 # New version
def compare_results(query, user_id):
old_results = mem0_v0.search(query, user_id=user_id)
new_results = mem0.search(query, user_id=user_id)
print("Old format:", old_results)
print("New format:", new_results["results"])
```
## Performance Improvements
### Before (v0.x)
```python
# Sequential operations
result1 = m.add("memory 1", user_id="alice")
result2 = m.add("memory 2", user_id="alice")
result3 = m.search("query", user_id="alice")
```
### After (v1.0.0 )
```python
# Better async performance
async def batch_operations():
async_memory = AsyncMemory()
# Concurrent operations
results = await asyncio.gather(
async_memory.add("memory 1", user_id="alice"),
async_memory.add("memory 2", user_id="alice"),
async_memory.search("query", user_id="alice")
)
return results
```
## Next Steps
1. **Complete the migration** using this guide
2. **Test thoroughly** with your existing data
3. **Explore new features** like enhanced filtering and reranking
4. **Update your documentation** to reflect the new API
5. **Monitor performance** and optimize as needed
<CardGroup cols={2}>
<Card title="Breaking Changes" icon="triangle-exclamation" href="/migration/breaking-changes">
Detailed list of all breaking changes
</Card>
<Card title="API Changes" icon="code" href="/migration/api-changes">
Complete API reference changes
</Card>
</CardGroup>
<Info>
Need help with migration? Check our [GitHub Discussions](https://github.com/mem0ai/mem0/discussions) or reach out to our community for support.
</Info>
+2 -2
View File
@@ -240,7 +240,7 @@ async_openai_client = AsyncOpenAI()
async_memory = AsyncMemory()
async def chat_with_memories(message: str, user_id: str = "default_user") -> str:
search_result = await async_memory.search(query=message, user_id=user_id, top_k=3)
search_result = await async_memory.search(query=message, user_id=user_id, limit=3)
relevant_memories = search_result["results"]
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories)
@@ -326,7 +326,7 @@ async def add_memory(messages: list, user_id: str):
@app.get("/memories/search")
async def search_memories(query: str, user_id: str, limit: int = 10):
try:
result = await memory.search(query=query, user_id=user_id, top_k=limit)
result = await memory.search(query=query, user_id=user_id, limit=limit)
return {"status": "success", "data": result}
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))
@@ -1,13 +1,13 @@
---
title: Custom Instructions
title: Custom Fact Extraction Prompt
description: Tailor fact extraction so Mem0 stores only the details you care about.
icon: "wand-magic-sparkles"
---
Custom instructions let you decide exactly which facts Mem0 records from a conversation. Define a focused prompt, give a few examples, and Mem0 will add only the memories that match your use case.
Custom fact extraction prompts let you decide exactly which facts Mem0 records from a conversation. Define a focused prompt, give a few examples, and Mem0 will add only the memories that match your use case.
<Info>
**You'll use this when...**
**You’ll use this when…**
- A project needs domain-specific facts (order numbers, customer info) without storing casual chatter.
- You already have a clear schema for memories and want the LLM to follow it.
- You must prevent irrelevant details from entering long-term storage.
@@ -17,10 +17,6 @@ Custom instructions let you decide exactly which facts Mem0 records from a conve
Prompts that are too broad cause unrelated facts to slip through. Keep instructions tight and test them with real transcripts.
</Warning>
<Note>
The `custom_fact_extraction_prompt` parameter has been renamed to `custom_instructions`. If you are upgrading from an older version, update your configuration accordingly.
</Note>
---
## Feature anatomy
@@ -28,13 +24,13 @@ Custom instructions let you decide exactly which facts Mem0 records from a conve
- **Prompt instructions:** Describe which entities or phrases to keep. Specific guidance keeps the extractor focused.
- **Few-shot examples:** Show positive and negative cases so the model copies the right format.
- **Structured output:** Responses return JSON with a `facts` array that Mem0 converts into individual memories.
- **LLM configuration:** `custom_instructions` (Python) or `customInstructions` (TypeScript) lives alongside your model settings.
- **LLM configuration:** `custom_fact_extraction_prompt` (Python) or `customPrompt` (TypeScript) lives alongside your model settings.
<AccordionGroup>
<Accordion title="Prompt blueprint">
1. State the allowed fact types.
2. Include short examples that mirror production messages.
3. Show both empty (`[]`) and populated outputs.
1. State the allowed fact types.
2. Include short examples that mirror production messages.
3. Show both empty (`[]`) and populated outputs.
4. Remind the model to return JSON with a `facts` key only.
</Accordion>
</AccordionGroup>
@@ -47,8 +43,8 @@ Custom instructions let you decide exactly which facts Mem0 records from a conve
<CodeGroup>
```python Python
custom_instructions = """
Please only extract entities containing customer support information, order details, and user information.
custom_fact_extraction_prompt = """
Please only extract entities containing customer support information, order details, and user information.
Here are some few shot examples:
Input: Hi.
@@ -71,8 +67,8 @@ Return the facts and customer information in a json format as shown above.
```
```ts TypeScript
const customInstructions = `
Please only extract entities containing customer support information, order details, and user information.
const customPrompt = `
Please only extract entities containing customer support information, order details, and user information.
Here are some few shot examples:
Input: Hi.
@@ -114,7 +110,7 @@ config = {
"max_tokens": 2000,
}
},
"custom_instructions": custom_instructions,
"custom_fact_extraction_prompt": custom_fact_extraction_prompt,
"version": "v1.1"
}
@@ -135,7 +131,7 @@ const config = {
maxTokens: 1500,
},
},
customInstructions: customInstructions,
customPrompt: customPrompt,
};
const memory = new Memory(config);
@@ -263,7 +263,7 @@ Please note to return the IDs in the output from the input IDs only and do not g
- Log each decision so product teams can review why a change happened.
<Note>
The prompt works alongside `custom_instructions`—fact extraction identifies candidate facts, and the update prompt decides how to merge them into long-term storage.
The prompt works alongside `custom_fact_extraction_prompt`—fact extraction identifies candidate facts, and the update prompt decides how to merge them into long-term storage.
</Note>
---
@@ -288,7 +288,7 @@ Please note to return the IDs in the output from the input IDs only and do not g
## Compare prompts
| Feature | `custom_update_memory_prompt` | `custom_instructions` |
| Feature | `custom_update_memory_prompt` | `custom_fact_extraction_prompt` |
| --- | --- | --- |
| Primary job | Decide memory actions (ADD/UPDATE/DELETE/NONE) | Pull facts from user and assistant messages |
| Inputs | Retrieved facts + existing memory entries | Raw conversation turns |
@@ -297,7 +297,7 @@ Please note to return the IDs in the output from the input IDs only and do not g
---
<CardGroup cols={2}>
<Card title="Design Fact Extraction" icon="sparkles" href="/open-source/features/custom-instructions">
<Card title="Design Fact Extraction" icon="sparkles" href="/open-source/features/custom-fact-extraction-prompt">
Coordinate both prompts so fact extraction feeds clean inputs into the update flow.
</Card>
<Card title="Build Email Automations" icon="inbox" href="/cookbooks/operations/email-automation">
+18 -4
View File
@@ -94,7 +94,7 @@ memory.add(conversation, user_id="demo-user")
results = memory.search(
"Who did Alice meet at GraphConf?",
user_id="demo-user",
top_k=3,
limit=3,
rerank=True,
)
@@ -123,6 +123,7 @@ export NEO4J_PASSWORD="your-password"
import { Memory } from "mem0ai/oss";
const config = {
enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
@@ -145,7 +146,7 @@ await memory.add(conversation, { userId: "demo-user" });
const results = await memory.search(
"Who did Alice meet at GraphConf?",
{ userId: "demo-user", topK: 3, rerank: true }
{ userId: "demo-user", limit: 3, rerank: true }
);
results.results.forEach((hit) => {
@@ -195,6 +196,7 @@ memory = Memory.from_config(config_dict=config)
import { Memory } from "mem0ai/oss";
const config = {
enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
@@ -202,7 +204,7 @@ const config = {
username: process.env.NEO4J_USERNAME!,
password: process.env.NEO4J_PASSWORD!,
},
customInstructions: "Please only capture people, organisations, and project links.",
customPrompt: "Please only capture people, organisations, and project links.",
}
};
@@ -215,6 +217,14 @@ const memory = new Memory(config);
```python
config["graph_store"]["config"]["threshold"] = 0.75
```
</Accordion>
<Accordion title="Toggle graph writes per request">
Disable graph writes or reads when you only want vector behaviour.
```python
memory.add(messages, user_id="demo-user", enable_graph=False)
results = memory.search("marketing partners", user_id="demo-user", enable_graph=False)
```
</Accordion>
<Accordion title="Organize multi-agent graphs">
@@ -247,12 +257,15 @@ Monitor graph growth, especially on free tiers, by periodically cleaning dormant
<Accordion title="Neptune Analytics rejects requests">
Ensure the graph identifier matches the vector dimension used by your embedder and that the IAM role allows `neptune-graph:*DataViaQuery` actions.
</Accordion>
<Accordion title="Graph store outage fallback">
Catch the provider error and retry with `enable_graph=False` so vector-only search keeps serving responses while the graph backend recovers.
</Accordion>
</AccordionGroup>
## Decision Points
- Select the graph store that fits your deployment (managed Aura vs. self-hosted Neo4j vs. AWS Neptune vs. local Kuzu vs. Apache AGE on PostgreSQL).
- Decide whether to include a graph store in your config; routine conversations may stay vector-only to save latency.
- Decide when to enable graph writes per request; routine conversations may stay vector-only to save latency.
- Set a policy for pruning stale relationships so your graph stays fast and affordable.
## Provider setup
@@ -267,6 +280,7 @@ Choose your backend and expand the matching panel for configuration details and
import { Memory } from "mem0ai/oss";
const config = {
enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
@@ -81,7 +81,7 @@ const messages = [
}
];
await client.add(messages, { userId: "alice" });
await client.add(messages, { user_id: "alice" });
```
</CodeGroup>
@@ -148,7 +148,7 @@ const messages = [
}
];
await client.add(messages, { userId: "alice" });
await client.add(messages, { user_id: "alice" });
```
</CodeGroup>
@@ -264,7 +264,7 @@ try {
}
}];
await client.add(messages, { userId: "user123" });
await client.add(messages, { user_id: "user123" });
console.log("Image processed successfully");
} catch (error: any) {
if (error.type === "invalid_image") {
@@ -131,7 +131,7 @@ print(response.choices[0].message.content)
| `run_id` | `str` | Optional session/run identifier for short-lived flows. |
| `metadata` | `dict` | Store extra fields alongside each memory entry. |
| `filters` | `dict` | Restrict retrieval to specific memories while responding. |
| `top_k` | `int` | Cap how many memories Mem0 pulls into the context (default 10). |
| `limit` | `int` | Cap how many memories Mem0 pulls into the context (default 10). |
Other request fields mirror OpenAI’s chat completion API.
+1 -1
View File
@@ -30,7 +30,7 @@ Mem0 Open Source ships with capabilities that adapt memory behavior for producti
<Card title="Multimodal Support" icon="image" href="/open-source/features/multimodal-support">
Process images, audio, and video memories.
</Card>
<Card title="Custom Instructions" icon="wand-magic-sparkles" href="/open-source/features/custom-instructions">
<Card title="Custom Fact Extraction" icon="wand-magic-sparkles" href="/open-source/features/custom-fact-extraction-prompt">
Tailor how facts are extracted from text.
</Card>
</CardGroup>
@@ -321,7 +321,7 @@ results = m.search(
]
},
rerank=True,
top_k=20
limit=20
)
```
@@ -366,7 +366,7 @@ results = m.search(
user_id="reader123",
filters={"content_type": "book_recommendation"},
rerank=True,
top_k=10
limit=10
)
for result in results["results"]:
+10 -2
View File
@@ -209,6 +209,14 @@ Mem0 offers granular configuration across vector stores, LLMs, embedders, and hi
| `topP` | Probability threshold | All |
| `topK` | Token count to keep | All |
| `openaiBaseUrl` | Base URL override | OpenAI |
</Accordion>
<Accordion title="Graph store">
| Parameter | Description | Default |
| --- | --- | --- |
| `provider` | Graph store provider (e.g., `"neo4j"`) | `"neo4j"` |
| `url` | Connection URL | `process.env.NEO4J_URL` |
| `username` | Username | `process.env.NEO4J_USERNAME` |
| `password` | Password | `process.env.NEO4J_PASSWORD` |
</Accordion>
<Accordion title="Embedder">
| Parameter | Description | Default |
@@ -222,7 +230,7 @@ Mem0 offers granular configuration across vector stores, LLMs, embedders, and hi
| --- | --- | --- |
| `historyDbPath` | Path to history database | `"{mem0_dir}/history.db"` |
| `version` | API version | `"v1.0"` |
| `customInstructions` | Custom processing prompt | `undefined` |
| `customPrompt` | Custom processing prompt | `undefined` |
</Accordion>
<Accordion title="History store">
| Parameter | Description | Default |
@@ -265,7 +273,7 @@ const config = {
}
},
disableHistory: false,
customInstructions: "I'm a virtual assistant. I'm here to help you with your queries."
customPrompt: "I'm a virtual assistant. I'm here to help you with your queries."
};
```
</Accordion>
+37 -38
View File
@@ -183,7 +183,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\nusers = client.users()\nprint(users)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\nusers = client.users()\nprint(users)"
},
{
"lang": "JavaScript",
@@ -717,7 +717,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\njson_schema = {pydantic_json_schema}\nfilters = {\n \"AND\": [\n {\"user_id\": \"alex\"}\n ]\n}\n\nresponse = client.create_memory_export(\n schema=json_schema,\n filters=filters\n)\nprint(response)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\njson_schema = {pydantic_json_schema}\nfilters = {\n \"AND\": [\n {\"user_id\": \"alex\"}\n ]\n}\n\nresponse = client.create_memory_export(\n schema=json_schema,\n filters=filters\n)\nprint(response)"
},
{
"lang": "JavaScript",
@@ -845,7 +845,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmemory_export_id = \"<memory_export_id>\"\n\nresponse = client.get_memory_export(memory_export_id=memory_export_id)\nprint(response)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"project_id\")\n\nmemory_export_id = \"<memory_export_id>\"\n\nresponse = client.get_memory_export(memory_export_id=memory_export_id)\nprint(response)"
},
{
"lang": "JavaScript",
@@ -1048,8 +1048,8 @@
},
"expiration_date": {
"type": "string",
"format": "date",
"description": "The date when the memory will expire. Format: YYYY-MM-DD.",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD.",
"title": "Expiration date",
"nullable": true,
"default": null
@@ -1096,7 +1096,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Retrieve memories for a specific user\nuser_memories = client.get_all(user_id=\"<user_id>\")\n\nprint(user_memories)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Retrieve memories for a specific user\nuser_memories = client.get_all(user_id=\"<user_id>\")\n\nprint(user_memories)"
},
{
"lang": "JavaScript",
@@ -1203,11 +1203,11 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmessages = [\n {\"role\": \"user\", \"content\": \"<user-message>\"},\n {\"role\": \"assistant\", \"content\": \"<assistant-response>\"}\n]\n\nclient.add(messages, user_id=\"<user-id>\")"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmessages = [\n {\"role\": \"user\", \"content\": \"<user-message>\"},\n {\"role\": \"assistant\", \"content\": \"<assistant-response>\"}\n]\n\nclient.add(messages, user_id=\"<user-id>\", version=\"v2\")"
},
{
"lang": "JavaScript",
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst messages = [\n { role: \"user\", content: \"Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts.\" },\n { role: \"assistant\", content: \"Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions.\" }\n];\n\nclient.add(messages, { user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst messages = [\n { role: \"user\", content: \"Hi, I'm Alex. I'm a vegetarian and I'm allergic to nuts.\" },\n { role: \"assistant\", content: \"Hello Alex! I've noted that you're a vegetarian and have a nut allergy. I'll keep this in mind for any food-related recommendations or discussions.\" }\n];\n\nclient.add(messages, { user_id: \"<user_id>\", version: \"v2\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
},
{
"lang": "cURL",
@@ -1232,8 +1232,8 @@
"tags": [
"memories"
],
"description": "Delete memories by filter. At least one filter is required \u2014 previously omitting all filters silently deleted everything; now it returns a validation error.",
"operationId": "memories_delete_all",
"description": "Delete memories by filter. At least one filter is required — previously omitting all filters silently deleted everything; now it returns a validation error.",
"operationId": "memories_delete",
"parameters": [
{
"name": "user_id",
@@ -1315,15 +1315,15 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Delete all memories for a specific user\nclient.delete_all(user_id=\"<user_id>\")\n\n# Delete all memories for every user in the project (wildcard)\nclient.delete_all(user_id=\"*\")\n\n# Full project wipe \u2014 all four filters must be explicitly set to \"*\"\nclient.delete_all(user_id=\"*\", agent_id=\"*\", app_id=\"*\", run_id=\"*\")\n\n# NOTE: Calling delete_all() with no filters raises a validation error.\n# At least one filter is required to prevent accidental data loss."
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Delete all memories for a specific user\nclient.delete_all(user_id=\"<user_id>\")\n\n# Delete all memories for every user in the project (wildcard)\nclient.delete_all(user_id=\"*\")\n\n# Full project wipe — all four filters must be explicitly set to \"*\"\nclient.delete_all(user_id=\"*\", agent_id=\"*\", app_id=\"*\", run_id=\"*\")\n\n# NOTE: Calling delete_all() with no filters raises a validation error.\n# At least one filter is required to prevent accidental data loss."
},
{
"lang": "JavaScript",
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Delete all memories for a specific user\nclient.deleteAll({ user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Delete all memories for every user in the project (wildcard)\nclient.deleteAll({ user_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Full project wipe \u2014 all four filters must be explicitly set to \"*\"\nclient.deleteAll({ user_id: \"*\", agent_id: \"*\", app_id: \"*\", run_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Delete all memories for a specific user\nclient.deleteAll({ user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Delete all memories for every user in the project (wildcard)\nclient.deleteAll({ user_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));\n\n// Full project wipe — all four filters must be explicitly set to \"*\"\nclient.deleteAll({ user_id: \"*\", agent_id: \"*\", app_id: \"*\", run_id: \"*\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
},
{
"lang": "cURL",
"source": "# Delete memories for a specific user\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=<user_id>' \\\n --header 'Authorization: Token <api-key>'\n\n# Delete memories for all users (wildcard)\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*' \\\n --header 'Authorization: Token <api-key>'\n\n# Full project wipe \u2014 all four filters must be set to *\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*&agent_id=*&app_id=*&run_id=*' \\\n --header 'Authorization: Token <api-key>'"
"source": "# Delete memories for a specific user\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=<user_id>' \\\n --header 'Authorization: Token <api-key>'\n\n# Delete memories for all users (wildcard)\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*' \\\n --header 'Authorization: Token <api-key>'\n\n# Full project wipe — all four filters must be set to *\ncurl --request DELETE \\\n --url 'https://api.mem0.ai/v1/memories/?user_id=*&agent_id=*&app_id=*&run_id=*' \\\n --header 'Authorization: Token <api-key>'"
},
{
"lang": "Go",
@@ -1393,8 +1393,8 @@
},
"expiration_date": {
"type": "string",
"format": "date",
"description": "The date when the memory will expire. Format: YYYY-MM-DD.",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD.",
"title": "Expiration date",
"nullable": true,
"default": null
@@ -1441,11 +1441,11 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Retrieve memories with filters\nmemories = client.get_all(\n filters={\n \"AND\": [\n {\n \"user_id\": \"alex\"\n },\n {\n \"created_at\": {\n \"gte\": \"2024-07-01\",\n \"lte\": \"2024-07-31\"\n }\n }\n ]\n }\n)\n\nprint(memories)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Retrieve memories with filters\nmemories = client.get_all(\n filters={\n \"AND\": [\n {\n \"user_id\": \"alex\"\n },\n {\n \"created_at\": {\n \"gte\": \"2024-07-01\",\n \"lte\": \"2024-07-31\"\n }\n }\n ]\n },\n version=\"v2\"\n)\n\nprint(memories)"
},
{
"lang": "JavaScript",
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst filters = {\n AND: [\n { user_id: 'alex' },\n { created_at: { gte: '2024-07-01', lte: '2024-07-31' } }\n ]\n};\n\nclient.getAll({ filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst filters = {\n AND: [\n { user_id: 'alex' },\n { created_at: { gte: '2024-07-01', lte: '2024-07-31' } }\n ]\n};\n\nclient.getAll({ filters, api_version: 'v2' })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
},
{
"lang": "cURL",
@@ -1540,8 +1540,8 @@
},
"expiration_date": {
"type": "string",
"format": "date",
"description": "The date when the memory will expire. Format: YYYY-MM-DD.",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD.",
"title": "Expiration date",
"nullable": true,
"default": null
@@ -1589,11 +1589,11 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nquery = \"Your search query here\"\n\nresults = client.search(query, user_id=\"<user_id>\")\nprint(results)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nquery = \"Your search query here\"\n\nresults = client.search(query, user_id=\"<user_id>\", output_format=\"v1.1\")\nprint(results)"
},
{
"lang": "JavaScript",
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"Your search query here\";\n\nclient.search(query, { user_id: \"<user_id>\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"Your search query here\";\n\nclient.search(query, { user_id: \"<user_id>\", output_format: \"v1.1\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
},
{
"lang": "cURL",
@@ -1675,8 +1675,8 @@
},
"expiration_date": {
"type": "string",
"format": "date",
"description": "The date when the memory will expire. Format: YYYY-MM-DD.",
"format": "date-time",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD.",
"title": "Expiration date",
"nullable": true,
"default": null
@@ -1708,11 +1708,11 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nquery = \"What do you know about me?\"\nfilters = {\n \"OR\":[\n {\n \"user_id\":\"alex\"\n },\n {\n \"agent_id\":{\n \"in\":[\n \"travel-assistant\",\n \"customer-support\"\n ]\n }\n }\n ]\n}\nclient.search(query, filters=filters)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nquery = \"What do you know about me?\"\nfilters = {\n \"OR\":[\n {\n \"user_id\":\"alex\"\n },\n {\n \"agent_id\":{\n \"in\":[\n \"travel-assistant\",\n \"customer-support\"\n ]\n }\n }\n ]\n}\nclient.search(query, version=\"v2\", filters=filters)"
},
{
"lang": "JavaScript",
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"What do you know about me?\";\nconst filters = {\n OR: [\n { user_id: \"alex\" },\n { agent_id: { in: [\"travel-assistant\", \"customer-support\"] } }\n ]\n};\n\nclient.search(query, { filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"What do you know about me?\";\nconst filters = {\n OR: [\n { user_id: \"alex\" },\n { agent_id: { in: [\"travel-assistant\", \"customer-support\"] } }\n ]\n};\n\nclient.search(query, { api_version: \"v2\", filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
},
{
"lang": "cURL",
@@ -1739,7 +1739,7 @@
"tags": [
"memories"
],
"operationId": "memories_entity_read",
"operationId": "memories_read",
"responses": {
"200": {
"description": "Successfully retrieved memories.",
@@ -1864,7 +1864,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmemory = client.get(memory_id=\"<memory_id>\")"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmemory = client.get(memory_id=\"<memory_id>\")"
},
{
"lang": "JavaScript",
@@ -1989,7 +1989,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Update a memory\nmemory_id = \"<memory_id>\"\nclient.update(\n memory_id=memory_id,\n text=\"Your updated memory message here\",\n metadata={\"category\": \"example\"}\n)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Update a memory\nmemory_id = \"<memory_id>\"\nclient.update(\n memory_id=memory_id,\n text=\"Your updated memory message here\",\n metadata={\"category\": \"example\"}\n)"
},
{
"lang": "JavaScript",
@@ -2053,7 +2053,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nmemory_id = \"<memory_id>\"\nclient.delete(memory_id=memory_id)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nmemory_id = \"<memory_id>\"\nclient.delete(memory_id=memory_id)"
},
{
"lang": "JavaScript",
@@ -2199,7 +2199,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Add some message to create history\nmessages = [{\"role\": \"user\", \"content\": \"<user-message>\"}]\nclient.add(messages, user_id=\"<user-id>\")\n\n# Add second message to update history\nmessages.append({\"role\": \"user\", \"content\": \"<user-message>\"})\nclient.add(messages, user_id=\"<user-id>\")\n\n# Get history of how memory changed over time\nmemory_id = \"<memory-id-here>\"\nhistory = client.history(memory_id)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Add some message to create history\nmessages = [{\"role\": \"user\", \"content\": \"<user-message>\"}]\nclient.add(messages, user_id=\"<user-id>\")\n\n# Add second message to update history\nmessages.append({\"role\": \"user\", \"content\": \"<user-message>\"})\nclient.add(messages, user_id=\"<user-id>\")\n\n# Get history of how memory changed over time\nmemory_id = \"<memory-id-here>\"\nhistory = client.history(memory_id)"
},
{
"lang": "JavaScript",
@@ -3608,7 +3608,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\")\n\nresponse = client.get_project()\nprint(response)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nresponse = client.get_project()\nprint(response)"
},
{
"lang": "JavaScript",
@@ -4411,7 +4411,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\nupdate_memories = [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\",\n \"text\": \"Watches football\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\",\n \"text\": \"Likes to travel\"\n }\n]\n\nresponse = client.batch_update(update_memories)\nprint(response)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\nupdate_memories = [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\",\n \"text\": \"Watches football\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\",\n \"text\": \"Likes to travel\"\n }\n]\n\nresponse = client.batch_update(update_memories)\nprint(response)"
},
{
"lang": "JavaScript",
@@ -4490,7 +4490,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\ndelete_memories = [\n {\"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\"},\n {\"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\"}\n]\n\nresponse = client.batch_delete(delete_memories)\nprint(response)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\ndelete_memories = [\n {\"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\"},\n {\"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\"}\n]\n\nresponse = client.batch_delete(delete_memories)\nprint(response)"
},
{
"lang": "JavaScript",
@@ -4758,7 +4758,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Create a webhook\nwebhook = client.create_webhook(\n url=\"https://your-webhook-url.com\",\n name=\"My Webhook\",\n project_id=\"your_project_id\",\n event_types=[\"memory:add\", \"memory:categorize\"]\n)\nprint(webhook)"
},
{
"lang": "JavaScript",
@@ -4998,7 +4998,7 @@
"x-code-samples": [
{
"lang": "Python",
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\")\n\n# Delete a webhook\nresponse = client.delete_webhook(webhook_id=\"your_webhook_id\")\nprint(response)"
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\n# Delete a webhook\nresponse = client.delete_webhook(webhook_id=\"your_webhook_id\")\nprint(response)"
},
{
"lang": "JavaScript",
@@ -5176,10 +5176,9 @@
"nullable": true
},
"expiration_date": {
"description": "The date when the memory will expire. Format: YYYY-MM-DD",
"description": "The date and time when the memory will expire. Format: YYYY-MM-DD",
"title": "Expiration date",
"type": "string",
"format": "date",
"nullable": true
},
"org_id": {
@@ -5743,4 +5742,4 @@
}
},
"x-original-swagger-version": "2.0"
}
}
@@ -81,6 +81,7 @@ result = await memory.add(
conversation,
user_id="traveler-42",
metadata={"trip": "japan-2025", "preferences": ["boutique", "no-shellfish"]},
enable_graph=True,
run_id="planning-call-1",
)
```
@@ -100,6 +101,7 @@ const conversation = [
const result = await memory.add(conversation, {
userId: "traveler-42",
metadata: { trip: "japan-2025", preferences: ["boutique", "no-shellfish"] },
enableGraph: true,
runId: "planning-call-1",
});
```
@@ -161,6 +163,10 @@ await memory.update(matches.results[0].id, {
</Tab>
</Tabs>
<Tip>
Need to pause graph writes on a per-request basis? Pass `enableGraph: false` (TypeScript) or `enable_graph=False` (Python) when latency matters more than relationship building.
</Tip>
## Clean up
<Tabs>
@@ -186,6 +192,8 @@ await memory.deleteAll({ userId: "traveler-42", runId: "planning-call-1" });
## Quick recovery
- `Missing required key enableGraph`: update the SDK to `mem0ai>=0.4.0`.
- `Graph backend unavailable`: retry with `enableGraph=False` and inspect your graph provider status.
- Empty results with filters: log `filters` values and confirm metadata keys match (case-sensitive).
<Warning>
+102 -16
View File
@@ -1,6 +1,6 @@
---
title: Advanced Retrieval
description: "Advanced memory search with intelligent reranking for precise results"
description: "Advanced memory search with keyword expansion, intelligent reranking, and precision filtering"
---
## What is Advanced Retrieval?
@@ -9,6 +9,40 @@ Advanced Retrieval gives you precise control over how memories are found and ran
## Search Enhancement Options
### Keyword Search
Expands results to include memories with specific terms, names, and technical keywords.
<Tabs>
<Tab title="When to Use">
- Searching for specific entities, names, or technical terms
- Need comprehensive coverage of a topic
- Want broader recall even if some results are less relevant
- Working with domain-specific terminology
</Tab>
<Tab title="How it Works">
```python Python
# Find memories containing specific food-related terms
results = client.search(
query="What foods should I avoid?",
keyword_search=True,
user_id="user123"
)
# Results might include:
# ✓ "Allergic to peanuts and shellfish"
# ✓ "Lactose intolerant - avoid dairy"
# ✓ "Mentioned avoiding gluten last week"
```
</Tab>
<Tab title="Performance">
- **Latency**: ~10ms additional
- **Recall**: Significantly increased
- **Precision**: Slightly decreased
- **Best for**: Entity search, comprehensive coverage
</Tab>
</Tabs>
### Reranking
Reorders results using deep semantic understanding to put the most relevant memories first.
@@ -43,6 +77,41 @@ results = client.search(
</Tab>
</Tabs>
### Memory Filtering
Filters results to keep only the most precisely relevant memories.
<Tabs>
<Tab title="When to Use">
- Need highly specific, focused results
- Working with large datasets where noise is problematic
- Quality over quantity is essential
- Building production or safety-critical applications
</Tab>
<Tab title="How it Works">
```python Python
# Get only the most relevant dietary restrictions
results = client.search(
query="What are my dietary restrictions?",
filter_memories=True,
user_id="user123"
)
# Before filtering: After filtering:
# • "Allergic to nuts" → • "Allergic to nuts"
# • "Likes Italian food" → • "Vegetarian diet"
# • "Vegetarian diet" →
# • "Eats dinner at 7pm" →
```
</Tab>
<Tab title="Performance">
- **Latency**: 200-300ms additional
- **Precision**: Maximized
- **Recall**: May be reduced
- **Best for**: Focused queries, production systems
</Tab>
</Tabs>
## Real-World Use Cases
<Tabs>
@@ -51,6 +120,7 @@ results = client.search(
# Smart home assistant finding device preferences
results = client.search(
query="How do I like my bedroom temperature?",
keyword_search=True, # Find specific temperature mentions
rerank=True, # Get most recent preferences first
user_id="user123"
)
@@ -63,6 +133,8 @@ results = client.search(
# Find specific product issues with high precision
results = client.search(
query="Problems with premium subscription billing",
keyword_search=True, # Find "premium", "billing", "subscription"
filter_memories=True, # Only billing-related issues
user_id="customer456"
)
@@ -75,10 +147,11 @@ results = client.search(
results = client.search(
query="Patient allergies and contraindications",
rerank=True, # Most important info first
filter_memories=True, # Only medical restrictions
user_id="patient789"
)
# Ensures critical allergy info appears first
# Ensures critical allergy info appears first and filters out non-medical data
```
</Tab>
<Tab title="Learning Platform">
@@ -86,6 +159,7 @@ results = client.search(
# Find learning progress for specific topics
results = client.search(
query="Python programming progress and difficulties",
keyword_search=True, # Find "Python", "programming", specific concepts
rerank=True, # Recent progress first
user_id="student123"
)
@@ -95,57 +169,63 @@ results = client.search(
</Tab>
</Tabs>
## Choosing the Right Configuration
## Choosing the Right Combination
### Recommended Configurations
<CodeGroup>
```python Python
# Basic search - good for exploration
# Fast and broad - good for exploration
def quick_search(query, user_id):
return client.search(
query=query,
keyword_search=True,
user_id=user_id
)
# Reranked search - good for most applications
# Balanced - good for most applications
def standard_search(query, user_id):
return client.search(
query=query,
keyword_search=True,
rerank=True,
user_id=user_id
)
# Reranked search - good for critical applications
# High precision - good for critical applications
def precise_search(query, user_id):
return client.search(
query=query,
rerank=True,
filter_memories=True,
user_id=user_id
)
```
```javascript JavaScript
// Basic search - good for exploration
// Fast and broad - good for exploration
function quickSearch(query, userId) {
return client.search(query, {
user_id: userId
user_id: userId,
keyword_search: true
});
}
// Reranked search - good for most applications
// Balanced - good for most applications
function standardSearch(query, userId) {
return client.search(query, {
user_id: userId,
keyword_search: true,
rerank: true
});
}
// Reranked search - good for critical applications
// High precision - good for critical applications
function preciseSearch(query, userId) {
return client.search(query, {
user_id: userId,
rerank: true
rerank: true,
filter_memories: true
});
}
```
@@ -155,15 +235,19 @@ function preciseSearch(query, userId) {
### Do
- Start simple with basic search and measure impact before enabling reranking
- Start simple with just one enhancement and measure impact
- Use keyword search for entity-heavy queries (names, places, technical terms)
- Use reranking when the top result quality matters most
- Use filtering for production systems where precision is critical
- Handle empty results gracefully when filtering is too aggressive
- Monitor latency and adjust based on your application's needs
- Handle empty results gracefully
### Don't
- Enable reranking by default without measuring necessity
- Enable all options by default without measuring necessity
- Use filtering for broad exploratory queries
- Ignore latency impact in real-time applications
- Forget to handle cases where filtering returns no results
- Use advanced retrieval for simple, fast lookup scenarios
## Performance Guidelines
@@ -177,18 +261,20 @@ import time
start_time = time.time()
results = client.search(
query="user preferences",
keyword_search=True, # +10ms
rerank=True, # +150ms
filter_memories=True, # +250ms
user_id="user123"
)
latency = time.time() - start_time
print(f"Search completed in {latency:.2f}s")
print(f"Search completed in {latency:.2f}s") # ~0.41s expected
```
### Optimization Tips
1. **Cache frequent queries** to avoid repeated advanced processing
2. **Use session-specific search** with `run_id` to reduce search space
3. **Implement fallback logic** when search returns empty results
3. **Implement fallback logic** when filtering returns empty results
4. **Monitor and alert** on search latency patterns
<Snippet file="get-help.mdx" />
+3 -3
View File
@@ -50,7 +50,7 @@ const messages = [
{"role": "user", "content": "Alice loves playing badminton"},
{"role": "assistant", "content": "That's great! Alice is a fitness freak"},
];
await client.add(messages, { userId: "alice" });
await client.add(messages, { user_id: "alice" });
```
</CodeGroup>
@@ -66,7 +66,7 @@ await client.search("What is Alice's favorite sport?", user_id="alice")
```
```javascript JavaScript
await client.search("What is Alice's favorite sport?", { userId: "alice" });
await client.search("What is Alice's favorite sport?", { user_id: "alice" });
```
</CodeGroup>
@@ -118,7 +118,7 @@ await client.delete_all(user_id="alice")
```
```javascript JavaScript
await client.deleteAll({ userId: "alice" });
await client.deleteAll({ user_id: "alice" });
```
</CodeGroup>
@@ -0,0 +1,197 @@
---
title: Async Mode Default Change
description: "The async_mode parameter now defaults to true for all memory additions, changing from synchronous processing."
---
<Note type="warning">
**Important Change**
The `async_mode` parameter defaults to `true` for all memory additions, changing the default API behavior to asynchronous processing.
</Note>
## Overview
The Memory Addition API processes all memory additions asynchronously by default. This change improves performance and scalability by queuing memory operations in the background, allowing your application to continue without waiting for memory processing to complete.
## What's Changing
The parameter `async_mode` will default to `true` instead of `false`.
This means memory additions will be **processed asynchronously** by default - queued for background execution instead of waiting for processing to complete.
## Behavior Comparison
### Old Default Behavior (async_mode = false)
When `async_mode` was set to `false`, the API returned fully processed memory objects immediately:
```json
{
"results": [
{
"id": "de0ee948-af6a-436c-835c-efb6705207de",
"event": "ADD",
"memory": "User Order #1234 was for a 'Nova 2000'",
"structured_attributes": {
"day": 13,
"hour": 16,
"year": 2025,
"month": 10,
"minute": 59,
"quarter": 4,
"is_weekend": false,
"day_of_week": "monday",
"day_of_year": 286,
"week_of_year": 42
}
}
]
}
```
### New Default Behavior (async_mode = true)
With `async_mode` defaulting to `true`, memory processing is queued in the background and the API returns immediately:
```json
{
"results": [
{
"message": "Memory processing has been queued for background execution",
"status": "PENDING",
"event_id": "d7b5282a-0031-4cc2-98ba-5a02d8531e17"
}
]
}
```
## Migration Guide
### If You Need Synchronous Processing
If your integration relies on receiving the processed memory object immediately, you can explicitly set `async_mode` to `false` in your requests:
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
# Explicitly set async_mode=False to preserve synchronous behavior
messages = [
{"role": "user", "content": "I ordered a Nova 2000"}
]
result = client.add(
messages,
user_id="user-123",
async_mode=False # This ensures synchronous processing
)
```
```javascript JavaScript
const { MemoryClient } = require('mem0ai');
const client = new MemoryClient({ apiKey: 'your-api-key' });
// Explicitly set async_mode: false to preserve synchronous behavior
const messages = [
{ role: "user", content: "I ordered a Nova 2000" }
];
const result = await client.add(messages, {
user_id: "user-123",
async_mode: false // This ensures synchronous processing
});
```
```bash cURL
curl -X POST https://api.mem0.ai/v1/memories/ \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "I ordered a Nova 2000"}
],
"user_id": "user-123",
"async_mode": false
}'
```
</CodeGroup>
### If You Want to Adopt Asynchronous Processing
If you want to benefit from the improved performance of asynchronous processing:
1. **Remove** any explicit `async_mode=False` parameters from your code
2. **Use webhooks** to receive notifications when memory processing completes
<Note>
Learn more about [Webhooks](/platform/features/webhooks) for real-time notifications about memory events.
</Note>
## Benefits of Asynchronous Processing
Switching to asynchronous processing provides several advantages:
- **Faster API Response Times**: Your application doesn't wait for memory processing
- **Better Scalability**: Handle more memory additions concurrently
- **Improved User Experience**: Reduced latency in your application
- **Resource Efficiency**: Background processing optimizes server resources
## Important Notes
- The default behavior is now `async_mode=true` for asynchronous processing
- Explicitly set `async_mode=false` if you need synchronous behavior
- Use webhooks to receive notifications when memories are processed
## Monitoring Memory Processing
When using asynchronous mode, use webhooks to receive notifications about memory events:
<Card title="Configure Webhooks" icon="webhook" href="/platform/features/webhooks">
Learn how to set up webhooks for memory processing events
</Card>
You can also retrieve all processed memories at any time:
<CodeGroup>
```python Python
# Retrieve all memories for a user
# Note: get_all now requires filters
memories = client.get_all(filters={"AND": [{"user_id": "user-123"}]})
```
```javascript JavaScript
// Retrieve all memories for a user
// Note: getAll now requires filters
const memories = await client.getAll({ filters: {"AND": [{"user_id": "user-123"}]} });
```
</CodeGroup>
## Need Help?
If you have questions about this change or need assistance updating your integration:
<Snippet file="get-help.mdx" />
## Related Documentation
<CardGroup cols={2}>
<Card title="Async Client" icon="bolt" href="/platform/features/async-client">
Learn about the asynchronous client for Mem0
</Card>
<Card title="Add Memories API" icon="plus" href="/api-reference/memory/add-memories">
View the complete API reference for adding memories
</Card>
<Card title="Webhooks" icon="webhook" href="/platform/features/webhooks">
Configure webhooks for memory processing events
</Card>
<Card title="Memory Operations" icon="gear" href="/core-concepts/memory-operations/add">
Understand memory addition operations
</Card>
</CardGroup>
+7 -7
View File
@@ -25,7 +25,7 @@ const messages = [
{"role": "assistant", "content": "Great! I'll remember your preference for Italian cuisine."}
];
await client.add(messages, { userId: "user123", version: "v2" });
await client.add(messages, { user_id: "user123", version: "v2" });
```
</CodeGroup>
@@ -65,14 +65,14 @@ const messages1 = [
{"role": "user", "content": "Hi, I'm Sarah from New York"},
{"role": "assistant", "content": "Hello Sarah! Nice to meet you."}
];
await client.add(messages1, { userId: "sarah", version: "v2" });
await client.add(messages1, { user_id: "sarah", version: "v2" });
// Later interaction - just send new messages
const messages2 = [
{"role": "user", "content": "I'm planning a trip to Italy next month"},
{"role": "assistant", "content": "How exciting! Italy is beautiful this time of year."}
];
await client.add(messages2, { userId: "sarah", version: "v2" });
await client.add(messages2, { user_id: "sarah", version: "v2" });
// Mem0 automatically knows Sarah is from New York and can use this context
```
</CodeGroup>
@@ -104,7 +104,7 @@ const messages = [
{"role": "assistant", "content": "I've noted your allergies for future reference."}
];
await client.add(messages, { userId: "user123", version: "v2" });
await client.add(messages, { user_id: "user123", version: "v2" });
// This allergy info will be available in ALL future interactions
```
</CodeGroup>
@@ -143,21 +143,21 @@ const messages1 = [
{"role": "user", "content": "I want to plan a 5-day trip to Tokyo"},
{"role": "assistant", "content": "Perfect! Let's plan your Tokyo adventure."}
];
await client.add(messages1, { userId: "user123", runId: "tokyo-trip-2024", version: "v2" });
await client.add(messages1, { user_id: "user123", run_id: "tokyo-trip-2024", version: "v2" });
// Later in the same trip planning session
const messages2 = [
{"role": "user", "content": "I prefer staying near Shibuya"},
{"role": "assistant", "content": "Great choice! Shibuya is very convenient."}
];
await client.add(messages2, { userId: "user123", runId: "tokyo-trip-2024", version: "v2" });
await client.add(messages2, { user_id: "user123", run_id: "tokyo-trip-2024", version: "v2" });
// Different session for work project (separate context)
const workMessages = [
{"role": "user", "content": "Let's discuss the Q4 marketing strategy"},
{"role": "assistant", "content": "Sure! What are your main goals for Q4?"}
];
await client.add(workMessages, { userId: "user123", runId: "q4-marketing", version: "v2" });
await client.add(workMessages, { user_id: "user123", run_id: "q4-marketing", version: "v2" });
```
</CodeGroup>
@@ -38,7 +38,11 @@ Before defining any criteria, make sure to initialize the `MemoryClient` with yo
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your_mem0_api_key")
client = MemoryClient(
api_key="your_mem0_api_key",
org_id="your_organization_id",
project_id="your_project_id"
)
```
### Define Your Criteria
+2 -2
View File
@@ -98,7 +98,7 @@ messages = [
]
# Add memories with project-level custom categories
client.add(messages, user_id="alice")
client.add(messages, user_id="alice", async_mode=False)
```
</CodeGroup>
@@ -187,7 +187,7 @@ messages = [
]
# Add memories with default categories
client.add(messages, user_id='alice')
client.add(messages, user_id='alice', async_mode=False)
```
```python Memories with categories
@@ -33,7 +33,7 @@ Extract only health and wellness information:
Exclude: Personal identifiers, financial data
`;
await client.project.update({ customInstructions: prompt });
await client.project.update({ custom_instructions: prompt });
```
</CodeGroup>
@@ -60,11 +60,11 @@ print(response["custom_instructions"])
```javascript JavaScript
// Set instructions for your project
await client.project.update({ customInstructions: "Your guidelines here..." });
await client.project.update({ custom_instructions: "Your guidelines here..." });
// Retrieve current instructions
const response = await client.project.get({ fields: ["customInstructions"] });
console.log(response.customInstructions);
const response = await client.project.get({ fields: ["custom_instructions"] });
console.log(response.custom_instructions);
```
</CodeGroup>
@@ -145,7 +145,7 @@ Extract customer service information for better support:
Exclude: Payment card numbers, passwords, personal identifiers.
`;
await client.project.update({ customInstructions: instructions });
await client.project.update({ custom_instructions: instructions });
```
</CodeGroup>
</Tab>
@@ -198,7 +198,7 @@ Extract learning-related information for personalized education:
Exclude: Specific grades, personal identifiers, financial information.
`;
await client.project.update({ customInstructions: educationPrompt });
await client.project.update({ custom_instructions: educationPrompt });
```
</CodeGroup>
</Tab>
@@ -251,7 +251,7 @@ Extract financial planning information for advisory services:
Exclude: Account numbers, SSNs, passwords, specific financial amounts.
`;
await client.project.update({ customInstructions: financePrompt });
await client.project.update({ custom_instructions: financePrompt });
```
</CodeGroup>
</Tab>
+109
View File
@@ -0,0 +1,109 @@
---
title: Expiration Date
description: 'Set time-bound memories in Mem0 with automatic expiration dates to manage temporal information effectively.'
---
## Benefits of Memory Expiration
Setting expiration dates for memories offers several advantages:
- **Time-Sensitive Information Management**: Handle information that is only relevant for a specific time period.
- **Event-Based Memory**: Manage information related to upcoming events that becomes irrelevant after the event passes.
These benefits enable more sophisticated memory management for applications where temporal context matters.
## Setting Memory Expiration Date
You can set an expiration date for memories, after which they will no longer be retrieved in searches. This is useful for creating temporary memories or memories that are relevant only for a specific time period.
<CodeGroup>
```python Python
import datetime
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
messages = [
{
"role": "user",
"content": "I'll be in San Francisco until the end of this month."
}
]
# Set an expiration date for this memory
client.add(messages=messages, user_id="alex", expiration_date=str(datetime.datetime.now().date() + datetime.timedelta(days=30)))
# You can also use an explicit date string
client.add(messages=messages, user_id="alex", expiration_date="2023-08-31")
```
```javascript JavaScript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'your-api-key' });
const messages = [
{
"role": "user",
"content": "I'll be in San Francisco until the end of this month."
}
];
// Set an expiration date 30 days from now
const expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + 30);
client.add(messages, {
user_id: "alex",
expiration_date: expirationDate.toISOString().split('T')[0]
})
.then(response => console.log(response))
.catch(error => console.error(error));
// You can also use an explicit date string
client.add(messages, {
user_id: "alex",
expiration_date: "2023-08-31"
})
.then(response => console.log(response))
.catch(error => console.error(error));
```
```bash cURL
curl -X POST "https://api.mem0.ai/v1/memories/" \
-H "Authorization: Token your-api-key" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "I'll be in San Francisco until the end of this month."
}
],
"user_id": "alex",
"expiration_date": "2023-08-31"
}'
```
```json Output
{
"results": [
{
"id": "a1b2c3d4-e5f6-4g7h-8i9j-k0l1m2n3o4p5",
"data": {
"memory": "In San Francisco until the end of this month"
},
"event": "ADD"
}
]
}
```
</CodeGroup>
<Note>
Once a memory reaches its expiration date, it will not be included in search or get results, though the data remains stored in the system.
</Note>
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx" />
+349
View File
@@ -0,0 +1,349 @@
---
title: Graph Memory
description: "Enable graph-based memory retrieval for more contextually relevant results"
---
## Overview
Graph Memory enhances the memory pipeline by creating relationships between entities in your data. It builds a network of interconnected information for more contextually relevant search results.
This feature allows your AI applications to understand connections between entities, providing richer context for responses. It's ideal for applications needing relationship tracking and nuanced information retrieval across related memories.
## How Graph Memory Works
The Graph Memory feature analyzes how each entity connects and relates to each other. When enabled:
1. Mem0 automatically builds a graph representation of entities
2. Vector search returns the top semantic matches (with any reranker you configure)
3. Graph relations are returned alongside those results to provide additional context—they do not reorder the vector hits
## Using Graph Memory
To use Graph Memory, you need to enable it in your API calls by setting the `enable_graph=True` parameter.
### Adding Memories with Graph Memory
When adding new memories, enable Graph Memory to automatically build relationships with existing memories:
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(
api_key="your-api-key",
org_id="your-org-id",
project_id="your-project-id"
)
messages = [
{"role": "user", "content": "My name is Joseph"},
{"role": "assistant", "content": "Hello Joseph, it's nice to meet you!"},
{"role": "user", "content": "I'm from Seattle and I work as a software engineer"}
]
# Enable graph memory when adding
client.add(
messages,
user_id="joseph",
enable_graph=True
)
```
```javascript JavaScript
import { MemoryClient } from "mem0";
const client = new MemoryClient({
apiKey: "your-api-key",
org_id: "your-org-id",
project_id: "your-project-id"
});
const messages = [
{ role: "user", content: "My name is Joseph" },
{ role: "assistant", content: "Hello Joseph, it's nice to meet you!" },
{ role: "user", content: "I'm from Seattle and I work as a software engineer" }
];
// Enable graph memory when adding
await client.add({
messages,
user_id: "joseph",
enable_graph: true
});
```
```json Output
{
"results": [
{
"memory": "Name is Joseph",
"event": "ADD",
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438"
},
{
"memory": "Is from Seattle",
"event": "ADD",
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d"
},
{
"memory": "Is a software engineer",
"event": "ADD",
"id": "5f0a184e-ddea-4fe6-9b92-692d6a901df8"
}
]
}
```
</CodeGroup>
The graph memory would look like this:
<Frame>
<img src="/images/graph-platform.png" alt="Graph Memory Visualization showing relationships between entities" />
</Frame>
<Caption>Graph Memory creates a network of relationships between entities, enabling more contextual retrieval</Caption>
<Note>
Response for the graph memory's `add` operation will not be available directly in the response. As adding graph memories is an asynchronous operation due to heavy processing, you can use the `get_all()` endpoint to retrieve the memory with the graph metadata.
</Note>
### Searching with Graph Memory
When searching memories, Graph Memory helps retrieve entities that are contextually important even if they're not direct semantic matches.
<CodeGroup>
```python Python
# Search with graph memory enabled
results = client.search(
"what is my name?",
user_id="joseph",
enable_graph=True
)
print(results)
```
```javascript JavaScript
// Search with graph memory enabled
const results = await client.search({
query: "what is my name?",
user_id: "joseph",
enable_graph: true
});
console.log(results);
```
```json Output
{
"results": [
{
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438",
"memory": "Name is Joseph",
"user_id": "joseph",
"metadata": null,
"categories": ["personal_details"],
"immutable": false,
"created_at": "2025-03-19T09:09:00.146390-07:00",
"updated_at": "2025-03-19T09:09:00.146404-07:00",
"score": 0.3621795393335552
},
{
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d",
"memory": "Is from Seattle",
"user_id": "joseph",
"metadata": null,
"categories": ["personal_details"],
"immutable": false,
"created_at": "2025-03-19T09:09:00.170680-07:00",
"updated_at": "2025-03-19T09:09:00.170692-07:00",
"score": 0.31212713194651254
}
],
"relations": [
{
"source": "joseph",
"source_type": "person",
"relationship": "name",
"target": "joseph",
"target_type": "person",
"score": 0.39
}
]
}
```
</CodeGroup>
<Note>
`results` always reflects the vector search order (optionally reranked). Graph Memory augments that response by adding related entities in the `relations` array; it does not re-rank the vector results automatically.
</Note>
### Retrieving All Memories with Graph Memory
When retrieving all memories, Graph Memory provides additional relationship context:
<Callout type="warning" title="Filters Required">
`get_all()` now requires filters to be specified.
</Callout>
<CodeGroup>
```python Python
# Get all memories with graph context
memories = client.get_all(
filters={"AND": [{"user_id": "joseph"}]},
enable_graph=True
)
print(memories)
```
```javascript JavaScript
// Get all memories with graph context
const memories = await client.getAll({
filters: {"AND": [{"user_id": "joseph"}]},
enable_graph: true
});
console.log(memories);
```
```json Output
{
"results": [
{
"id": "5f0a184e-ddea-4fe6-9b92-692d6a901df8",
"memory": "Is a software engineer",
"user_id": "joseph",
"metadata": null,
"categories": ["professional_details"],
"immutable": false,
"created_at": "2025-03-19T09:09:00.194116-07:00",
"updated_at": "2025-03-19T09:09:00.194128-07:00",
},
{
"id": "8d268d0f-5452-4714-b27d-ae46f676a49d",
"memory": "Is from Seattle",
"user_id": "joseph",
"metadata": null,
"categories": ["personal_details"],
"immutable": false,
"created_at": "2025-03-19T09:09:00.170680-07:00",
"updated_at": "2025-03-19T09:09:00.170692-07:00",
},
{
"id": "4a5a417a-fa10-43b5-8c53-a77c45e80438",
"memory": "Name is Joseph",
"user_id": "joseph",
"metadata": null,
"categories": ["personal_details"],
"immutable": false,
"created_at": "2025-03-19T09:09:00.146390-07:00",
"updated_at": "2025-03-19T09:09:00.146404-07:00",
}
],
"relations": [
{
"source": "joseph",
"source_type": "person",
"relationship": "name",
"target": "joseph",
"target_type": "person"
},
{
"source": "joseph",
"source_type": "person",
"relationship": "city",
"target": "seattle",
"target_type": "city"
},
{
"source": "joseph",
"source_type": "person",
"relationship": "job",
"target": "software engineer",
"target_type": "job"
}
]
}
```
</CodeGroup>
### Setting Graph Memory at Project Level
Instead of passing `enable_graph=True` to every add call, you can enable it once at the project level:
<CodeGroup>
```python Python
from mem0 import MemoryClient
client = MemoryClient(
api_key="your-api-key",
org_id="your-org-id",
project_id="your-project-id"
)
# Enable graph memory for all operations in this project
client.project.update(enable_graph=True)
# Now all add operations will use graph memory by default
messages = [
{"role": "user", "content": "My name is Joseph"},
{"role": "assistant", "content": "Hello Joseph, it's nice to meet you!"},
{"role": "user", "content": "I'm from Seattle and I work as a software engineer"}
]
client.add(
messages,
user_id="joseph"
)
```
```javascript JavaScript
import { MemoryClient } from "mem0";
const client = new MemoryClient({
apiKey: "your-api-key",
org_id: "your-org-id",
project_id: "your-project-id"
});
// Enable graph memory for all operations in this project
await client.project.update({ enable_graph: true });
// Now all add operations will use graph memory by default
const messages = [
{ role: "user", content: "My name is Joseph" },
{ role: "assistant", content: "Hello Joseph, it's nice to meet you!" },
{ role: "user", content: "I'm from Seattle and I work as a software engineer" }
];
await client.add({
messages,
user_id: "joseph"
});
```
</CodeGroup>
## Best Practices
- Enable Graph Memory for applications where understanding context and relationships between memories is important.
- Graph Memory works best with a rich history of related conversations.
- Consider Graph Memory for long-running assistants that need to track evolving information.
## Performance Considerations
Graph Memory requires additional processing and may increase response times slightly for very large memory stores. However, for most use cases, the improved retrieval quality outweighs the minimal performance impact.
If you have any questions, please feel free to reach out to us using one of the following methods:
<Snippet file="get-help.mdx" />
+1 -1
View File
@@ -206,5 +206,5 @@ config = {"graph_store": {"threshold": 0.7}} # Valid: 0.0 ≤ x ≤ 1.0
## Related
- [Graph Memory](/open-source/features/graph-memory)
- [Graph Memory](/platform/features/graph-memory)
- [Issue #3590](https://github.com/mem0ai/mem0/issues/3590)
+4 -3
View File
@@ -217,16 +217,17 @@ print(search_response)
## Async Mode Support
Group chat supports async processing for improved performance. Memory additions are processed asynchronously by default.
Group chat also supports async processing for improved performance:
<CodeGroup>
```python Python
# Group chat — async processing is the default
# Group chat with async mode
response = client.add(
messages,
run_id="groupchat_async",
infer=True,
async_mode=True
)
print(response)
```
@@ -267,7 +268,7 @@ Each message in a group chat must include:
4. **Memory Filtering**: Use filters to retrieve memories from specific participants or sessions when needed.
5. **Async Processing**: Memory additions are processed asynchronously by default, which is ideal for large group conversations.
5. **Async Processing**: Use `async_mode=True` for large group conversations to improve performance.
6. **Search Context**: Leverage the search functionality to find specific information within group chat contexts.
+3 -3
View File
@@ -134,7 +134,7 @@ const filters = {
const responseWithInstructions = await client.createMemoryExport({
schema: json_schema,
filters: filters,
exportInstructions: export_instructions
export_instructions: export_instructions
});
console.log(responseWithInstructions);
@@ -176,10 +176,10 @@ print(response)
```javascript JavaScript
// Retrieve using export ID
const memoryExportId = "550e8400-e29b-41d4-a716-446655440000";
const memory_export_id = "550e8400-e29b-41d4-a716-446655440000";
const response = await client.getMemoryExport({
memoryExportId: memoryExportId
memory_export_id: memory_export_id
});
console.log(response);
@@ -67,7 +67,7 @@ const messages = [
},
]
await client.add(messages, { userId: "alice" })
await client.add(messages, { user_id: "alice" })
```
```json Output
@@ -166,7 +166,7 @@ const imageMessage = {
}
};
await client.add([imageMessage], { userId: "alice" })
await client.add([imageMessage], { user_id: "alice" })
```
</CodeGroup>
+1 -1
View File
@@ -20,7 +20,7 @@ Mem0 Platform features help managed deployments scale from basic filtering to gr
<Card title="Go Real-Time with Async" icon="bolt" href="/platform/features/async-client">
Non-blocking add/search requests for agents.
</Card>
<Card title="Unlock Graph Memory" icon="circle-nodes" href="/open-source/features/graph-memory">
<Card title="Unlock Graph Memory" icon="circle-nodes" href="/platform/features/graph-memory">
Relationship-aware recall across entities.
</Card>
<Card
+2 -2
View File
@@ -76,7 +76,7 @@ const unixTimestamp = Math.floor(fiveDaysAgo.getTime() / 1000);
const messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
client.add(messages, { userId: "user1", timestamp: unixTimestamp })
client.add(messages, { user_id: "user1", timestamp: unixTimestamp })
.then(response => console.log(response))
.catch(error => console.error(error));
```
@@ -131,7 +131,7 @@ const january2023Timestamp = 1672531200; // Unix timestamp for 2023-01-01 00:00
const messages = [
{"role": "user", "content": "I'm travelling to SF"}
]
client.add(messages, { userId: "user1", timestamp: january2023Timestamp })
client.add(messages, { user_id: "user1", timestamp: january2023Timestamp })
.then(response => console.log(response))
.catch(error => console.error(error));
```
+1 -1
View File
@@ -68,7 +68,7 @@ const messages = [
{"role": "user", "content": "I'm a vegetarian and allergic to nuts."},
{"role": "assistant", "content": "Got it! I'll remember your dietary preferences."}
];
await client.add(messages, { userId: "user123" });
await client.add(messages, { user_id: "user123" });
````
```bash cURL
+2 -2
View File
@@ -8,6 +8,6 @@
},
"homepage": "https://mem0.ai",
"repository": "https://github.com/mem0ai/mem0",
"license": "Apache-2.0",
"keywords": ["memory", "personalization", "mcp", "semantic-search"]
"logo": "logo.svg",
"license": "Apache-2.0"
}
+1
View File
@@ -1,4 +1,5 @@
{
"description": "Mem0 memory capture hooks — automatic memory extraction at key lifecycle points",
"hooks": {
"SessionStart": [
{
+3 -4
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "3.0.0-beta.2",
"version": "2.4.6",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -120,11 +120,10 @@
"better-sqlite3": "^12.6.2",
"cloudflare": "^4.2.0",
"groq-sdk": "0.3.0",
"neo4j-driver": "^5.28.1",
"ollama": "^0.5.14",
"pg": "8.11.3",
"redis": "^4.6.13",
"compromise": "^14.0.0",
"natural": "^8.0.1"
"redis": "^4.6.13"
},
"engines": {
"node": ">=18"
+4129 -2354
View File
File diff suppressed because it is too large Load Diff
+2 -6
View File
@@ -3,16 +3,14 @@ import type * as MemoryTypes from "./mem0.types";
// Re-export all types from mem0.types
export type {
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
MemoryOptions,
ProjectOptions,
Memory,
MemoryHistory,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
SearchOptions,
Webhook,
WebhookCreatePayload,
WebhookUpdatePayload,
@@ -21,8 +19,6 @@ export type {
AllUsers,
User,
FeedbackPayload,
CreateMemoryExportPayload,
GetMemoryExportPayload,
} from "./mem0.types";
// Re-export enums as values (not type-only)
+234 -134
View File
@@ -1,17 +1,14 @@
import axios from "axios";
import {
AllUsers,
PaginatedMemories,
ProjectOptions,
Memory,
MemoryHistory,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
MemoryOptions,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
SearchOptions,
Webhook,
WebhookCreatePayload,
WebhookUpdatePayload,
@@ -21,40 +18,8 @@ import {
GetMemoryExportPayload,
} from "./mem0.types";
import { captureClientEvent, generateHash } from "./telemetry";
import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils";
import { createExceptionFromResponse, MemoryError } from "../common/exceptions";
// Entity params that must be passed via filters - check both snake_case and camelCase
const ENTITY_PARAMS = [
"user_id",
"agent_id",
"app_id",
"run_id",
"userId",
"agentId",
"appId",
"runId",
];
/**
* Validates that no top-level entity parameters are passed.
* @throws Error if entity params are found at top level
*/
function rejectTopLevelEntityParams(
options: Record<string, any> | undefined,
methodName: string,
): void {
const invalidKeys = Object.keys(options ?? {}).filter((k) =>
ENTITY_PARAMS.includes(k),
);
if (invalidKeys.length > 0) {
throw new Error(
`Top-level entity parameters [${invalidKeys.join(", ")}] are not supported in ${methodName}(). ` +
`Use filters: { user_id: "..." } instead.`,
);
}
}
class APIError extends Error {
constructor(message: string) {
super(message);
@@ -65,13 +30,19 @@ class APIError extends Error {
interface ClientOptions {
apiKey: string;
host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
}
export default class MemoryClient {
apiKey: string;
host: string;
private organizationId: string | number | null;
private projectId: string | number | null;
organizationName: string | null;
projectName: string | null;
organizationId: string | number | null;
projectId: string | number | null;
headers: Record<string, string>;
client: any;
telemetryId: string;
@@ -88,11 +59,35 @@ export default class MemoryClient {
}
}
_validateOrgProject(): void {
// Check for organizationName/projectName pair
if (
(this.organizationName === null && this.projectName !== null) ||
(this.organizationName !== null && this.projectName === null)
) {
console.warn(
"Warning: Both organizationName and projectName must be provided together when using either. This will be removed from version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.",
);
}
// Check for organizationId/projectId pair
if (
(this.organizationId === null && this.projectId !== null) ||
(this.organizationId !== null && this.projectId === null)
) {
console.warn(
"Warning: Both organizationId and projectId must be provided together when using either. This will be removed from version 1.0.40.",
);
}
}
constructor(options: ClientOptions) {
this.apiKey = options.apiKey;
this.host = options.host || "https://api.mem0.ai";
this.organizationId = null;
this.projectId = null;
this.organizationName = options.organizationName || null;
this.projectName = options.projectName || null;
this.organizationId = options.organizationId || null;
this.projectId = options.projectId || null;
this.headers = {
Authorization: `Token ${this.apiKey}`,
@@ -106,19 +101,28 @@ export default class MemoryClient {
});
this._validateApiKey();
// Initialize with a temporary ID that will be updated
this.telemetryId = "";
// Initialize the client
this._initializeClient();
}
private async _initializeClient() {
try {
// Generate telemetry ID
await this.ping();
if (!this.telemetryId) {
this.telemetryId = generateHash(this.apiKey);
}
this._validateOrgProject();
// Capture initialization event
captureClientEvent("init", this, {
api_version: "v1",
client_type: "MemoryClient",
}).catch((error: any) => {
console.error("Failed to capture event:", error);
@@ -156,19 +160,16 @@ export default class MemoryClient {
throw createExceptionFromResponse(response.status, errorData);
}
const jsonResponse = await response.json();
return snakeToCamelKeys(jsonResponse);
return jsonResponse;
}
_preparePayload(
messages: Array<Message>,
options: Record<string, any>,
): object {
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
const payload: any = {};
payload.messages = messages;
return camelToSnakeKeys({ ...payload, ...options });
return { ...payload, ...options };
}
_prepareParams(options: Record<string, any>): object {
_prepareParams(options: MemoryOptions): object {
return Object.fromEntries(
Object.entries(options).filter(([_, v]) => v != null),
);
@@ -194,11 +195,12 @@ export default class MemoryClient {
throw new APIError(response.message || "API Key is invalid");
}
const { orgId, projectId, userEmail } = response;
const { org_id, project_id, user_email } = response;
if (orgId) this.organizationId = orgId;
if (projectId) this.projectId = projectId;
if (userEmail) this.telemetryId = userEmail;
// Only update if values are actually present
if (org_id && !this.organizationId) this.organizationId = org_id;
if (project_id && !this.projectId) this.projectId = project_id;
if (user_email) this.telemetryId = user_email;
} catch (error: any) {
// Pass through structured exceptions and APIError
if (error instanceof MemoryError || error instanceof APIError) {
@@ -213,16 +215,35 @@ export default class MemoryClient {
async add(
messages: Array<Message>,
options: AddMemoryOptions & Record<string, any> = {},
options: MemoryOptions & Record<string, any> = {},
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
if (options.api_version) {
options.version = options.api_version.toString() || "v2";
}
const payload = this._preparePayload(messages, options);
// get payload keys whose value is not null or undefined
const payloadKeys = Object.keys(payload);
this._captureEvent("add", [payloadKeys]);
const response = await this._fetchWithErrorHandling(
`${this.host}/v3/memories/add/`,
`${this.host}/v1/memories/`,
{
method: "POST",
headers: this.headers,
@@ -255,6 +276,7 @@ export default class MemoryClient {
}
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payload: Record<string, any> = {};
if (text !== undefined) payload.text = text;
if (metadata !== undefined) payload.metadata = metadata;
@@ -285,52 +307,80 @@ export default class MemoryClient {
);
}
async getAll(options?: GetAllMemoryOptions): Promise<PaginatedMemories> {
// Reject top-level entity params - must use filters instead
rejectTopLevelEntityParams(options as Record<string, any>, "getAll");
async getAll(options?: SearchOptions): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]);
const { page, pageSize, filters, ...rest } = options ?? {};
const body: Record<string, any> = {
...camelToSnakeKeys(rest),
...(filters && { filters }),
};
let url = `${this.host}/v3/memories/`;
if (page && pageSize) {
url += `?page=${page}&page_size=${pageSize}`;
const { api_version, page, page_size, ...otherOptions } = options ?? {};
if (this.organizationName != null && this.projectName != null) {
otherOptions.org_name = this.organizationName;
otherOptions.project_name = this.projectName;
}
const response = await this._fetchWithErrorHandling(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
return response;
let appendedParams = "";
let paginated_response = false;
if (page && page_size) {
appendedParams += `page=${page}&page_size=${page_size}`;
paginated_response = true;
}
if (this.organizationId != null && this.projectId != null) {
otherOptions.org_id = this.organizationId;
otherOptions.project_id = this.projectId;
if (otherOptions.org_name) delete otherOptions.org_name;
if (otherOptions.project_name) delete otherOptions.project_name;
}
if (api_version === "v2") {
let url = paginated_response
? `${this.host}/v2/memories/?${appendedParams}`
: `${this.host}/v2/memories/`;
return this._fetchWithErrorHandling(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(otherOptions),
});
} else {
// @ts-ignore
const params = new URLSearchParams(this._prepareParams(otherOptions));
const url = paginated_response
? `${this.host}/v1/memories/?${params}&${appendedParams}`
: `${this.host}/v1/memories/?${params}`;
return this._fetchWithErrorHandling(url, {
headers: this.headers,
});
}
}
async search(
query: string,
options?: SearchMemoryOptions,
): Promise<{ results: Array<Memory> }> {
// Reject top-level entity params - must use filters instead
rejectTopLevelEntityParams(options as Record<string, any>, "search");
options?: SearchOptions & Record<string, any>,
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("search", [payloadKeys]);
const { filters, ...rest } = options ?? {};
const payload: Record<string, any> = {
query,
output_format: "v1.1",
...camelToSnakeKeys(rest),
...(filters && { filters }),
};
const { api_version, ...otherOptions } = options ?? {};
const payload = { query, ...otherOptions };
if (this.organizationName != null && this.projectName != null) {
payload.org_name = this.organizationName;
payload.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
payload.org_id = this.organizationId;
payload.project_id = this.projectId;
if (payload.org_name) delete payload.org_name;
if (payload.project_name) delete payload.project_name;
}
const endpoint =
api_version === "v2" ? "/v2/memories/search/" : "/v1/memories/search/";
const response = await this._fetchWithErrorHandling(
`${this.host}/v3/memories/search/`,
`${this.host}${endpoint}`,
{
method: "POST",
headers: this.headers,
@@ -352,15 +402,25 @@ export default class MemoryClient {
);
}
async deleteAll(
options: DeleteAllMemoryOptions = {},
): Promise<{ message: string }> {
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]);
const snakeOptions = camelToSnakeKeys(this._prepareParams(options));
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
// @ts-ignore
const params = new URLSearchParams(snakeOptions);
const params = new URLSearchParams(this._prepareParams(options));
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/?${params}`,
{
@@ -383,20 +443,31 @@ export default class MemoryClient {
return response;
}
async users(options?: {
page?: number;
pageSize?: number;
}): Promise<AllUsers> {
async users(): Promise<AllUsers> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("users", []);
let url = `${this.host}/v1/entities/`;
const params: string[] = [];
if (options?.page) params.push(`page=${options.page}`);
if (options?.pageSize) params.push(`page_size=${options.pageSize}`);
if (params.length) url += `?${params.join("&")}`;
const response = await this._fetchWithErrorHandling(url, {
headers: this.headers,
});
const options: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
// @ts-ignore
const params = new URLSearchParams(options);
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/entities/?${params}`,
{
headers: this.headers,
},
);
return response;
}
@@ -424,25 +495,26 @@ export default class MemoryClient {
async deleteUsers(
params: {
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
user_id?: string;
agent_id?: string;
app_id?: string;
run_id?: string;
} = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
let to_delete: Array<{ type: string; name: string }> = [];
const { userId, agentId, appId, runId } = params;
const { user_id, agent_id, app_id, run_id } = params;
if (userId) {
to_delete = [{ type: "user", name: userId }];
} else if (agentId) {
to_delete = [{ type: "agent", name: agentId }];
} else if (appId) {
to_delete = [{ type: "app", name: appId }];
} else if (runId) {
to_delete = [{ type: "run", name: runId }];
if (user_id) {
to_delete = [{ type: "user", name: user_id }];
} else if (agent_id) {
to_delete = [{ type: "agent", name: agent_id }];
} else if (app_id) {
to_delete = [{ type: "app", name: app_id }];
} else if (run_id) {
to_delete = [{ type: "run", name: run_id }];
} else {
const entities = await this.users();
to_delete = entities.results.map((entity) => ({
@@ -455,9 +527,29 @@ export default class MemoryClient {
throw new Error("No entities to delete");
}
const requestOptions: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) {
requestOptions.org_name = this.organizationName;
requestOptions.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
requestOptions.org_id = this.organizationId;
requestOptions.project_id = this.projectId;
if (requestOptions.org_name) delete requestOptions.org_name;
if (requestOptions.project_name) delete requestOptions.project_name;
}
// Delete each entity and handle errors
for (const entity of to_delete) {
try {
await this.client.delete(`/v2/entities/${entity.type}/${entity.name}/`);
await this.client.delete(
`/v2/entities/${entity.type}/${entity.name}/`,
{
params: requestOptions,
},
);
} catch (error: any) {
throw new APIError(
`Failed to delete ${entity.type} ${entity.name}: ${error.message}`,
@@ -466,12 +558,18 @@ export default class MemoryClient {
}
this._captureEvent("delete_users", [
{ userId, agentId, appId, runId, sync_type: "sync" },
{
user_id: user_id,
agent_id: agent_id,
app_id: app_id,
run_id: run_id,
sync_type: "sync",
},
]);
return {
message:
userId || agentId || appId || runId
user_id || agent_id || app_id || run_id
? "Entity deleted successfully."
: "All users, agents, apps and runs deleted.",
};
@@ -514,6 +612,7 @@ export default class MemoryClient {
async getProject(options: ProjectOptions): Promise<ProjectResponse> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_project", [payloadKeys]);
const { fields } = options;
@@ -525,7 +624,7 @@ export default class MemoryClient {
}
const params = new URLSearchParams();
fields?.forEach((field) => params.append("fields", camelToSnake(field)));
fields?.forEach((field) => params.append("fields", field));
const response = await this._fetchWithErrorHandling(
`${this.host}/api/v1/orgs/organizations/${this.organizationId}/projects/${this.projectId}/?${params.toString()}`,
@@ -540,6 +639,7 @@ export default class MemoryClient {
prompts: PromptUpdatePayload,
): Promise<Record<string, any>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("update_project", []);
if (!(this.organizationId && this.projectId)) {
throw new Error(
@@ -552,7 +652,7 @@ export default class MemoryClient {
{
method: "PATCH",
headers: this.headers,
body: JSON.stringify(camelToSnakeKeys(prompts)),
body: JSON.stringify(prompts),
},
);
return response;
@@ -636,7 +736,7 @@ export default class MemoryClient {
{
method: "POST",
headers: this.headers,
body: JSON.stringify(camelToSnakeKeys(data)),
body: JSON.stringify(data),
},
);
return response;
@@ -648,20 +748,21 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
this._captureEvent("create_memory_export", []);
// Return if missing filters or schema
if (!data.filters || !data.schema) {
throw new Error("Missing filters or schema");
}
const { filters, ...rest } = data;
// Add Org and Project ID
data.org_id = this.organizationId?.toString() || null;
data.project_id = this.projectId?.toString() || null;
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify({
...camelToSnakeKeys(rest),
filters,
}),
body: JSON.stringify(data),
},
);
@@ -674,20 +775,19 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
this._captureEvent("get_memory_export", []);
if (!data.memoryExportId && !data.filters) {
throw new Error("Missing memoryExportId or filters");
if (!data.memory_export_id && !data.filters) {
throw new Error("Missing memory_export_id or filters");
}
const { filters, ...rest } = data;
data.org_id = this.organizationId?.toString() || "";
data.project_id = this.projectId?.toString() || "";
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/get/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify({
...camelToSnakeKeys(rest),
...(filters && { filters }),
}),
body: JSON.stringify(data),
},
);
return response;
+102 -92
View File
@@ -1,66 +1,59 @@
// ─── Entity Options (for add/delete — top-level identity) ───
export interface EntityOptions {
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
interface Common {
project_id?: string | null;
org_id?: string | null;
}
// ─── Per-Method Options ─────────────────────────────────────
export interface AddMemoryOptions extends EntityOptions {
export interface MemoryOptions {
api_version?: API_VERSION | string;
version?: API_VERSION | string;
user_id?: string;
agent_id?: string;
app_id?: string;
run_id?: string;
metadata?: Record<string, any>;
filters?: Record<string, any>;
org_name?: string | null; // Deprecated
project_name?: string | null; // Deprecated
org_id?: string | number | null;
project_id?: string | number | null;
infer?: boolean;
customCategories?: custom_categories[];
customInstructions?: string;
timestamp?: number;
structuredDataSchema?: Record<string, any>;
}
export interface SearchMemoryOptions {
filters?: Record<string, any>;
metadata?: Record<string, any>;
topK?: number;
threshold?: number;
rerank?: boolean;
fields?: string[];
categories?: string[];
}
export interface GetAllMemoryOptions {
filters?: Record<string, any>;
page?: number;
pageSize?: number;
startDate?: string;
endDate?: string;
categories?: string[];
page_size?: number;
includes?: string;
excludes?: string;
enable_graph?: boolean;
start_date?: string;
end_date?: string;
custom_categories?: custom_categories[];
custom_instructions?: string;
timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
filter_memories?: boolean;
immutable?: boolean;
structured_data_schema?: Record<string, any>;
}
export interface DeleteAllMemoryOptions extends EntityOptions {}
// ─── Project Options ────────────────────────────────────────
export interface ProjectOptions {
fields?: string[];
}
export interface PromptUpdatePayload {
customInstructions?: string;
customCategories?: custom_categories[];
retrievalCriteria?: any[];
version?: string;
memoryDepth?: string | null;
usecaseSetting?: string | number;
multilingual?: boolean;
[key: string]: any;
export enum OutputFormat {
V1 = "v1.0",
V1_1 = "v1.1",
}
export enum API_VERSION {
V1 = "v1",
V2 = "v2",
}
// ─── Enums ──────────────────────────────────────────────────
export enum Feedback {
POSITIVE = "POSITIVE",
NEGATIVE = "NEGATIVE",
VERY_NEGATIVE = "VERY_NEGATIVE",
}
// ─── Message Types ──────────────────────────────────────────
export interface MultiModalMessages {
type: "image_url";
image_url: {
@@ -75,9 +68,30 @@ export interface Messages {
export interface Message extends Messages {}
// ─── Response Types (camelCase — converted from API snake_case) ─────
export interface MemoryData {
memory: string;
export interface MemoryHistory {
id: string;
memory_id: string;
input: Array<Messages>;
old_memory: string | null;
new_memory: string | null;
user_id: string;
categories: Array<string>;
event: Event | string;
created_at: Date;
updated_at: Date;
}
export interface SearchOptions extends MemoryOptions {
api_version?: API_VERSION | string;
limit?: number;
enable_graph?: boolean;
threshold?: number;
top_k?: number;
only_metadata_based_search?: boolean;
keyword_search?: boolean;
fields?: string[];
categories?: string[];
rerank?: boolean;
}
enum Event {
@@ -87,37 +101,28 @@ enum Event {
NOOP = "NOOP",
}
export interface MemoryData {
memory: string;
}
export interface Memory {
id: string;
messages?: Array<Messages>;
event?: Event | string;
data?: MemoryData | null;
memory?: string;
userId?: string;
user_id?: string;
hash?: string;
categories?: Array<string>;
createdAt?: Date;
updatedAt?: Date;
memoryType?: string;
created_at?: Date;
updated_at?: Date;
memory_type?: string;
score?: number;
metadata?: any | null;
owner?: string | null;
agentId?: string | null;
appId?: string | null;
runId?: string | null;
}
export interface MemoryHistory {
id: string;
memoryId: string;
input: Array<Messages>;
oldMemory: string | null;
newMemory: string | null;
userId: string;
categories: Array<string>;
event: Event | string;
createdAt: Date;
updatedAt: Date;
agent_id?: string | null;
app_id?: string | null;
run_id?: string | null;
}
export interface MemoryUpdateBody {
@@ -128,9 +133,9 @@ export interface MemoryUpdateBody {
export interface User {
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
totalMemories: number;
created_at: Date;
updated_at: Date;
total_memories: number;
owner: string;
type: string;
}
@@ -142,16 +147,9 @@ export interface AllUsers {
previous: any;
}
export interface PaginatedMemories {
count: number;
next: string | null;
previous: string | null;
results: Array<Memory>;
}
export interface ProjectResponse {
customInstructions?: string;
customCategories?: string[];
custom_instructions?: string;
custom_categories?: string[];
[key: string]: any;
}
@@ -159,7 +157,20 @@ interface custom_categories {
[key: string]: any;
}
// ─── Webhook Types ──────────────────────────────────────────
export interface PromptUpdatePayload {
custom_instructions?: string;
custom_categories?: custom_categories[];
retrieval_criteria?: any[];
enable_graph?: boolean;
version?: string;
inclusion_prompt?: string;
exclusion_prompt?: string;
memory_depth?: string | null;
usecase_setting?: string | number;
multilingual?: boolean;
[key: string]: any;
}
export enum WebhookEvent {
MEMORY_ADDED = "memory_add",
MEMORY_UPDATED = "memory_update",
@@ -168,14 +179,14 @@ export enum WebhookEvent {
}
export interface Webhook {
webhookId?: string;
webhook_id?: string;
name: string;
url: string;
project?: string;
createdAt?: Date;
updatedAt?: Date;
isActive?: boolean;
eventTypes?: WebhookEvent[];
created_at?: Date;
updated_at?: Date;
is_active?: boolean;
event_types?: WebhookEvent[];
}
export interface WebhookCreatePayload {
@@ -191,20 +202,19 @@ export interface WebhookUpdatePayload {
eventTypes?: WebhookEvent[];
}
// ─── Feedback & Export Types ────────────────────────────────
export interface FeedbackPayload {
memoryId: string;
memory_id: string;
feedback?: Feedback | null;
feedbackReason?: string | null;
feedback_reason?: string | null;
}
export interface CreateMemoryExportPayload {
export interface CreateMemoryExportPayload extends Common {
schema: Record<string, any>;
filters: Record<string, any>;
exportInstructions?: string;
export_instructions?: string;
}
export interface GetMemoryExportPayload {
export interface GetMemoryExportPayload extends Common {
filters?: Record<string, any>;
memoryExportId?: string;
memory_export_id?: string;
}
@@ -26,7 +26,7 @@ describeIntegration("MemoryClient Integration — Batch Operations", () => {
beforeAll(async () => {
cleanup = suppressTelemetryNoise();
client = await createTestClient();
client = createTestClient();
memoryIds = await seedTestMemories(client, TEST_USER_ID);
});
@@ -26,9 +26,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
let cleanup: () => void;
let memoryIds: string[] = [];
beforeAll(async () => {
beforeAll(() => {
cleanup = suppressTelemetryNoise();
client = await createTestClient();
client = createTestClient();
});
afterAll(async () => {
@@ -51,13 +51,17 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
},
];
const result = await client.add(messages, {
userId: TEST_USER_ID,
});
const result = await client.add(messages, { user_id: TEST_USER_ID });
// v3 API processes memories asynchronously — returns PENDING
expect(result).toHaveProperty("eventId");
expect(result).toHaveProperty("status");
// API processes memories asynchronously — returns PENDING
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
// Validate response shape
for (const item of result) {
expect(item).toHaveProperty("status");
expect(item).toHaveProperty("event_id");
}
});
test("adds a second batch of messages", async () => {
@@ -72,10 +76,8 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
},
];
const result = await client.add(messages, {
userId: TEST_USER_ID,
});
expect(result).toHaveProperty("eventId");
const result = await client.add(messages, { user_id: TEST_USER_ID });
expect(Array.isArray(result)).toBe(true);
});
test("memories become available after async processing", async () => {
@@ -101,17 +103,17 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
expect(memory.id).toBe(memoryId);
expect(typeof memory.memory).toBe("string");
expect(memory.memory!.length).toBeGreaterThan(0);
expect(typeof memory.userId).toBe("string");
expect(typeof memory.user_id).toBe("string");
expect(
memory.metadata === null || typeof memory.metadata === "object",
).toBe(true);
expect(
Array.isArray(memory.categories) || memory.categories === null,
).toBe(true);
expect(new Date(memory.createdAt || "").toString()).not.toBe(
expect(new Date(memory.created_at || "").toString()).not.toBe(
"Invalid Date",
);
expect(new Date(memory.updatedAt || "").toString()).not.toBe(
expect(new Date(memory.updated_at || "").toString()).not.toBe(
"Invalid Date",
);
});
@@ -120,20 +122,12 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
// ─── Get all ──────────────────────────────────────────────
describe("get all memories", () => {
test("returns all memories for test user", async () => {
const response = await client.getAll({
filters: { user_id: TEST_USER_ID },
});
const memories = await client.getAll({ user_id: TEST_USER_ID });
// Paginated shape: { count, next, previous, results: [...] }
expect(response).toHaveProperty("count");
expect(response).toHaveProperty("next");
expect(response).toHaveProperty("previous");
expect(response).toHaveProperty("results");
expect(typeof response.count).toBe("number");
expect(Array.isArray(response.results)).toBe(true);
expect(response.results.length).toBeGreaterThanOrEqual(memoryIds.length);
expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
for (const mem of response.results) {
for (const mem of memories) {
expect(typeof mem.id).toBe("string");
expect(typeof mem.memory).toBe("string");
}
@@ -141,17 +135,13 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("returns paginated results with page and page_size", async () => {
const page1 = await client.getAll({
filters: { user_id: TEST_USER_ID },
user_id: TEST_USER_ID,
page: 1,
pageSize: 1,
page_size: 1,
});
// Paginated response is an object with results array
expect(page1).toBeDefined();
expect(page1).toHaveProperty("count");
expect(page1).toHaveProperty("next");
expect(page1).toHaveProperty("previous");
expect(Array.isArray(page1.results)).toBe(true);
expect(page1.results.length).toBeLessThanOrEqual(1);
});
});
@@ -199,28 +189,27 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
},
],
{
userId: TEST_USER_ID,
user_id: TEST_USER_ID,
metadata: { source: "integration-test", category: "preferences" },
},
);
expect(result).toHaveProperty("eventId");
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
});
test("getAll for non-existent user returns empty results", async () => {
const response = await client.getAll({
filters: { user_id: `nonexistent-user-${randomUUID()}` },
test("getAll for non-existent user returns empty array", async () => {
const memories = await client.getAll({
user_id: `nonexistent-user-${randomUUID()}`,
});
expect(response).toHaveProperty("results");
expect(Array.isArray(response.results)).toBe(true);
expect(response.results.length).toBe(0);
expect(response.count).toBe(0);
expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBe(0);
});
test("deleteAll for non-existent user does not throw", async () => {
const result = await client.deleteAll({
userId: `nonexistent-user-${randomUUID()}`,
user_id: `nonexistent-user-${randomUUID()}`,
});
expect(result).toBeDefined();
@@ -250,15 +239,13 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
// ─── Delete all + delete user ─────────────────────────────
describe("cleanup operations", () => {
test("deletes all memories for test user", async () => {
const result = await client.deleteAll({
userId: TEST_USER_ID,
});
const result = await client.deleteAll({ user_id: TEST_USER_ID });
expect(result).toBeDefined();
expect(typeof result.message).toBe("string");
});
test("deletes the test user entity", async () => {
const result = await client.deleteUsers({ userId: TEST_USER_ID });
const result = await client.deleteUsers({ user_id: TEST_USER_ID });
expect(result).toBeDefined();
expect(result.message).toBe("Entity deleted successfully.");
});
@@ -18,10 +18,10 @@ export default async function globalSetup() {
// Full project wipe — all four filters set explicitly
try {
await client.deleteAll({
userId: "*",
agentId: "*",
appId: "*",
runId: "*",
user_id: "*",
agent_id: "*",
app_id: "*",
run_id: "*",
});
} catch {
// ignore — may 404 if no data exists
@@ -17,10 +17,10 @@ export default async function globalTeardown() {
try {
await client.deleteAll({
userId: "*",
agentId: "*",
appId: "*",
runId: "*",
user_id: "*",
agent_id: "*",
app_id: "*",
run_id: "*",
});
} catch {
// ignore
+14 -22
View File
@@ -18,13 +18,9 @@ export const describeIntegration = API_KEY ? describe : describe.skip;
* Create a MemoryClient with the real API key.
* Call this inside beforeAll — not at module scope — so it only
* runs when the suite is not skipped.
*
* Returns an initialized client ready for immediate use.
*/
export async function createTestClient(): Promise<MemoryClient> {
const client = new MemoryClient({ apiKey: API_KEY! });
await client.ping();
return client;
export function createTestClient(): MemoryClient {
return new MemoryClient({ apiKey: API_KEY! });
}
/**
@@ -67,11 +63,8 @@ export async function waitForMemories(
maxRetries = 4,
): Promise<Memory[]> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await withRetry(() =>
client.getAll({ filters: { user_id: userId } }),
);
const memories = response.results ?? [];
if (memories.length >= minCount) {
const memories = await withRetry(() => client.getAll({ user_id: userId }));
if (Array.isArray(memories) && memories.length >= minCount) {
return memories;
}
if (attempt < maxRetries) {
@@ -97,9 +90,8 @@ export async function waitForSearchResults(
maxRetries = 4,
): Promise<Memory[]> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await withRetry(() => client.search(query, options));
const results = response?.results ?? [];
if (results.length > 0) {
const results = await withRetry(() => client.search(query, options));
if (Array.isArray(results) && results.length > 0) {
return results;
}
if (attempt < maxRetries) {
@@ -160,7 +152,7 @@ export async function seedTestMemories(
"Nice to meet you! I'll remember that your favorite color is blue.",
},
],
{ userId },
{ user_id: userId },
),
);
@@ -176,7 +168,7 @@ export async function seedTestMemories(
content: "Got it, you're a software engineer at Acme Corp!",
},
],
{ userId },
{ user_id: userId },
),
);
@@ -192,12 +184,12 @@ export async function cleanupTestUser(
userId: string,
): Promise<void> {
try {
await client.deleteAll({ userId });
await client.deleteAll({ user_id: userId });
} catch {
// ignore
}
try {
await client.deleteUsers({ userId });
await client.deleteUsers({ user_id: userId });
} catch {
// ignore
}
@@ -215,10 +207,10 @@ export async function fullProjectCleanup(client: MemoryClient): Promise<void> {
// Delete all memories — all four filters set explicitly
try {
await client.deleteAll({
userId: "*",
agentId: "*",
appId: "*",
runId: "*",
user_id: "*",
agent_id: "*",
app_id: "*",
run_id: "*",
});
} catch {
// ignore — may 404 if no data exists
@@ -24,17 +24,17 @@ describeIntegration("MemoryClient Integration — Initialization", () => {
let client: MemoryClient;
let cleanup: () => void;
beforeAll(async () => {
beforeAll(() => {
cleanup = suppressTelemetryNoise();
client = await createTestClient();
client = createTestClient();
});
afterAll(() => cleanup());
test("client pings successfully", async () => {
test("client pings successfully and resolves org/project", async () => {
await client.ping();
// org/project are now resolved internally from the API key
expect(client.telemetryId).toBeTruthy();
expect(client.organizationId).toBeTruthy();
expect(client.projectId).toBeTruthy();
});
test("get with invalid ID throws ValidationError", async () => {
@@ -27,7 +27,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
beforeAll(async () => {
cleanup = suppressTelemetryNoise();
client = await createTestClient();
client = createTestClient();
await seedTestMemories(client, TEST_USER_ID);
});
@@ -67,16 +67,16 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
expect(project).toBeDefined();
expect(typeof project).toBe("object");
expect("customInstructions" in project).toBe(true);
expect("custom_instructions" in project).toBe(true);
originalInstructions = project.customInstructions;
originalInstructions = project.custom_instructions;
});
test("updates project custom_instructions via updateProject()", async () => {
const testInstruction = `integration-test-${randomUUID().slice(0, 8)}`;
const result = await client.updateProject({
customInstructions: testInstruction,
custom_instructions: testInstruction,
});
expect(result).toBeDefined();
@@ -85,11 +85,11 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
const project = await client.getProject({
fields: ["custom_instructions"],
});
expect(project.customInstructions).toBe(testInstruction);
expect(project.custom_instructions).toBe(testInstruction);
// Restore original
await client.updateProject({
customInstructions: originalInstructions || "",
custom_instructions: originalInstructions || "",
});
});
});
@@ -120,26 +120,26 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED],
}),
);
createdWebhookId = result.webhookId!;
expect(result.webhookId).toBeDefined();
createdWebhookId = result.webhook_id!;
expect(result.webhook_id).toBeDefined();
});
test("createWebhook returns the correct name", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(wh!.name).toBe(hookName);
});
test("createWebhook returns the correct url", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(wh!.url).toBe(hookUrl);
});
test("createWebhook returns the correct event_types", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
expect(wh!.eventTypes?.sort()).toStrictEqual(
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(wh!.event_types?.sort()).toStrictEqual(
[WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED].sort(),
);
});
@@ -152,14 +152,14 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
test("getWebhooks includes the created webhook", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(found).toBeDefined();
});
test("getWebhooks shows the webhook as active", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
expect(found!.isActive).toBe(true);
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(found!.is_active).toBe(true);
});
// ─── Update ────────────────────────────────────────────
@@ -177,14 +177,14 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
test("updateWebhook persists the new name", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
const updated = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(updated!.name).toBe(updatedName);
});
test("updateWebhook persists the new event_types", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
expect(updated!.eventTypes?.sort()).toStrictEqual(
const updated = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(updated!.event_types?.sort()).toStrictEqual(
[WebhookEvent.MEMORY_DELETED].sort(),
);
});
@@ -199,7 +199,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
test("deleteWebhook removes the webhook from the list", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
expect(found).toBeUndefined();
createdWebhookId = "";
});
@@ -1,7 +1,7 @@
/**
* Integration tests: Search and history operations.
*
* Tests search, filtered search, and memory history against the real API.
* Tests search v1, search v2, and memory history against the real API.
*
* Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
*/
@@ -27,7 +27,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
beforeAll(async () => {
cleanup = suppressTelemetryNoise();
client = await createTestClient();
client = createTestClient();
memoryIds = await seedTestMemories(client, TEST_USER_ID);
});
@@ -36,14 +36,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
cleanup();
});
// ─── Search ─────────────────────────────────────────────
describe("search", () => {
// ─── Search v1 ────────────────────────────────────────────
describe("search v1", () => {
test("searches memories by user_id and returns results with scores", async () => {
// Search index may lag behind listing index — poll until ready
const results = await waitForSearchResults(
client,
"What is my favorite color?",
{ filters: { user_id: TEST_USER_ID } },
{ user_id: TEST_USER_ID },
);
expect(Array.isArray(results)).toBe(true);
@@ -57,14 +57,15 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
});
});
// ─── Search with filters ─────────────────────────────────
describe("search with filters", () => {
// ─── Search v2 ────────────────────────────────────────────
describe("search v2", () => {
test("searches with OR filters and returns results", async () => {
const results = await waitForSearchResults(
client,
"What do you know about me?",
{
filters: { OR: [{ user_id: TEST_USER_ID }] },
api_version: "v2",
},
);
@@ -89,15 +90,15 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
const entry = history[0];
expect(typeof entry.id).toBe("string");
expect(typeof entry.memoryId).toBe("string");
expect(typeof entry.memory_id).toBe("string");
expect(["ADD", "UPDATE", "DELETE", "NOOP"]).toContain(entry.event);
expect(new Date(entry.createdAt).toString()).not.toBe("Invalid Date");
expect(new Date(entry.updatedAt).toString()).not.toBe("Invalid Date");
expect(new Date(entry.created_at).toString()).not.toBe("Invalid Date");
expect(new Date(entry.updated_at).toString()).not.toBe("Invalid Date");
expect(
entry.newMemory === null || typeof entry.newMemory === "string",
entry.new_memory === null || typeof entry.new_memory === "string",
).toBe(true);
expect(
entry.oldMemory === null || typeof entry.oldMemory === "string",
entry.old_memory === null || typeof entry.old_memory === "string",
).toBe(true);
const events = history.map((h) => h.event);
@@ -108,24 +109,24 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
// ─── Edge cases ─────────────────────────────────────────
describe("edge cases", () => {
test("search for non-existent user returns empty results", async () => {
const response = await client.search("test search query", {
filters: { user_id: `nonexistent-user-${randomUUID()}` },
const results = await client.search("anything", {
user_id: `nonexistent-user-${randomUUID()}`,
});
expect(response).toHaveProperty("results");
expect(response.results).toHaveLength(0);
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBe(0);
});
test("search with top_k param does not throw", async () => {
const response = await client.search(
test("search with limit param does not throw", async () => {
const results = await client.search(
"Tell me about integration test user",
{
filters: { user_id: TEST_USER_ID },
topK: 1,
user_id: TEST_USER_ID,
limit: 1,
},
);
expect(response).toHaveProperty("results");
expect(Array.isArray(results)).toBe(true);
});
});
});
@@ -1,13 +1,15 @@
/**
* MemoryClient unit tests — add, get, update, delete, deleteAll, history.
* MemoryClient unit tests — add, get, getAll, update, delete, deleteAll, history.
* Tests verify request construction, not mock response echo.
*/
import { MemoryClient } from "../mem0";
import type { MemoryHistory } from "../mem0.types";
import type { Memory, MemoryHistory } from "../mem0.types";
import {
createMockMemory,
createMockMemoryHistory,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import {
setupMockFetch,
@@ -21,33 +23,33 @@ installConsoleSuppression();
// ─── add() ───────────────────────────────────────────────
describe("MemoryClient - add()", () => {
test("sends POST to /v3/memories/add/", async () => {
test("sends POST to /v1/memories/", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] });
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add([{ role: "user", content: "Hello" }], { userId: "u1" });
await client.add([{ role: "user", content: "Hello" }], { user_id: "u1" });
expect(findFetchCall(mock, "/v3/memories/add/", "POST")).toBeDefined();
expect(findFetchCall(mock, "/v1/memories/", "POST")).toBeDefined();
});
test("includes messages in request body", async () => {
const messages = [{ role: "user" as const, content: "Hello, I am Alex" }];
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] });
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add(messages, { userId: "u1" });
await client.add(messages, { user_id: "u1" });
const call = findFetchCall(mock, "/v3/memories/add/", "POST");
const call = findFetchCall(mock, "/v1/memories/", "POST");
expect(getFetchBody(call!).messages).toEqual(messages);
});
test("includes user_id in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] });
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -55,19 +57,53 @@ describe("MemoryClient - add()", () => {
user_id: "user_1",
});
const call = findFetchCall(mock, "/v3/memories/add/", "POST");
const call = findFetchCall(mock, "/v1/memories/", "POST");
expect(getFetchBody(call!).user_id).toBe("user_1");
});
test("attaches org_id from constructor to payload", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
const body = getFetchBody(call!);
expect(body.org_id).toBe(TEST_ORG_ID);
});
test("attaches project_id from constructor to payload", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
const body = getFetchBody(call!);
expect(body.project_id).toBe(TEST_PROJECT_ID);
});
test("sends empty messages array without crashing", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/add/", { status: 200, body: [] });
extra.set("/v1/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add([], { userId: "u1" });
await client.add([], { user_id: "u1" });
const call = findFetchCall(mock, "/v3/memories/add/", "POST");
const call = findFetchCall(mock, "/v1/memories/", "POST");
expect(getFetchBody(call!).messages).toEqual([]);
});
});
@@ -106,6 +142,67 @@ describe("MemoryClient - get()", () => {
});
});
// ─── getAll() ────────────────────────────────────────────
describe("MemoryClient - getAll()", () => {
test("uses v2 POST endpoint when api_version=v2", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({ user_id: "u1", api_version: "v2" });
expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined();
});
test("uses v1 GET endpoint by default with user_id as query param", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({ user_id: "u1" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
c[0].includes("/v1/memories/?") && !c[1]?.method,
);
expect(call).toBeDefined();
expect(call![0]).toContain("user_id=u1");
});
test("appends page and page_size to URL as query params", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({
user_id: "u1",
api_version: "v2",
page: 2,
page_size: 25,
});
const call = mock.mock.calls.find((c: [string, RequestInit]) =>
c[0].includes("page="),
);
expect(call![0]).toContain("page=2");
expect(call![0]).toContain("page_size=25");
});
test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.getAll();
expect(Array.isArray(result)).toBe(true);
});
});
// ─── update() ────────────────────────────────────────────
describe("MemoryClient - update()", () => {
@@ -215,7 +312,7 @@ describe("MemoryClient - deleteAll()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.deleteAll({ userId: "u1" });
await client.deleteAll({ user_id: "u1" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
@@ -231,7 +328,7 @@ describe("MemoryClient - deleteAll()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.deleteAll({ userId: "user@email.com" });
await client.deleteAll({ user_id: "user@email.com" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
@@ -7,7 +7,13 @@ import {
ValidationError,
MemoryError,
} from "../../common/exceptions";
import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
import {
createMockFetch,
TEST_API_KEY,
TEST_HOST,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import {
setupMockFetch,
installConsoleSuppression,
@@ -49,6 +55,24 @@ describe("MemoryClient - Initialization", () => {
expect(client.host).toBe(TEST_HOST);
});
test("sets organizationId from constructor", () => {
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
expect(client.organizationId).toBe(TEST_ORG_ID);
});
test("sets projectId from constructor", () => {
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
expect(client.projectId).toBe(TEST_PROJECT_ID);
});
test("sets Authorization header with Token prefix", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
@@ -63,6 +87,20 @@ describe("MemoryClient - Initialization", () => {
// ─── Ping ────────────────────────────────────────────────
describe("MemoryClient - ping()", () => {
test("sets organizationId from ping response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.organizationId).toBe(TEST_ORG_ID);
});
test("sets projectId from ping response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.projectId).toBe(TEST_PROJECT_ID);
});
test("sets telemetryId from user_email in response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -70,6 +108,28 @@ describe("MemoryClient - ping()", () => {
expect(client.telemetryId).toBe("test@example.com");
});
test("preserves constructor organizationId over ping response", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: "my_org",
projectId: "my_proj",
});
await client.ping();
expect(client.organizationId).toBe("my_org");
});
test("preserves constructor projectId over ping response", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: "my_org",
projectId: "my_proj",
});
await client.ping();
expect(client.projectId).toBe("my_proj");
});
test("throws AuthenticationError on 401 response", async () => {
const { AuthenticationError } = await import("../../common/exceptions");
const responses = new Map<string, { status: number; body: unknown }>();
@@ -4,7 +4,12 @@
*/
import { MemoryClient } from "../mem0";
import { Feedback } from "../mem0.types";
import { createMockFetch, TEST_API_KEY } from "./helpers";
import {
createMockFetch,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -17,7 +22,7 @@ installConsoleSuppression();
// ─── getProject() ───────────────────────────────────────
describe("MemoryClient - getProject()", () => {
test("throws when organizationId and projectId not set (ping returns no org)", async () => {
test("throws when organizationId and projectId not set", async () => {
const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", { status: 200, body: { status: "ok" } });
global.fetch = createMockFetch(responses);
@@ -42,9 +47,11 @@ describe("MemoryClient - getProject()", () => {
});
const mock = setupMockFetch(extra);
// org/project come from ping mock response
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.getProject({ fields: ["custom_instructions"] });
const call = mock.mock.calls.find(
@@ -67,8 +74,11 @@ describe("MemoryClient - updateProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.updateProject({
custom_instructions: "Updated instructions",
});
@@ -85,8 +95,11 @@ describe("MemoryClient - updateProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.updateProject({
custom_instructions: "Updated instructions",
});
@@ -111,9 +124,9 @@ describe("MemoryClient - feedback()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.feedback({
memoryId: "mem_123",
memory_id: "mem_123",
feedback: Feedback.POSITIVE,
feedbackReason: "Very helpful",
feedback_reason: "Very helpful",
});
const call = findFetchCall(mock, "/v1/feedback/", "POST");
@@ -130,9 +143,9 @@ describe("MemoryClient - feedback()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.feedback({
memoryId: "mem_123",
memory_id: "mem_123",
feedback: Feedback.POSITIVE,
feedbackReason: "Very helpful",
feedback_reason: "Very helpful",
});
const call = findFetchCall(mock, "/v1/feedback/", "POST");
@@ -148,7 +161,11 @@ describe("MemoryClient - feedback()", () => {
describe("MemoryClient - Memory Exports", () => {
test("createMemoryExport throws when missing filters or schema", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await expect(
client.createMemoryExport({
filters: null as never,
@@ -165,7 +182,11 @@ describe("MemoryClient - Memory Exports", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.createMemoryExport({
schema: { fields: ["memory", "user_id"] },
filters: { user_id: "u1" },
@@ -174,11 +195,39 @@ describe("MemoryClient - Memory Exports", () => {
expect(findFetchCall(mock, "/v1/exports/", "POST")).toBeDefined();
});
test("createMemoryExport attaches org_id and project_id to body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/exports/", {
status: 200,
body: { message: "Created", id: "exp_1" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.createMemoryExport({
schema: { fields: ["memory"] },
filters: { user_id: "u1" },
});
const call = findFetchCall(mock, "/v1/exports/", "POST");
const body = getFetchBody(call!);
expect(body.org_id).toBe(TEST_ORG_ID);
expect(body.project_id).toBe(TEST_PROJECT_ID);
});
test("getMemoryExport throws when missing both id and filters", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
"Missing memoryExportId or filters",
"Missing memory_export_id or filters",
);
});
@@ -190,8 +239,12 @@ describe("MemoryClient - Memory Exports", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getMemoryExport({ memoryExportId: "exp_123" });
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.getMemoryExport({ memory_export_id: "exp_123" });
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
});
@@ -1,5 +1,5 @@
/**
* MemoryClient unit tests — search (v3 endpoint, filters).
* MemoryClient unit tests — search (v1/v2 routing, filters).
* Tests verify request construction, not mock response echo.
*/
import { MemoryClient } from "../mem0";
@@ -15,253 +15,89 @@ import {
installConsoleSuppression();
describe("MemoryClient - search()", () => {
test("sends POST to /v3/memories/search/ by default", async () => {
test("sends POST to /v1/memories/search/ by default", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/search/", {
status: 200,
body: { results: [] },
});
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", {
filters: { user_id: "u1" },
});
await client.search("What is my name?", { user_id: "u1" });
expect(findFetchCall(mock, "/v3/memories/search/", "POST")).toBeDefined();
expect(findFetchCall(mock, "/v1/memories/search/", "POST")).toBeDefined();
});
test("includes query in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/search/", {
status: 200,
body: { results: [] },
});
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", {
filters: { user_id: "u1" },
});
await client.search("What is my name?", { user_id: "u1" });
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
expect(getFetchBody(call!).query).toBe("What is my name?");
});
test("passes filters through to the API body", async () => {
test("includes user_id in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/search/", {
status: 200,
body: { results: [] },
});
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { filters: { user_id: "u1" } });
await client.search("test", { user_id: "u1" });
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
expect(getFetchBody(call!).user_id).toBe("u1");
});
test("passes complex OR filters through to the API body", async () => {
test("uses /v2/memories/search/ when api_version=v2", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/search/", {
status: 200,
body: { results: [] },
});
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { user_id: "u1", api_version: "v2" });
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
});
test("passes filters through to the v2 API body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("query", {
api_version: "v2",
filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] },
});
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
const body = getFetchBody(call!);
expect(body.filters).toEqual({
OR: [{ user_id: "u1" }, { agent_id: "a1" }],
});
});
test("passes complex AND filters through to the API body", 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("query", {
filters: {
AND: [
{ user_id: "u1" },
{ created_at: { gte: "2024-01-01T00:00:00Z" } },
],
},
});
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
const body = getFetchBody(call!);
expect(body.filters).toEqual({
AND: [{ user_id: "u1" }, { created_at: { gte: "2024-01-01T00:00:00Z" } }],
});
});
test("passes NOT filters through to the API body", 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("query", {
filters: {
AND: [
{ user_id: "u1" },
{ NOT: { categories: { in: ["spam", "test"] } } },
],
},
});
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
const body = getFetchBody(call!);
expect(body.filters).toEqual({
AND: [
{ user_id: "u1" },
{ NOT: { categories: { in: ["spam", "test"] } } },
],
});
});
test("passes complex nested AND/OR/NOT filters through to the API body", 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 });
const complexFilter = {
AND: [
{ user_id: "u1" },
{ created_at: { gte: "2024-01-01T00:00:00Z" } },
{
NOT: {
OR: [
{ categories: { in: ["spam"] } },
{ categories: { in: ["test"] } },
],
},
},
],
};
await client.search("query", { filters: complexFilter });
const call = findFetchCall(mock, "/v3/memories/search/", "POST");
const body = getFetchBody(call!);
expect(body.filters).toEqual(complexFilter);
});
test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/search/", {
status: 200,
body: { results: [] },
});
extra.set("/v1/memories/search/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result = await client.search("query");
expect(result).toHaveProperty("results");
const result: Memory[] = await client.search("query");
expect(Array.isArray(result)).toBe(true);
});
test("handles empty results array", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/search/", {
status: 200,
body: { results: [] },
});
extra.set("/v1/memories/search/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result = await client.search("nonexistent query", {
filters: { AND: [{ user_id: "u1" }] },
const result: Memory[] = await client.search("nonexistent query", {
user_id: "u1",
});
expect(result.results).toHaveLength(0);
});
});
describe("MemoryClient - search() entity param rejection", () => {
test("rejects user_id at top level", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(
client.search("query", { user_id: "u1" } as any),
).rejects.toThrow(/filters/);
});
test("rejects agent_id at top level", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(
client.search("query", { agent_id: "a1" } as any),
).rejects.toThrow(/filters/);
});
test("rejects app_id at top level", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(
client.search("query", { app_id: "app1" } as any),
).rejects.toThrow(/filters/);
});
test("accepts filters with user_id", 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 });
// Should not throw
await client.search("query", { filters: { AND: [{ user_id: "u1" }] } });
expect(findFetchCall(mock, "/v3/memories/search/", "POST")).toBeDefined();
});
});
describe("MemoryClient - getAll() entity param rejection", () => {
test("rejects user_id at top level", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.getAll({ user_id: "u1" } as any)).rejects.toThrow(
/filters/,
);
});
test("rejects agent_id at top level", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.getAll({ agent_id: "a1" } as any)).rejects.toThrow(
/filters/,
);
});
test("accepts filters with user_id", 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" } });
expect(findFetchCall(mock, "/v3/memories/", "POST")).toBeDefined();
expect(result).toHaveLength(0);
});
});
@@ -1,9 +1,15 @@
/**
* MemoryClient unit tests — users, deleteUser.
* MemoryClient unit tests — users, deleteUser, deleteUsers.
* Tests verify entity type routing and request construction.
*/
import { MemoryClient } from "../mem0";
import { createMockUser, createMockAllUsers, TEST_API_KEY } from "./helpers";
import {
createMockUser,
createMockAllUsers,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -34,6 +40,111 @@ describe("MemoryClient - users()", () => {
});
});
// ─── deleteUsers() ──────────────────────────────────────
describe("MemoryClient - deleteUsers()", () => {
function createClientWithMockedAxios() {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const axiosDeleteMock = jest
.fn()
.mockResolvedValue({ data: { message: "Deleted" } });
client.client.delete = axiosDeleteMock;
return { client, axiosDeleteMock };
}
test("routes user_id to DELETE /v2/entities/user/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ user_id: "u1" });
expect(axiosDeleteMock).toHaveBeenCalledWith("/v2/entities/user/u1/", {
params: expect.objectContaining({
org_id: TEST_ORG_ID,
project_id: TEST_PROJECT_ID,
}),
});
});
test("routes agent_id to DELETE /v2/entities/agent/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ agent_id: "agent_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/agent/agent_1/",
expect.any(Object),
);
});
test("routes app_id to DELETE /v2/entities/app/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ app_id: "app_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/app/app_1/",
expect.any(Object),
);
});
test("routes run_id to DELETE /v2/entities/run/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ run_id: "run_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/run/run_1/",
expect.any(Object),
);
});
test("returns 'Entity deleted successfully.' for single entity", async () => {
const { client } = createClientWithMockedAxios();
const result = await client.deleteUsers({ user_id: "u1" });
expect(result.message).toBe("Entity deleted successfully.");
});
test("returns 'All users, agents, apps and runs deleted.' when no params given", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/entities/", {
status: 200,
body: createMockAllUsers([createMockUser({ name: "u1", type: "user" })]),
});
setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
client.client.delete = jest
.fn()
.mockResolvedValue({ data: { message: "Deleted" } });
const result = await client.deleteUsers();
expect(result.message).toBe("All users, agents, apps and runs deleted.");
});
test("throws when no entities exist to delete", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/entities/", {
status: 200,
body: createMockAllUsers([]),
});
setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
client.client.delete = jest.fn();
await expect(client.deleteUsers()).rejects.toThrow("No entities to delete");
});
});
// ─── deleteUser() (deprecated) ──────────────────────────
describe("MemoryClient - deleteUser() (deprecated)", () => {
@@ -5,7 +5,7 @@
*/
import { MemoryClient } from "../mem0";
import { WebhookEvent } from "../mem0.types";
import { TEST_API_KEY } from "./helpers";
import { TEST_API_KEY, TEST_ORG_ID, TEST_PROJECT_ID } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -23,6 +23,8 @@ function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
function createClient() {
return new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
}
@@ -98,6 +100,14 @@ describe("MemoryClient - createWebhook", () => {
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callCreate();
const body = getFetchBody(
@@ -163,6 +173,22 @@ describe("MemoryClient - updateWebhook", () => {
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain project_id", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.project_id).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
-49
View File
@@ -1,49 +0,0 @@
/**
* Converts a camelCase string to snake_case.
*/
export function camelToSnake(str: string): string {
// Skip all-uppercase keys (e.g. OR, AND, NOT — logical operators)
if (str === str.toUpperCase()) return str;
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
/**
* Converts a snake_case string to camelCase.
*/
function snakeToCamel(str: string): string {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
/**
* Recursively converts all keys of an object from camelCase to snake_case.
* Used for converting user-facing camelCase params to API snake_case payloads.
*/
export function camelToSnakeKeys(obj: any): any {
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
if (Array.isArray(obj)) return obj.map(camelToSnakeKeys);
if (obj instanceof Date) return obj;
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
camelToSnake(key),
camelToSnakeKeys(value),
]),
);
}
/**
* Recursively converts all keys of an object from snake_case to camelCase.
* Used for converting API snake_case responses to user-facing camelCase.
*/
export function snakeToCamelKeys(obj: any): any {
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
if (Array.isArray(obj)) return obj.map(snakeToCamelKeys);
if (obj instanceof Date) return obj;
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
snakeToCamel(key),
snakeToCamelKeys(value),
]),
);
}
@@ -1,10 +1,5 @@
import { MemoryClient } from "mem0ai";
import type {
Memory,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
} from "mem0ai";
import type { Memory, MemoryOptions, SearchOptions } from "mem0ai";
import {
InputValues,
@@ -107,6 +102,10 @@ export const mem0MemoryToMessages = (memories: Memory[]): BaseMessage[] => {
export interface ClientOptions {
apiKey: string;
host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
}
/**
@@ -118,7 +117,7 @@ export interface Mem0MemoryInput extends BaseChatMemoryInput {
apiKey: string;
humanPrefix?: string;
aiPrefix?: string;
memoryOptions?: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
memoryOptions?: MemoryOptions | SearchOptions;
mem0Options?: ClientOptions;
separateMessages?: boolean;
}
@@ -161,7 +160,7 @@ export class Mem0Memory extends BaseChatMemory implements Mem0MemoryInput {
mem0Client: InstanceType<typeof MemoryClient>;
memoryOptions: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
memoryOptions: MemoryOptions | SearchOptions;
mem0Options: ClientOptions;
+95 -2
View File
@@ -82,14 +82,14 @@ async function runTests(memory: Memory) {
// Get all memories
console.log("\nGetting all memories...");
const allMemories = await memory.getAll({
filters: { user_id: "john" },
userId: "john",
});
console.log("All memories:", allMemories);
// Search for memories
console.log("\nSearching memories...");
const searchResult = await memory.search("What do you know about Paris?", {
filters: { user_id: "john" },
userId: "john",
});
console.log("Search results:", searchResult);
@@ -292,6 +292,86 @@ async function demoRedis() {
await runTests(memory);
}
async function demoGraphMemory() {
console.log("\n=== Testing Graph Memory Store ===\n");
const memory = new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "memory",
config: {
collectionName: "memories",
dimension: 1536,
},
},
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
},
},
graphStore: {
provider: "neo4j",
config: {
url: process.env.NEO4J_URL || "neo4j://localhost:7687",
username: process.env.NEO4J_USERNAME || "neo4j",
password: process.env.NEO4J_PASSWORD || "password",
},
llm: {
provider: "openai",
config: {
model: "gpt-4-turbo-preview",
},
},
},
historyDbPath: "memory.db",
});
try {
// Reset all memories
await memory.reset();
// Add memories with relationships
const result = await memory.add(
[
{
role: "user",
content: "Alice is Bob's sister and works as a doctor.",
},
{
role: "assistant",
content:
"I understand that Alice and Bob are siblings and Alice is a medical professional.",
},
{ role: "user", content: "Bob is married to Carol who is a teacher." },
],
{
userId: "john",
},
);
console.log("Added memories with relationships:", result);
// Search for connected information
const searchResult = await memory.search(
"Tell me about Bob's family connections",
{
userId: "john",
},
);
console.log("Search results with graph relationships:", searchResult);
} catch (error) {
console.error("Error in graph memory demo:", error);
}
}
async function main() {
// Test in-memory store
await demoMemoryStore();
@@ -299,6 +379,19 @@ async function main() {
// Test in-memory store with Ollama
await demoLocalMemory();
// Test graph memory if Neo4j environment variables are set
if (
process.env.NEO4J_URL &&
process.env.NEO4J_USERNAME &&
process.env.NEO4J_PASSWORD
) {
await demoGraphMemory();
} else {
console.log(
"\nSkipping Graph Memory test - Neo4j environment variables not set",
);
}
// Test PGVector store if environment variables are set
if (process.env.PGVECTOR_DB) {
await demoPGVector();
+1 -3
View File
@@ -26,9 +26,7 @@ const memory = new Memory({
});
async function chatWithMemories(message: string, userId = "default_user") {
const relevantMemories = await memory.search(message, {
filters: { user_id: userId },
});
const relevantMemories = await memory.search(message, { userId: userId });
const memoriesStr = relevantMemories.results
.map((entry) => `- ${entry.memory}`)
+2 -2
View File
@@ -64,14 +64,14 @@ export async function runTests(memory: Memory) {
// Get all memories
console.log("\nGetting all memories...");
const allMemories = await memory.getAll({
filters: { user_id: "john" },
userId: "john",
});
console.log("All memories:", allMemories);
// Search for memories
console.log("\nSearching memories...");
const searchResult = await memory.search("What do you know about Paris?", {
filters: { user_id: "john" },
userId: "john",
});
console.log("Search results:", searchResult);
+10 -1
View File
@@ -22,10 +22,19 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
config: {
baseURL: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-5-mini",
model: "gpt-4-turbo-preview",
modelProperties: undefined,
},
},
enableGraph: false,
graphStore: {
provider: "neo4j",
config: {
url: process.env.NEO4J_URL || "neo4j://localhost:7687",
username: process.env.NEO4J_USERNAME || "neo4j",
password: process.env.NEO4J_PASSWORD || "password",
},
},
historyStore: {
provider: "sqlite",
config: {
+6 -2
View File
@@ -110,7 +110,6 @@ export class ConfigManager {
((userConf as Record<string, unknown>)?.lmstudio_base_url as
| string
| undefined) ??
userConf?.url ??
defaultConf.baseURL;
return {
@@ -132,7 +131,11 @@ export class ConfigManager {
userConfig.historyDbPath ||
userConfig.historyStore?.config?.historyDbPath ||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
customInstructions: userConfig.customInstructions,
customPrompt: userConfig.customPrompt,
graphStore: {
...DEFAULT_MEMORY_CONFIG.graphStore,
...userConfig.graphStore,
},
historyStore: (() => {
const defaultHistoryStore = DEFAULT_MEMORY_CONFIG.historyStore!;
const historyProvider =
@@ -155,6 +158,7 @@ export class ConfigManager {
})(),
disableHistory:
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
};
// Validate the merged config
+8 -18
View File
@@ -35,23 +35,13 @@ export class AzureOpenAIEmbedder implements Embedder {
}
async embedBatch(texts: string[]): Promise<number[][]> {
const MAX_BATCH = 100;
const allEmbeddings: number[][] = [];
for (let i = 0; i < texts.length; i += MAX_BATCH) {
const chunk = texts.slice(i, i + MAX_BATCH);
const response = await this.client.embeddings.create({
model: this.model,
input: chunk,
...(this.embeddingDims !== undefined && {
dimensions: this.embeddingDims,
}),
});
allEmbeddings.push(
...response.data
.sort((a, b) => a.index - b.index)
.map((item) => item.embedding),
);
}
return allEmbeddings;
const response = await this.client.embeddings.create({
model: this.model,
input: texts,
...(this.embeddingDims !== undefined && {
dimensions: this.embeddingDims,
}),
});
return response.data.map((item) => item.embedding);
}
}
+8 -18
View File
@@ -28,23 +28,13 @@ export class OpenAIEmbedder implements Embedder {
}
async embedBatch(texts: string[]): Promise<number[][]> {
const MAX_BATCH = 100;
const allEmbeddings: number[][] = [];
for (let i = 0; i < texts.length; i += MAX_BATCH) {
const chunk = texts.slice(i, i + MAX_BATCH);
const response = await this.openai.embeddings.create({
model: this.model,
input: chunk,
...(this.embeddingDims !== undefined && {
dimensions: this.embeddingDims,
}),
});
allEmbeddings.push(
...response.data
.sort((a, b) => a.index - b.index)
.map((item) => item.embedding),
);
}
return allEmbeddings;
const response = await this.openai.embeddings.create({
model: this.model,
input: texts,
...(this.embeddingDims !== undefined && {
dimensions: this.embeddingDims,
}),
});
return response.data.map((item) => item.embedding);
}
}
+30
View File
@@ -0,0 +1,30 @@
import { LLMConfig } from "../types";
export interface Neo4jConfig {
url: string | null;
username: string | null;
password: string | null;
}
export interface GraphStoreConfig {
provider: string;
config: Neo4jConfig;
llm?: LLMConfig;
customPrompt?: string;
}
export function validateNeo4jConfig(config: Neo4jConfig): void {
const { url, username, password } = config;
if (!url || !username || !password) {
throw new Error("Please provide 'url', 'username' and 'password'.");
}
}
export function validateGraphStoreConfig(config: GraphStoreConfig): void {
const { provider } = config;
if (provider === "neo4j") {
validateNeo4jConfig(config.config);
} else {
throw new Error(`Unsupported graph store provider: ${provider}`);
}
}
+267
View File
@@ -0,0 +1,267 @@
import { z } from "zod";
export interface GraphToolParameters {
source: string;
destination: string;
relationship: string;
source_type?: string;
destination_type?: string;
}
export interface GraphEntitiesParameters {
entities: Array<{
entity: string;
entity_type: string;
}>;
}
export interface GraphRelationsParameters {
entities: Array<{
source: string;
relationship: string;
destination: string;
}>;
}
// --- Zod Schemas for Tool Arguments ---
// Schema for simple relationship arguments (Update, Delete)
export const GraphSimpleRelationshipArgsSchema = z.object({
source: z
.string()
.describe("The identifier of the source node in the relationship."),
relationship: z
.string()
.describe("The relationship between the source and destination nodes."),
destination: z
.string()
.describe("The identifier of the destination node in the relationship."),
});
// Schema for adding a relationship (includes types)
export const GraphAddRelationshipArgsSchema =
GraphSimpleRelationshipArgsSchema.extend({
source_type: z
.string()
.describe("The type or category of the source node."),
destination_type: z
.string()
.describe("The type or category of the destination node."),
});
// Schema for extracting entities
export const GraphExtractEntitiesArgsSchema = z.object({
entities: z
.array(
z.object({
entity: z.string().describe("The name or identifier of the entity."),
entity_type: z.string().describe("The type or category of the entity."),
}),
)
.describe("An array of entities with their types."),
});
// Schema for establishing relationships
export const GraphRelationsArgsSchema = z.object({
entities: z
.array(GraphSimpleRelationshipArgsSchema)
.describe("An array of relationships (source, relationship, destination)."),
});
// --- Tool Definitions (using JSON schema, keep as is) ---
// Note: The tool definitions themselves still use JSON schema format
// as expected by the LLM APIs. The Zod schemas above are for internal
// validation and potentially for use with Langchain's .withStructuredOutput
// if we adapt it to handle tool calls via schema.
export const UPDATE_MEMORY_TOOL_GRAPH = {
type: "function",
function: {
name: "update_graph_memory",
description:
"Update the relationship key of an existing graph memory based on new information.",
parameters: {
type: "object",
properties: {
source: {
type: "string",
description:
"The identifier of the source node in the relationship to be updated.",
},
destination: {
type: "string",
description:
"The identifier of the destination node in the relationship to be updated.",
},
relationship: {
type: "string",
description:
"The new or updated relationship between the source and destination nodes.",
},
},
required: ["source", "destination", "relationship"],
additionalProperties: false,
},
},
};
export const ADD_MEMORY_TOOL_GRAPH = {
type: "function",
function: {
name: "add_graph_memory",
description: "Add a new graph memory to the knowledge graph.",
parameters: {
type: "object",
properties: {
source: {
type: "string",
description:
"The identifier of the source node in the new relationship.",
},
destination: {
type: "string",
description:
"The identifier of the destination node in the new relationship.",
},
relationship: {
type: "string",
description:
"The type of relationship between the source and destination nodes.",
},
source_type: {
type: "string",
description: "The type or category of the source node.",
},
destination_type: {
type: "string",
description: "The type or category of the destination node.",
},
},
required: [
"source",
"destination",
"relationship",
"source_type",
"destination_type",
],
additionalProperties: false,
},
},
};
export const NOOP_TOOL = {
type: "function",
function: {
name: "noop",
description: "No operation should be performed to the graph entities.",
parameters: {
type: "object",
properties: {},
required: [],
additionalProperties: false,
},
},
};
export const RELATIONS_TOOL = {
type: "function",
function: {
name: "establish_relationships",
description:
"Establish relationships among the entities based on the provided text.",
parameters: {
type: "object",
properties: {
entities: {
type: "array",
items: {
type: "object",
properties: {
source: {
type: "string",
description: "The source entity of the relationship.",
},
relationship: {
type: "string",
description:
"The relationship between the source and destination entities.",
},
destination: {
type: "string",
description: "The destination entity of the relationship.",
},
},
required: ["source", "relationship", "destination"],
additionalProperties: false,
},
},
},
required: ["entities"],
additionalProperties: false,
},
},
};
export const EXTRACT_ENTITIES_TOOL = {
type: "function",
function: {
name: "extract_entities",
description: "Extract entities and their types from the text.",
parameters: {
type: "object",
properties: {
entities: {
type: "array",
items: {
type: "object",
properties: {
entity: {
type: "string",
description: "The name or identifier of the entity.",
},
entity_type: {
type: "string",
description: "The type or category of the entity.",
},
},
required: ["entity", "entity_type"],
additionalProperties: false,
},
description: "An array of entities with their types.",
},
},
required: ["entities"],
additionalProperties: false,
},
},
};
export const DELETE_MEMORY_TOOL_GRAPH = {
type: "function",
function: {
name: "delete_graph_memory",
description: "Delete the relationship between two nodes.",
parameters: {
type: "object",
properties: {
source: {
type: "string",
description: "The identifier of the source node in the relationship.",
},
relationship: {
type: "string",
description:
"The existing relationship between the source and destination nodes that needs to be deleted.",
},
destination: {
type: "string",
description:
"The identifier of the destination node in the relationship.",
},
},
required: ["source", "relationship", "destination"],
additionalProperties: false,
},
},
};
+116
View File
@@ -0,0 +1,116 @@
export const UPDATE_GRAPH_PROMPT = `
You are an AI expert specializing in graph memory management and optimization. Your task is to analyze existing graph memories alongside new information, and update the relationships in the memory list to ensure the most accurate, current, and coherent representation of knowledge.
Input:
1. Existing Graph Memories: A list of current graph memories, each containing source, target, and relationship information.
2. New Graph Memory: Fresh information to be integrated into the existing graph structure.
Guidelines:
1. Identification: Use the source and target as primary identifiers when matching existing memories with new information.
2. Conflict Resolution:
- If new information contradicts an existing memory:
a) For matching source and target but differing content, update the relationship of the existing memory.
b) If the new memory provides more recent or accurate information, update the existing memory accordingly.
3. Comprehensive Review: Thoroughly examine each existing graph memory against the new information, updating relationships as necessary. Multiple updates may be required.
4. Consistency: Maintain a uniform and clear style across all memories. Each entry should be concise yet comprehensive.
5. Semantic Coherence: Ensure that updates maintain or improve the overall semantic structure of the graph.
6. Temporal Awareness: If timestamps are available, consider the recency of information when making updates.
7. Relationship Refinement: Look for opportunities to refine relationship descriptions for greater precision or clarity.
8. Redundancy Elimination: Identify and merge any redundant or highly similar relationships that may result from the update.
Memory Format:
source -- RELATIONSHIP -- destination
Task Details:
======= Existing Graph Memories:=======
{existing_memories}
======= New Graph Memory:=======
{new_memories}
Output:
Provide a list of update instructions, each specifying the source, target, and the new relationship to be set. Only include memories that require updates.
`;
export const EXTRACT_RELATIONS_PROMPT = `
You are an advanced algorithm designed to extract structured information from text to construct knowledge graphs. Your goal is to capture comprehensive and accurate information. Follow these key principles:
1. Extract only explicitly stated information from the text.
2. Establish relationships among the entities provided.
3. Use "USER_ID" as the source entity for any self-references (e.g., "I," "me," "my," etc.) in user messages.
CUSTOM_PROMPT
Relationships:
- Use consistent, general, and timeless relationship types.
- Example: Prefer "professor" over "became_professor."
- Relationships should only be established among the entities explicitly mentioned in the user message.
Entity Consistency:
- Ensure that relationships are coherent and logically align with the context of the message.
- Maintain consistent naming for entities across the extracted data.
Strive to construct a coherent and easily understandable knowledge graph by eshtablishing all the relationships among the entities and adherence to the user's context.
Adhere strictly to these guidelines to ensure high-quality knowledge graph extraction.
`;
export const DELETE_RELATIONS_SYSTEM_PROMPT = `
You are a graph memory manager specializing in identifying, managing, and optimizing relationships within graph-based memories. Your primary task is to analyze a list of existing relationships and determine which ones should be deleted based on the new information provided.
Input:
1. Existing Graph Memories: A list of current graph memories, each containing source, relationship, and destination information.
2. New Text: The new information to be integrated into the existing graph structure.
3. Use "USER_ID" as node for any self-references (e.g., "I," "me," "my," etc.) in user messages.
Guidelines:
1. Identification: Use the new information to evaluate existing relationships in the memory graph.
2. Deletion Criteria: Delete a relationship only if it meets at least one of these conditions:
- Outdated or Inaccurate: The new information is more recent or accurate.
- Contradictory: The new information conflicts with or negates the existing information.
3. DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
4. Comprehensive Analysis:
- Thoroughly examine each existing relationship against the new information and delete as necessary.
- Multiple deletions may be required based on the new information.
5. Semantic Integrity:
- Ensure that deletions maintain or improve the overall semantic structure of the graph.
- Avoid deleting relationships that are NOT contradictory/outdated to the new information.
6. Temporal Awareness: Prioritize recency when timestamps are available.
7. Necessity Principle: Only DELETE relationships that must be deleted and are contradictory/outdated to the new information to maintain an accurate and coherent memory graph.
Note: DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
For example:
Existing Memory: alice -- loves_to_eat -- pizza
New Information: Alice also loves to eat burger.
Do not delete in the above example because there is a possibility that Alice loves to eat both pizza and burger.
Memory Format:
source -- relationship -- destination
Provide a list of deletion instructions, each specifying the relationship to be deleted.
Respond in JSON format.
`;
export function getDeleteMessages(
existingMemoriesString: string,
data: string,
userId: string,
): [string, string] {
return [
DELETE_RELATIONS_SYSTEM_PROMPT.replace("USER_ID", userId),
`Here are the existing memories: ${existingMemoriesString} \n\n New Information: ${data}`,
];
}
export function formatEntities(
entities: Array<{
source: string;
relationship: string;
destination: string;
}>,
): string {
return entities
.map((e) => `${e.source} -- ${e.relationship} -- ${e.destination}`)
.join("\n");
}
+1 -1
View File
@@ -18,7 +18,7 @@ export class AzureOpenAILLM implements LLM {
endpoint: endpoint as string,
...rest,
});
this.model = config.model || "gpt-5-mini";
this.model = config.model || "gpt-4";
}
async generateResponse(
+70 -14
View File
@@ -10,6 +10,12 @@ import { LLM, LLMResponse } from "./base";
import { LLMConfig, Message } from "../types/index";
// Import the schemas directly into LangchainLLM
import { FactRetrievalSchema, MemoryUpdateSchema } from "../prompts";
// Import graph tool argument schemas
import {
GraphExtractEntitiesArgsSchema,
GraphRelationsArgsSchema,
GraphSimpleRelationshipArgsSchema, // Used for delete tool
} from "../graphs/tools";
const convertToLangchainMessages = (messages: Message[]): BaseMessage[] => {
return messages.map((msg) => {
@@ -67,14 +73,28 @@ export class LangchainLLM implements LLM {
const invokeOptions: Record<string, any> = {};
let isStructuredOutput = false;
let selectedSchema: z.ZodSchema<any> | null = null;
let isToolCallResponse = false;
// --- Internal Schema Selection Logic (runs regardless of response_format) ---
const systemPromptContent =
(messages.find((m) => m.role === "system")?.content as string) || "";
const userPromptContent =
(messages.find((m) => m.role === "user")?.content as string) || "";
// Check for memory prompts
if (
const toolNames = tools?.map((t) => t.function.name) || [];
// Prioritize tool call argument schemas
if (toolNames.includes("extract_entities")) {
selectedSchema = GraphExtractEntitiesArgsSchema;
isToolCallResponse = true;
} else if (toolNames.includes("establish_relationships")) {
selectedSchema = GraphRelationsArgsSchema;
isToolCallResponse = true;
} else if (toolNames.includes("delete_graph_memory")) {
selectedSchema = GraphSimpleRelationshipArgsSchema;
isToolCallResponse = true;
}
// Check for memory prompts if no tool schema matched
else if (
systemPromptContent.includes("Personal Information Organizer") &&
systemPromptContent.includes("extract relevant pieces of information")
) {
@@ -91,18 +111,26 @@ export class LangchainLLM implements LLM {
selectedSchema &&
typeof (this.llmInstance as any).withStructuredOutput === "function"
) {
try {
runnable = (this.llmInstance as any).withStructuredOutput(
selectedSchema,
{ name: tools?.[0]?.function.name },
);
isStructuredOutput = true;
} catch (e) {
isStructuredOutput = false; // Ensure flag is false on error
// No fallback to response_format here unless explicitly passed
if (response_format?.type === "json_object") {
invokeOptions.response_format = { type: "json_object" };
// Apply if a schema was selected (for memory or single tool calls)
if (
!isToolCallResponse ||
(isToolCallResponse && tools && tools.length === 1)
) {
try {
runnable = (this.llmInstance as any).withStructuredOutput(
selectedSchema,
{ name: tools?.[0]?.function.name },
);
isStructuredOutput = true;
} catch (e) {
isStructuredOutput = false; // Ensure flag is false on error
// No fallback to response_format here unless explicitly passed
if (response_format?.type === "json_object") {
invokeOptions.response_format = { type: "json_object" };
}
}
} else if (isToolCallResponse) {
// If multiple tools, don't apply structured output, handle via tool binding below
}
} else if (selectedSchema && response_format?.type === "json_object") {
// Schema selected, but no .withStructuredOutput. Try basic response_format only if explicitly requested.
@@ -136,9 +164,37 @@ export class LangchainLLM implements LLM {
try {
const response = await runnable.invoke(langchainMessages, invokeOptions);
if (isStructuredOutput) {
if (isStructuredOutput && !isToolCallResponse) {
// Memory prompt with structured output
return JSON.stringify(response);
} else if (isStructuredOutput && isToolCallResponse) {
// Tool call with structured arguments
if (response?.tool_calls && Array.isArray(response.tool_calls)) {
const mappedToolCalls = response.tool_calls.map((call: any) => ({
name: call.name || tools?.[0]?.function.name || "unknown_tool",
arguments:
typeof call.args === "string"
? call.args
: JSON.stringify(call.args),
}));
return {
content: response.content || "",
role: "assistant",
toolCalls: mappedToolCalls,
};
} else {
// Direct object response for tool args
return {
content: "",
role: "assistant",
toolCalls: [
{
name: tools?.[0]?.function.name || "unknown_tool",
arguments: JSON.stringify(response),
},
],
};
}
} else if (
response &&
response.tool_calls &&

Some files were not shown because too many files have changed in this diff Show More