diff --git a/docs/api-reference/memory/add-memories.mdx b/docs/api-reference/memory/add-memories.mdx
index a44c7d384..146f1ea07 100644
--- a/docs/api-reference/memory/add-memories.mdx
+++ b/docs/api-reference/memory/add-memories.mdx
@@ -47,7 +47,6 @@ 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. |
-| `enable_graph` | boolean (default `false`) | Optional | Set to `true` to create entity nodes and relationships for knowledge graph. |
> \* 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.
@@ -82,20 +81,3 @@ Successful requests return an array of events queued for processing. Each event
```
-## 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.
-
-
-```json Graph-aware request
-{
- "user_id": "alice",
- "messages": [
- { "role": "user", "content": "I met with Dr. Lee at General Hospital." }
- ],
- "enable_graph": true
-}
-```
-
-
-The response follows the same format, and related entities become available in [Graph Memory](/platform/features/graph-memory) queries.
diff --git a/docs/api-reference/organizations-projects.mdx b/docs/api-reference/organizations-projects.mdx
index 22bd9350c..cbdf1f162 100644
--- a/docs/api-reference/organizations-projects.mdx
+++ b/docs/api-reference/organizations-projects.mdx
@@ -95,9 +95,6 @@ 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)
@@ -108,7 +105,6 @@ client.project.update(
{"personal_info": "User personal information and preferences"},
{"work_context": "Professional context and work-related information"}
],
- enable_graph=True,
multilingual=True
)
```
@@ -173,7 +169,7 @@ async def manage_project():
# All methods support async/await
project_info = await client.project.get()
- await client.project.update(enable_graph=True)
+ await client.project.update(multilingual=True)
members = await client.project.get_members()
# To call the async function properly
diff --git a/docs/cookbooks/integrations/aws-bedrock.mdx b/docs/cookbooks/integrations/aws-bedrock.mdx
index e43ea9599..bf6a46b08 100644
--- a/docs/cookbooks/integrations/aws-bedrock.mdx
+++ b/docs/cookbooks/integrations/aws-bedrock.mdx
@@ -137,7 +137,7 @@ With Mem0 and AWS services like Bedrock, OpenSearch, and Neptune Analytics, you
Explore graph-based memory storage with AWS Neptune Analytics.
-
+
Learn how to leverage knowledge graphs for entity relationships.
diff --git a/docs/core-concepts/memory-operations/add.mdx b/docs/core-concepts/memory-operations/add.mdx
index 28aa3fe59..45adc300d 100644
--- a/docs/core-concepts/memory-operations/add.mdx
+++ b/docs/core-concepts/memory-operations/add.mdx
@@ -172,7 +172,6 @@ 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 |
diff --git a/docs/core-concepts/memory-operations/delete.mdx b/docs/core-concepts/memory-operations/delete.mdx
index 3b07b0ca9..bb9801eeb 100644
--- a/docs/core-concepts/memory-operations/delete.mdx
+++ b/docs/core-concepts/memory-operations/delete.mdx
@@ -120,7 +120,7 @@ import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: "your-api-key" });
-client.deleteAll({ user_id: "alice" })
+client.deleteAll({ userId: "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({ user_id: "*" })
+client.deleteAll({ userId: "*" })
.then(result => console.log(result))
.catch(error => console.error(error));
// Full project wipe — all four filters must be explicitly set to "*"
-client.deleteAll({ user_id: "*", agent_id: "*", app_id: "*", run_id: "*" })
+client.deleteAll({ userId: "*", agentId: "*", appId: "*", runId: "*" })
.then(result => console.log(result))
.catch(error => console.error(error));
```
diff --git a/docs/docs.json b/docs/docs.json
index 18bb24613..fcf12d777 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -78,7 +78,6 @@
"group": "Advanced Features",
"icon": "bolt",
"pages": [
- "platform/features/graph-memory",
"platform/features/graph-threshold",
"platform/features/advanced-retrieval",
"platform/advanced-memory-operations",
@@ -633,6 +632,10 @@
"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"
@@ -987,7 +990,7 @@
},
{
"source": "/features/graph-memory",
- "destination": "/platform/features/graph-memory"
+ "destination": "/open-source/features/graph-memory"
},
{
"source": "/features/:slug",
diff --git a/docs/open-source/features/graph-memory.mdx b/docs/open-source/features/graph-memory.mdx
index a9ef43768..ed5a74f99 100644
--- a/docs/open-source/features/graph-memory.mdx
+++ b/docs/open-source/features/graph-memory.mdx
@@ -123,7 +123,6 @@ export NEO4J_PASSWORD="your-password"
import { Memory } from "mem0ai/oss";
const config = {
- enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
@@ -196,7 +195,6 @@ memory = Memory.from_config(config_dict=config)
import { Memory } from "mem0ai/oss";
const config = {
- enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
@@ -217,14 +215,6 @@ const memory = new Memory(config);
```python
config["graph_store"]["config"]["threshold"] = 0.75
-```
-
-
- 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)
```
@@ -257,9 +247,6 @@ Monitor graph growth, especially on free tiers, by periodically cleaning dormant
Ensure the graph identifier matches the vector dimension used by your embedder and that the IAM role allows `neptune-graph:*DataViaQuery` actions.
-
- Catch the provider error and retry with `enable_graph=False` so vector-only search keeps serving responses while the graph backend recovers.
-
## Decision Points
@@ -280,7 +267,6 @@ 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: {
diff --git a/docs/open-source/features/multimodal-support.mdx b/docs/open-source/features/multimodal-support.mdx
index 776e6a53d..a01cec4aa 100644
--- a/docs/open-source/features/multimodal-support.mdx
+++ b/docs/open-source/features/multimodal-support.mdx
@@ -81,7 +81,7 @@ const messages = [
}
];
-await client.add(messages, { user_id: "alice" });
+await client.add(messages, { userId: "alice" });
```
@@ -148,7 +148,7 @@ const messages = [
}
];
-await client.add(messages, { user_id: "alice" });
+await client.add(messages, { userId: "alice" });
```
@@ -264,7 +264,7 @@ try {
}
}];
- await client.add(messages, { user_id: "user123" });
+ await client.add(messages, { userId: "user123" });
console.log("Image processed successfully");
} catch (error: any) {
if (error.type === "invalid_image") {
diff --git a/docs/open-source/node-quickstart.mdx b/docs/open-source/node-quickstart.mdx
index e42fcf20b..2e3aa2740 100644
--- a/docs/open-source/node-quickstart.mdx
+++ b/docs/open-source/node-quickstart.mdx
@@ -209,14 +209,6 @@ 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 |
-
-
-| 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` |
| Parameter | Description | Default |
diff --git a/docs/platform/advanced-memory-operations.mdx b/docs/platform/advanced-memory-operations.mdx
index c8c3a14b6..36c47a78c 100644
--- a/docs/platform/advanced-memory-operations.mdx
+++ b/docs/platform/advanced-memory-operations.mdx
@@ -81,7 +81,6 @@ 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",
)
```
@@ -101,7 +100,6 @@ 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",
});
```
@@ -163,10 +161,6 @@ await memory.update(matches.results[0].id, {
-
- 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.
-
-
## Clean up
@@ -192,8 +186,6 @@ 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).
diff --git a/docs/platform/features/async-client.mdx b/docs/platform/features/async-client.mdx
index 51c73a333..e08283598 100644
--- a/docs/platform/features/async-client.mdx
+++ b/docs/platform/features/async-client.mdx
@@ -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, { user_id: "alice" });
+await client.add(messages, { userId: "alice" });
```
@@ -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?", { user_id: "alice" });
+await client.search("What is Alice's favorite sport?", { userId: "alice" });
```
@@ -118,7 +118,7 @@ await client.delete_all(user_id="alice")
```
```javascript JavaScript
-await client.deleteAll({ user_id: "alice" });
+await client.deleteAll({ userId: "alice" });
```
diff --git a/docs/platform/features/contextual-add.mdx b/docs/platform/features/contextual-add.mdx
index ce3d9dbce..1344e1ca3 100644
--- a/docs/platform/features/contextual-add.mdx
+++ b/docs/platform/features/contextual-add.mdx
@@ -25,7 +25,7 @@ const messages = [
{"role": "assistant", "content": "Great! I'll remember your preference for Italian cuisine."}
];
-await client.add(messages, { user_id: "user123", version: "v2" });
+await client.add(messages, { userId: "user123", version: "v2" });
```
@@ -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, { user_id: "sarah", version: "v2" });
+await client.add(messages1, { userId: "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, { user_id: "sarah", version: "v2" });
+await client.add(messages2, { userId: "sarah", version: "v2" });
// Mem0 automatically knows Sarah is from New York and can use this context
```
@@ -104,7 +104,7 @@ const messages = [
{"role": "assistant", "content": "I've noted your allergies for future reference."}
];
-await client.add(messages, { user_id: "user123", version: "v2" });
+await client.add(messages, { userId: "user123", version: "v2" });
// This allergy info will be available in ALL future interactions
```
@@ -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, { user_id: "user123", run_id: "tokyo-trip-2024", version: "v2" });
+await client.add(messages1, { userId: "user123", runId: "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, { user_id: "user123", run_id: "tokyo-trip-2024", version: "v2" });
+await client.add(messages2, { userId: "user123", runId: "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, { user_id: "user123", run_id: "q4-marketing", version: "v2" });
+await client.add(workMessages, { userId: "user123", runId: "q4-marketing", version: "v2" });
```
diff --git a/docs/platform/features/custom-instructions.mdx b/docs/platform/features/custom-instructions.mdx
index 6d6bae3c5..f406f6206 100644
--- a/docs/platform/features/custom-instructions.mdx
+++ b/docs/platform/features/custom-instructions.mdx
@@ -33,7 +33,7 @@ Extract only health and wellness information:
Exclude: Personal identifiers, financial data
`;
-await client.project.update({ custom_instructions: prompt });
+await client.project.update({ customInstructions: prompt });
```
@@ -60,11 +60,11 @@ print(response["custom_instructions"])
```javascript JavaScript
// Set instructions for your project
-await client.project.update({ custom_instructions: "Your guidelines here..." });
+await client.project.update({ customInstructions: "Your guidelines here..." });
// Retrieve current instructions
-const response = await client.project.get({ fields: ["custom_instructions"] });
-console.log(response.custom_instructions);
+const response = await client.project.get({ fields: ["customInstructions"] });
+console.log(response.customInstructions);
```
@@ -145,7 +145,7 @@ Extract customer service information for better support:
Exclude: Payment card numbers, passwords, personal identifiers.
`;
-await client.project.update({ custom_instructions: instructions });
+await client.project.update({ customInstructions: instructions });
```
@@ -198,7 +198,7 @@ Extract learning-related information for personalized education:
Exclude: Specific grades, personal identifiers, financial information.
`;
-await client.project.update({ custom_instructions: educationPrompt });
+await client.project.update({ customInstructions: educationPrompt });
```
@@ -251,7 +251,7 @@ Extract financial planning information for advisory services:
Exclude: Account numbers, SSNs, passwords, specific financial amounts.
`;
-await client.project.update({ custom_instructions: financePrompt });
+await client.project.update({ customInstructions: financePrompt });
```
diff --git a/docs/platform/features/graph-memory.mdx b/docs/platform/features/graph-memory.mdx
deleted file mode 100644
index 8bb947667..000000000
--- a/docs/platform/features/graph-memory.mdx
+++ /dev/null
@@ -1,333 +0,0 @@
----
-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:
-
-
-
-```python Python
-from mem0 import MemoryClient
-
-client = MemoryClient(api_key="your-api-key")
-
-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" });
-
-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"
- }
- ]
-}
-```
-
-
-The graph memory would look like this:
-
-
-
-
-
-Graph Memory creates a network of relationships between entities, enabling more contextual retrieval
-
-
-
-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.
-
-
-
-### Searching with Graph Memory
-
-When searching memories, Graph Memory helps retrieve entities that are contextually important even if they're not direct semantic matches.
-
-
-
-```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
- }
- ]
-}
-```
-
-
-
-
-`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.
-
-
-### Retrieving All Memories with Graph Memory
-
-When retrieving all memories, Graph Memory provides additional relationship context:
-
-
-`get_all()` now requires filters to be specified.
-
-
-
-
-```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"
- }
- ]
-}
-```
-
-
-
-### 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:
-
-
-
-```python Python
-from mem0 import MemoryClient
-
-client = MemoryClient(api_key="your-api-key")
-
-# 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" });
-
-// 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"
-});
-```
-
-
-
-
-## 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:
-
-
diff --git a/docs/platform/features/graph-threshold.mdx b/docs/platform/features/graph-threshold.mdx
index 8bd841e66..5f9d5b546 100644
--- a/docs/platform/features/graph-threshold.mdx
+++ b/docs/platform/features/graph-threshold.mdx
@@ -206,5 +206,5 @@ config = {"graph_store": {"threshold": 0.7}} # Valid: 0.0 ≤ x ≤ 1.0
## Related
-- [Graph Memory](/platform/features/graph-memory)
+- [Graph Memory](/open-source/features/graph-memory)
- [Issue #3590](https://github.com/mem0ai/mem0/issues/3590)
diff --git a/docs/platform/features/memory-export.mdx b/docs/platform/features/memory-export.mdx
index 3d08a4cd5..a18773ea1 100644
--- a/docs/platform/features/memory-export.mdx
+++ b/docs/platform/features/memory-export.mdx
@@ -134,7 +134,7 @@ const filters = {
const responseWithInstructions = await client.createMemoryExport({
schema: json_schema,
filters: filters,
- export_instructions: export_instructions
+ exportInstructions: export_instructions
});
console.log(responseWithInstructions);
@@ -176,10 +176,10 @@ print(response)
```javascript JavaScript
// Retrieve using export ID
-const memory_export_id = "550e8400-e29b-41d4-a716-446655440000";
+const memoryExportId = "550e8400-e29b-41d4-a716-446655440000";
const response = await client.getMemoryExport({
- memory_export_id: memory_export_id
+ memoryExportId: memoryExportId
});
console.log(response);
diff --git a/docs/platform/features/multimodal-support.mdx b/docs/platform/features/multimodal-support.mdx
index b1d419ba1..24a3b2f4b 100644
--- a/docs/platform/features/multimodal-support.mdx
+++ b/docs/platform/features/multimodal-support.mdx
@@ -67,7 +67,7 @@ const messages = [
},
]
-await client.add(messages, { user_id: "alice" })
+await client.add(messages, { userId: "alice" })
```
```json Output
@@ -166,7 +166,7 @@ const imageMessage = {
}
};
-await client.add([imageMessage], { user_id: "alice" })
+await client.add([imageMessage], { userId: "alice" })
```
diff --git a/docs/platform/features/platform-overview.mdx b/docs/platform/features/platform-overview.mdx
index 7376b7e20..8b947f9e8 100644
--- a/docs/platform/features/platform-overview.mdx
+++ b/docs/platform/features/platform-overview.mdx
@@ -20,7 +20,7 @@ Mem0 Platform features help managed deployments scale from basic filtering to gr
Non-blocking add/search requests for agents.
-
+
Relationship-aware recall across entities.
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, { user_id: "user1", timestamp: january2023Timestamp })
+client.add(messages, { userId: "user1", timestamp: january2023Timestamp })
.then(response => console.log(response))
.catch(error => console.error(error));
```
diff --git a/docs/platform/quickstart.mdx b/docs/platform/quickstart.mdx
index f5737cdc4..fe1b8aa8f 100644
--- a/docs/platform/quickstart.mdx
+++ b/docs/platform/quickstart.mdx
@@ -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, { user_id: "user123" });
+await client.add(messages, { userId: "user123" });
````
```bash cURL
diff --git a/mem0-ts/src/client/mem0.ts b/mem0-ts/src/client/mem0.ts
index 51c658611..0200282b0 100644
--- a/mem0-ts/src/client/mem0.ts
+++ b/mem0-ts/src/client/mem0.ts
@@ -20,6 +20,7 @@ import {
GetMemoryExportPayload,
} from "./mem0.types";
import { captureClientEvent, generateHash } from "./telemetry";
+import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils";
import { createExceptionFromResponse, MemoryError } from "../common/exceptions";
class APIError extends Error {
@@ -123,7 +124,7 @@ export default class MemoryClient {
throw createExceptionFromResponse(response.status, errorData);
}
const jsonResponse = await response.json();
- return jsonResponse;
+ return snakeToCamelKeys(jsonResponse);
}
_preparePayload(
@@ -132,7 +133,7 @@ export default class MemoryClient {
): object {
const payload: any = {};
payload.messages = messages;
- return { ...payload, ...options };
+ return camelToSnakeKeys({ ...payload, ...options });
}
_prepareParams(options: Record): object {
@@ -161,11 +162,11 @@ export default class MemoryClient {
throw new APIError(response.message || "API Key is invalid");
}
- const { org_id, project_id, user_email } = response;
+ const { orgId, projectId, userEmail } = response;
- if (org_id) this.organizationId = org_id;
- if (project_id) this.projectId = project_id;
- if (user_email) this.telemetryId = user_email;
+ if (orgId) this.organizationId = orgId;
+ if (projectId) this.projectId = projectId;
+ if (userEmail) this.telemetryId = userEmail;
} catch (error: any) {
// Pass through structured exceptions and APIError
if (error instanceof MemoryError || error instanceof APIError) {
@@ -256,15 +257,15 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]);
- const { page, page_size, ...rest } = options ?? {};
+ const { page, pageSize, ...rest } = options ?? {};
const body: Record = {
output_format: "v1.1",
- ...rest,
+ ...camelToSnakeKeys(rest),
};
let url = `${this.host}/v2/memories/`;
- if (page && page_size) {
- url += `?page=${page}&page_size=${page_size}`;
+ if (page && pageSize) {
+ url += `?page=${page}&page_size=${pageSize}`;
}
const response = await this._fetchWithErrorHandling(url, {
@@ -286,7 +287,7 @@ export default class MemoryClient {
const payload: Record = {
query,
output_format: "v1.1",
- ...(options ?? {}),
+ ...camelToSnakeKeys(options ?? {}),
};
const response = await this._fetchWithErrorHandling(
@@ -319,8 +320,9 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]);
+ const snakeOptions = camelToSnakeKeys(this._prepareParams(options));
// @ts-ignore
- const params = new URLSearchParams(this._prepareParams(options));
+ const params = new URLSearchParams(snakeOptions);
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/?${params}`,
{
@@ -345,14 +347,14 @@ export default class MemoryClient {
async users(options?: {
page?: number;
- page_size?: number;
+ pageSize?: number;
}): Promise {
if (this.telemetryId === "") await this.ping();
this._captureEvent("users", []);
let url = `${this.host}/v1/entities/`;
const params: string[] = [];
if (options?.page) params.push(`page=${options.page}`);
- if (options?.page_size) params.push(`page_size=${options.page_size}`);
+ if (options?.pageSize) params.push(`page_size=${options.pageSize}`);
if (params.length) url += `?${params.join("&")}`;
const response = await this._fetchWithErrorHandling(url, {
headers: this.headers,
@@ -384,25 +386,25 @@ export default class MemoryClient {
async deleteUsers(
params: {
- user_id?: string;
- agent_id?: string;
- app_id?: string;
- run_id?: string;
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
} = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
let to_delete: Array<{ type: string; name: string }> = [];
- const { user_id, agent_id, app_id, run_id } = params;
+ const { userId, agentId, appId, runId } = params;
- 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 }];
+ 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 }];
} else {
const entities = await this.users();
to_delete = entities.results.map((entity) => ({
@@ -426,12 +428,12 @@ export default class MemoryClient {
}
this._captureEvent("delete_users", [
- { user_id, agent_id, app_id, run_id, sync_type: "sync" },
+ { userId, agentId, appId, runId, sync_type: "sync" },
]);
return {
message:
- user_id || agent_id || app_id || run_id
+ userId || agentId || appId || runId
? "Entity deleted successfully."
: "All users, agents, apps and runs deleted.",
};
@@ -485,7 +487,7 @@ export default class MemoryClient {
}
const params = new URLSearchParams();
- fields?.forEach((field) => params.append("fields", field));
+ fields?.forEach((field) => params.append("fields", camelToSnake(field)));
const response = await this._fetchWithErrorHandling(
`${this.host}/api/v1/orgs/organizations/${this.organizationId}/projects/${this.projectId}/?${params.toString()}`,
@@ -512,7 +514,7 @@ export default class MemoryClient {
{
method: "PATCH",
headers: this.headers,
- body: JSON.stringify(prompts),
+ body: JSON.stringify(camelToSnakeKeys(prompts)),
},
);
return response;
@@ -596,7 +598,7 @@ export default class MemoryClient {
{
method: "POST",
headers: this.headers,
- body: JSON.stringify(data),
+ body: JSON.stringify(camelToSnakeKeys(data)),
},
);
return response;
@@ -617,7 +619,7 @@ export default class MemoryClient {
{
method: "POST",
headers: this.headers,
- body: JSON.stringify(data),
+ body: JSON.stringify(camelToSnakeKeys(data)),
},
);
@@ -630,8 +632,8 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
this._captureEvent("get_memory_export", []);
- if (!data.memory_export_id && !data.filters) {
- throw new Error("Missing memory_export_id or filters");
+ if (!data.memoryExportId && !data.filters) {
+ throw new Error("Missing memoryExportId or filters");
}
const response = await this._fetchWithErrorHandling(
@@ -639,7 +641,7 @@ export default class MemoryClient {
{
method: "POST",
headers: this.headers,
- body: JSON.stringify(data),
+ body: JSON.stringify(camelToSnakeKeys(data)),
},
);
return response;
diff --git a/mem0-ts/src/client/mem0.types.ts b/mem0-ts/src/client/mem0.types.ts
index 342bab674..af1f67a72 100644
--- a/mem0-ts/src/client/mem0.types.ts
+++ b/mem0-ts/src/client/mem0.types.ts
@@ -1,41 +1,38 @@
// ─── Entity Options (for add/delete — top-level identity) ───
export interface EntityOptions {
- user_id?: string;
- agent_id?: string;
- app_id?: string;
- run_id?: string;
+ userId?: string;
+ agentId?: string;
+ appId?: string;
+ runId?: string;
}
// ─── Per-Method Options ─────────────────────────────────────
export interface AddMemoryOptions extends EntityOptions {
metadata?: Record;
infer?: boolean;
- custom_categories?: custom_categories[];
- custom_instructions?: string;
+ customCategories?: custom_categories[];
+ customInstructions?: string;
timestamp?: number;
- structured_data_schema?: Record;
- enable_graph?: boolean;
+ structuredDataSchema?: Record;
}
export interface SearchMemoryOptions {
filters?: Record;
metadata?: Record;
- top_k?: number;
+ topK?: number;
threshold?: number;
rerank?: boolean;
fields?: string[];
categories?: string[];
- enable_graph?: boolean;
}
export interface GetAllMemoryOptions {
filters?: Record;
page?: number;
- page_size?: number;
- start_date?: string;
- end_date?: string;
+ pageSize?: number;
+ startDate?: string;
+ endDate?: string;
categories?: string[];
- enable_graph?: boolean;
}
export interface DeleteAllMemoryOptions extends EntityOptions {}
@@ -46,13 +43,12 @@ export interface ProjectOptions {
}
export interface PromptUpdatePayload {
- custom_instructions?: string;
- custom_categories?: custom_categories[];
- retrieval_criteria?: any[];
- enable_graph?: boolean;
+ customInstructions?: string;
+ customCategories?: custom_categories[];
+ retrievalCriteria?: any[];
version?: string;
- memory_depth?: string | null;
- usecase_setting?: string | number;
+ memoryDepth?: string | null;
+ usecaseSetting?: string | number;
multilingual?: boolean;
[key: string]: any;
}
@@ -79,7 +75,7 @@ export interface Messages {
export interface Message extends Messages {}
-// ─── Response Types (reflect API shapes, unchanged) ─────────
+// ─── Response Types (camelCase — converted from API snake_case) ─────
export interface MemoryData {
memory: string;
}
@@ -97,31 +93,31 @@ export interface Memory {
event?: Event | string;
data?: MemoryData | null;
memory?: string;
- user_id?: string;
+ userId?: string;
hash?: string;
categories?: Array;
- created_at?: Date;
- updated_at?: Date;
- memory_type?: string;
+ createdAt?: Date;
+ updatedAt?: Date;
+ memoryType?: string;
score?: number;
metadata?: any | null;
owner?: string | null;
- agent_id?: string | null;
- app_id?: string | null;
- run_id?: string | null;
+ agentId?: string | null;
+ appId?: string | null;
+ runId?: string | null;
}
export interface MemoryHistory {
id: string;
- memory_id: string;
+ memoryId: string;
input: Array;
- old_memory: string | null;
- new_memory: string | null;
- user_id: string;
+ oldMemory: string | null;
+ newMemory: string | null;
+ userId: string;
categories: Array;
event: Event | string;
- created_at: Date;
- updated_at: Date;
+ createdAt: Date;
+ updatedAt: Date;
}
export interface MemoryUpdateBody {
@@ -132,9 +128,9 @@ export interface MemoryUpdateBody {
export interface User {
id: string;
name: string;
- created_at: Date;
- updated_at: Date;
- total_memories: number;
+ createdAt: Date;
+ updatedAt: Date;
+ totalMemories: number;
owner: string;
type: string;
}
@@ -147,8 +143,8 @@ export interface AllUsers {
}
export interface ProjectResponse {
- custom_instructions?: string;
- custom_categories?: string[];
+ customInstructions?: string;
+ customCategories?: string[];
[key: string]: any;
}
@@ -165,14 +161,14 @@ export enum WebhookEvent {
}
export interface Webhook {
- webhook_id?: string;
+ webhookId?: string;
name: string;
url: string;
project?: string;
- created_at?: Date;
- updated_at?: Date;
- is_active?: boolean;
- event_types?: WebhookEvent[];
+ createdAt?: Date;
+ updatedAt?: Date;
+ isActive?: boolean;
+ eventTypes?: WebhookEvent[];
}
export interface WebhookCreatePayload {
@@ -190,18 +186,18 @@ export interface WebhookUpdatePayload {
// ─── Feedback & Export Types ────────────────────────────────
export interface FeedbackPayload {
- memory_id: string;
+ memoryId: string;
feedback?: Feedback | null;
- feedback_reason?: string | null;
+ feedbackReason?: string | null;
}
export interface CreateMemoryExportPayload {
schema: Record;
filters: Record;
- export_instructions?: string;
+ exportInstructions?: string;
}
export interface GetMemoryExportPayload {
filters?: Record;
- memory_export_id?: string;
+ memoryExportId?: string;
}
diff --git a/mem0-ts/src/client/tests/integration/crud.test.ts b/mem0-ts/src/client/tests/integration/crud.test.ts
index 2b6e2e771..134ba1c7e 100644
--- a/mem0-ts/src/client/tests/integration/crud.test.ts
+++ b/mem0-ts/src/client/tests/integration/crud.test.ts
@@ -51,7 +51,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
},
];
- const result = await client.add(messages, { user_id: TEST_USER_ID });
+ const result = await client.add(messages, { userId: TEST_USER_ID });
// API processes memories asynchronously — returns PENDING
expect(Array.isArray(result)).toBe(true);
@@ -60,7 +60,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
// Validate response shape
for (const item of result) {
expect(item).toHaveProperty("status");
- expect(item).toHaveProperty("event_id");
+ expect(item).toHaveProperty("eventId");
}
});
@@ -76,7 +76,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
},
];
- const result = await client.add(messages, { user_id: TEST_USER_ID });
+ const result = await client.add(messages, { userId: TEST_USER_ID });
expect(Array.isArray(result)).toBe(true);
});
@@ -103,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.user_id).toBe("string");
+ expect(typeof memory.userId).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.created_at || "").toString()).not.toBe(
+ expect(new Date(memory.createdAt || "").toString()).not.toBe(
"Invalid Date",
);
- expect(new Date(memory.updated_at || "").toString()).not.toBe(
+ expect(new Date(memory.updatedAt || "").toString()).not.toBe(
"Invalid Date",
);
});
@@ -123,7 +123,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
describe("get all memories", () => {
test("returns all memories for test user", async () => {
const memories = await client.getAll({
- filters: { user_id: TEST_USER_ID },
+ filters: { userId: TEST_USER_ID },
});
expect(Array.isArray(memories)).toBe(true);
@@ -137,9 +137,9 @@ 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 },
+ filters: { userId: TEST_USER_ID },
page: 1,
- page_size: 1,
+ pageSize: 1,
});
// Paginated response is an object with results array
@@ -191,7 +191,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
},
],
{
- user_id: TEST_USER_ID,
+ userId: TEST_USER_ID,
metadata: { source: "integration-test", category: "preferences" },
},
);
@@ -202,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("getAll for non-existent user returns empty array", async () => {
const memories = await client.getAll({
- filters: { user_id: `nonexistent-user-${randomUUID()}` },
+ filters: { userId: `nonexistent-user-${randomUUID()}` },
});
expect(Array.isArray(memories)).toBe(true);
@@ -211,7 +211,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("deleteAll for non-existent user does not throw", async () => {
const result = await client.deleteAll({
- user_id: `nonexistent-user-${randomUUID()}`,
+ userId: `nonexistent-user-${randomUUID()}`,
});
expect(result).toBeDefined();
@@ -241,13 +241,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({ user_id: TEST_USER_ID });
+ const result = await client.deleteAll({ userId: TEST_USER_ID });
expect(result).toBeDefined();
expect(typeof result.message).toBe("string");
});
test("deletes the test user entity", async () => {
- const result = await client.deleteUsers({ user_id: TEST_USER_ID });
+ const result = await client.deleteUsers({ userId: TEST_USER_ID });
expect(result).toBeDefined();
expect(result.message).toBe("Entity deleted successfully.");
});
diff --git a/mem0-ts/src/client/tests/integration/global-setup.ts b/mem0-ts/src/client/tests/integration/global-setup.ts
index 215c61d8a..7adae5831 100644
--- a/mem0-ts/src/client/tests/integration/global-setup.ts
+++ b/mem0-ts/src/client/tests/integration/global-setup.ts
@@ -18,10 +18,10 @@ export default async function globalSetup() {
// Full project wipe — all four filters set explicitly
try {
await client.deleteAll({
- user_id: "*",
- agent_id: "*",
- app_id: "*",
- run_id: "*",
+ userId: "*",
+ agentId: "*",
+ appId: "*",
+ runId: "*",
});
} catch {
// ignore — may 404 if no data exists
diff --git a/mem0-ts/src/client/tests/integration/global-teardown.ts b/mem0-ts/src/client/tests/integration/global-teardown.ts
index 9e94af661..56815d556 100644
--- a/mem0-ts/src/client/tests/integration/global-teardown.ts
+++ b/mem0-ts/src/client/tests/integration/global-teardown.ts
@@ -17,10 +17,10 @@ export default async function globalTeardown() {
try {
await client.deleteAll({
- user_id: "*",
- agent_id: "*",
- app_id: "*",
- run_id: "*",
+ userId: "*",
+ agentId: "*",
+ appId: "*",
+ runId: "*",
});
} catch {
// ignore
diff --git a/mem0-ts/src/client/tests/integration/helpers.ts b/mem0-ts/src/client/tests/integration/helpers.ts
index 3f0b8bfc1..c85baa189 100644
--- a/mem0-ts/src/client/tests/integration/helpers.ts
+++ b/mem0-ts/src/client/tests/integration/helpers.ts
@@ -64,7 +64,7 @@ export async function waitForMemories(
): Promise {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const memories = await withRetry(() =>
- client.getAll({ filters: { user_id: userId } }),
+ client.getAll({ filters: { userId } }),
);
if (Array.isArray(memories) && memories.length >= minCount) {
return memories;
@@ -154,7 +154,7 @@ export async function seedTestMemories(
"Nice to meet you! I'll remember that your favorite color is blue.",
},
],
- { user_id: userId },
+ { userId },
),
);
@@ -170,7 +170,7 @@ export async function seedTestMemories(
content: "Got it, you're a software engineer at Acme Corp!",
},
],
- { user_id: userId },
+ { userId },
),
);
@@ -186,12 +186,12 @@ export async function cleanupTestUser(
userId: string,
): Promise {
try {
- await client.deleteAll({ user_id: userId });
+ await client.deleteAll({ userId });
} catch {
// ignore
}
try {
- await client.deleteUsers({ user_id: userId });
+ await client.deleteUsers({ userId });
} catch {
// ignore
}
@@ -209,10 +209,10 @@ export async function fullProjectCleanup(client: MemoryClient): Promise {
// Delete all memories — all four filters set explicitly
try {
await client.deleteAll({
- user_id: "*",
- agent_id: "*",
- app_id: "*",
- run_id: "*",
+ userId: "*",
+ agentId: "*",
+ appId: "*",
+ runId: "*",
});
} catch {
// ignore — may 404 if no data exists
diff --git a/mem0-ts/src/client/tests/integration/management.test.ts b/mem0-ts/src/client/tests/integration/management.test.ts
index dc79ff86e..a6b5c97b5 100644
--- a/mem0-ts/src/client/tests/integration/management.test.ts
+++ b/mem0-ts/src/client/tests/integration/management.test.ts
@@ -67,16 +67,16 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
expect(project).toBeDefined();
expect(typeof project).toBe("object");
- expect("custom_instructions" in project).toBe(true);
+ expect("customInstructions" in project).toBe(true);
- originalInstructions = project.custom_instructions;
+ originalInstructions = project.customInstructions;
});
test("updates project custom_instructions via updateProject()", async () => {
const testInstruction = `integration-test-${randomUUID().slice(0, 8)}`;
const result = await client.updateProject({
- custom_instructions: testInstruction,
+ customInstructions: testInstruction,
});
expect(result).toBeDefined();
@@ -85,11 +85,11 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
const project = await client.getProject({
fields: ["custom_instructions"],
});
- expect(project.custom_instructions).toBe(testInstruction);
+ expect(project.customInstructions).toBe(testInstruction);
// Restore original
await client.updateProject({
- custom_instructions: originalInstructions || "",
+ customInstructions: originalInstructions || "",
});
});
});
@@ -120,26 +120,26 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED],
}),
);
- createdWebhookId = result.webhook_id!;
- expect(result.webhook_id).toBeDefined();
+ createdWebhookId = result.webhookId!;
+ expect(result.webhookId).toBeDefined();
});
test("createWebhook returns the correct name", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
- const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
+ const wh = webhooks.find((w) => w.webhookId === 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.webhook_id === createdWebhookId);
+ const wh = webhooks.find((w) => w.webhookId === 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.webhook_id === createdWebhookId);
- expect(wh!.event_types?.sort()).toStrictEqual(
+ const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
+ expect(wh!.eventTypes?.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.webhook_id === createdWebhookId);
+ const found = webhooks.find((w) => w.webhookId === createdWebhookId);
expect(found).toBeDefined();
});
test("getWebhooks shows the webhook as active", async () => {
const webhooks = await withRetry(() => client.getWebhooks());
- const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
- expect(found!.is_active).toBe(true);
+ const found = webhooks.find((w) => w.webhookId === createdWebhookId);
+ expect(found!.isActive).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.webhook_id === createdWebhookId);
+ const updated = webhooks.find((w) => w.webhookId === 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.webhook_id === createdWebhookId);
- expect(updated!.event_types?.sort()).toStrictEqual(
+ const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
+ expect(updated!.eventTypes?.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.webhook_id === createdWebhookId);
+ const found = webhooks.find((w) => w.webhookId === createdWebhookId);
expect(found).toBeUndefined();
createdWebhookId = "";
});
diff --git a/mem0-ts/src/client/tests/integration/search.test.ts b/mem0-ts/src/client/tests/integration/search.test.ts
index 0591f2ffa..1f3e9cf89 100644
--- a/mem0-ts/src/client/tests/integration/search.test.ts
+++ b/mem0-ts/src/client/tests/integration/search.test.ts
@@ -43,7 +43,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
const results = await waitForSearchResults(
client,
"What is my favorite color?",
- { filters: { user_id: TEST_USER_ID } },
+ { filters: { userId: TEST_USER_ID } },
);
expect(Array.isArray(results)).toBe(true);
@@ -64,7 +64,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
client,
"What do you know about me?",
{
- filters: { OR: [{ user_id: TEST_USER_ID }] },
+ filters: { OR: [{ userId: TEST_USER_ID }] },
},
);
@@ -89,15 +89,15 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
const entry = history[0];
expect(typeof entry.id).toBe("string");
- expect(typeof entry.memory_id).toBe("string");
+ expect(typeof entry.memoryId).toBe("string");
expect(["ADD", "UPDATE", "DELETE", "NOOP"]).toContain(entry.event);
- expect(new Date(entry.created_at).toString()).not.toBe("Invalid Date");
- expect(new Date(entry.updated_at).toString()).not.toBe("Invalid Date");
+ expect(new Date(entry.createdAt).toString()).not.toBe("Invalid Date");
+ expect(new Date(entry.updatedAt).toString()).not.toBe("Invalid Date");
expect(
- entry.new_memory === null || typeof entry.new_memory === "string",
+ entry.newMemory === null || typeof entry.newMemory === "string",
).toBe(true);
expect(
- entry.old_memory === null || typeof entry.old_memory === "string",
+ entry.oldMemory === null || typeof entry.oldMemory === "string",
).toBe(true);
const events = history.map((h) => h.event);
@@ -109,7 +109,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
describe("edge cases", () => {
test("search for non-existent user returns empty results", async () => {
const results = await client.search("anything", {
- filters: { user_id: `nonexistent-user-${randomUUID()}` },
+ filters: { userId: `nonexistent-user-${randomUUID()}` },
});
expect(Array.isArray(results)).toBe(true);
@@ -120,8 +120,8 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
const results = await client.search(
"Tell me about integration test user",
{
- filters: { user_id: TEST_USER_ID },
- top_k: 1,
+ filters: { userId: TEST_USER_ID },
+ topK: 1,
},
);
diff --git a/mem0-ts/src/client/tests/memoryClient.crud.test.ts b/mem0-ts/src/client/tests/memoryClient.crud.test.ts
index 7391f8994..9ce61c003 100644
--- a/mem0-ts/src/client/tests/memoryClient.crud.test.ts
+++ b/mem0-ts/src/client/tests/memoryClient.crud.test.ts
@@ -27,7 +27,7 @@ describe("MemoryClient - add()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.add([{ role: "user", content: "Hello" }], { user_id: "u1" });
+ await client.add([{ role: "user", content: "Hello" }], { userId: "u1" });
expect(findFetchCall(mock, "/v1/memories/", "POST")).toBeDefined();
});
@@ -39,7 +39,7 @@ describe("MemoryClient - add()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.add(messages, { user_id: "u1" });
+ await client.add(messages, { userId: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
expect(getFetchBody(call!).messages).toEqual(messages);
@@ -65,7 +65,7 @@ describe("MemoryClient - add()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.add([], { user_id: "u1" });
+ await client.add([], { userId: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
expect(getFetchBody(call!).messages).toEqual([]);
@@ -215,7 +215,7 @@ describe("MemoryClient - deleteAll()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.deleteAll({ user_id: "u1" });
+ await client.deleteAll({ userId: "u1" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
@@ -231,7 +231,7 @@ describe("MemoryClient - deleteAll()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.deleteAll({ user_id: "user@email.com" });
+ await client.deleteAll({ userId: "user@email.com" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
diff --git a/mem0-ts/src/client/tests/memoryClient.project.test.ts b/mem0-ts/src/client/tests/memoryClient.project.test.ts
index 4e5c9014e..ed5dd6696 100644
--- a/mem0-ts/src/client/tests/memoryClient.project.test.ts
+++ b/mem0-ts/src/client/tests/memoryClient.project.test.ts
@@ -111,9 +111,9 @@ describe("MemoryClient - feedback()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.feedback({
- memory_id: "mem_123",
+ memoryId: "mem_123",
feedback: Feedback.POSITIVE,
- feedback_reason: "Very helpful",
+ feedbackReason: "Very helpful",
});
const call = findFetchCall(mock, "/v1/feedback/", "POST");
@@ -130,9 +130,9 @@ describe("MemoryClient - feedback()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.feedback({
- memory_id: "mem_123",
+ memoryId: "mem_123",
feedback: Feedback.POSITIVE,
- feedback_reason: "Very helpful",
+ feedbackReason: "Very helpful",
});
const call = findFetchCall(mock, "/v1/feedback/", "POST");
@@ -178,7 +178,7 @@ describe("MemoryClient - Memory Exports", () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
- "Missing memory_export_id or filters",
+ "Missing memoryExportId or filters",
);
});
@@ -191,7 +191,7 @@ describe("MemoryClient - Memory Exports", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.getMemoryExport({ memory_export_id: "exp_123" });
+ await client.getMemoryExport({ memoryExportId: "exp_123" });
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
});
diff --git a/mem0-ts/src/client/tests/memoryClient.search.test.ts b/mem0-ts/src/client/tests/memoryClient.search.test.ts
index 6efbaf6b5..f1f5c7e5f 100644
--- a/mem0-ts/src/client/tests/memoryClient.search.test.ts
+++ b/mem0-ts/src/client/tests/memoryClient.search.test.ts
@@ -22,7 +22,7 @@ describe("MemoryClient - search()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", {
- filters: { user_id: "u1" },
+ filters: { userId: "u1" },
});
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
@@ -35,7 +35,7 @@ describe("MemoryClient - search()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", {
- filters: { user_id: "u1" },
+ filters: { userId: "u1" },
});
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
@@ -48,7 +48,7 @@ describe("MemoryClient - search()", () => {
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
- await client.search("test", { filters: { user_id: "u1" } });
+ await client.search("test", { filters: { userId: "u1" } });
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
@@ -88,7 +88,7 @@ describe("MemoryClient - search()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.search("nonexistent query", {
- filters: { user_id: "u1" },
+ filters: { userId: "u1" },
});
expect(result).toHaveLength(0);
});
diff --git a/mem0-ts/src/client/utils.ts b/mem0-ts/src/client/utils.ts
new file mode 100644
index 000000000..5580b0585
--- /dev/null
+++ b/mem0-ts/src/client/utils.ts
@@ -0,0 +1,49 @@
+/**
+ * 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),
+ ]),
+ );
+}
diff --git a/mem0-ts/src/oss/src/config/defaults.ts b/mem0-ts/src/oss/src/config/defaults.ts
index bbce65cc6..c42718139 100644
--- a/mem0-ts/src/oss/src/config/defaults.ts
+++ b/mem0-ts/src/oss/src/config/defaults.ts
@@ -26,15 +26,6 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
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: {
diff --git a/mem0-ts/src/oss/src/config/manager.ts b/mem0-ts/src/oss/src/config/manager.ts
index 6235157ce..02afe6ddd 100644
--- a/mem0-ts/src/oss/src/config/manager.ts
+++ b/mem0-ts/src/oss/src/config/manager.ts
@@ -132,10 +132,9 @@ export class ConfigManager {
userConfig.historyStore?.config?.historyDbPath ||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
customInstructions: userConfig.customInstructions,
- graphStore: {
- ...DEFAULT_MEMORY_CONFIG.graphStore,
- ...userConfig.graphStore,
- },
+ graphStore: userConfig.graphStore
+ ? { ...userConfig.graphStore }
+ : undefined,
historyStore: (() => {
const defaultHistoryStore = DEFAULT_MEMORY_CONFIG.historyStore!;
const historyProvider =
@@ -158,7 +157,6 @@ export class ConfigManager {
})(),
disableHistory:
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
- enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
};
// Validate the merged config
diff --git a/mem0-ts/src/oss/src/memory/index.ts b/mem0-ts/src/oss/src/memory/index.ts
index d4c4e25d0..044585028 100644
--- a/mem0-ts/src/oss/src/memory/index.ts
+++ b/mem0-ts/src/oss/src/memory/index.ts
@@ -47,7 +47,6 @@ export class Memory {
private collectionName: string | undefined;
private apiVersion: string;
private graphMemory?: MemoryGraph;
- private enableGraph: boolean;
telemetryId: string;
private _initPromise: Promise;
private _initError?: Error;
@@ -79,11 +78,10 @@ export class Memory {
this.collectionName = this.config.vectorStore.config.collectionName;
this.apiVersion = this.config.version || "v1.0";
- this.enableGraph = this.config.enableGraph || false;
this.telemetryId = "anonymous";
- // Initialize graph memory if configured
- if (this.enableGraph && this.config.graphStore) {
+ // Initialize graph memory if graphStore is configured
+ if (this.config.graphStore) {
this.graphMemory = new MemoryGraph(this.config);
}
@@ -159,7 +157,6 @@ export class Memory {
api_version: this.apiVersion,
client_type: "Memory",
collection_name: this.collectionName,
- enable_graph: this.enableGraph,
});
} catch (error) {}
}
diff --git a/mem0-ts/src/oss/src/tests/sqlite-backward-compat.test.ts b/mem0-ts/src/oss/src/tests/sqlite-backward-compat.test.ts
index 8e2eb7750..3f7991b3f 100644
--- a/mem0-ts/src/oss/src/tests/sqlite-backward-compat.test.ts
+++ b/mem0-ts/src/oss/src/tests/sqlite-backward-compat.test.ts
@@ -38,7 +38,7 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
expect(cfg.historyStore!.provider).toBe("sqlite");
expect(cfg.historyStore!.config.historyDbPath).toBe("memory.db");
expect(cfg.disableHistory).toBe(false);
- expect(cfg.enableGraph).toBe(false);
+ expect(cfg.graphStore).toBeUndefined();
});
it("workaround: explicit historyStore still works (existing user pattern)", () => {
@@ -106,7 +106,6 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
it("graphStore config passes through unchanged", () => {
const cfg = ConfigManager.mergeConfig({
- enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
@@ -116,7 +115,6 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
},
},
});
- expect(cfg.enableGraph).toBe(true);
expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687");
});
diff --git a/mem0-ts/src/oss/src/types/index.ts b/mem0-ts/src/oss/src/types/index.ts
index 0476ef515..5a5cb91b4 100644
--- a/mem0-ts/src/oss/src/types/index.ts
+++ b/mem0-ts/src/oss/src/types/index.ts
@@ -82,7 +82,6 @@ export interface MemoryConfig {
historyDbPath?: string;
customInstructions?: string;
graphStore?: GraphStoreConfig;
- enableGraph?: boolean;
}
export interface MemoryItem {
@@ -149,7 +148,6 @@ export const MemoryConfigSchema = z.object({
}),
historyDbPath: z.string().optional(),
customInstructions: z.string().optional(),
- enableGraph: z.boolean().optional(),
graphStore: z
.object({
provider: z.string(),
diff --git a/mem0/client/main.py b/mem0/client/main.py
index 6511e73ca..6cf74e412 100644
--- a/mem0/client/main.py
+++ b/mem0/client/main.py
@@ -665,7 +665,6 @@ class MemoryClient:
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
- enable_graph: Optional[bool] = None,
memory_depth: Optional[str] = None,
usecase_setting: Optional[str] = None,
multilingual: Optional[bool] = None,
@@ -677,7 +676,6 @@ class MemoryClient:
custom_instructions: New instructions for the project.
custom_categories: New categories for the project.
retrieval_criteria: New retrieval criteria for the project.
- enable_graph: Enable or disable the graph for the project.
memory_depth: Memory depth for the project.
usecase_setting: Usecase setting for the project.
multilingual: Whether to use the input language for memory storage and retrieval.
@@ -703,7 +701,6 @@ class MemoryClient:
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
- "enable_graph": enable_graph,
"memory_depth": memory_depth,
"usecase_setting": usecase_setting,
"multilingual": multilingual,
@@ -1549,7 +1546,6 @@ class AsyncMemoryClient:
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
- enable_graph: Optional[bool] = None,
memory_depth: Optional[str] = None,
usecase_setting: Optional[str] = None,
multilingual: Optional[bool] = None,
@@ -1561,7 +1557,6 @@ class AsyncMemoryClient:
custom_instructions: New instructions for the project.
custom_categories: New categories for the project.
retrieval_criteria: New retrieval criteria for the project.
- enable_graph: Enable or disable the graph for the project.
memory_depth: Memory depth for the project.
usecase_setting: Usecase setting for the project.
multilingual: Whether to use the input language for memory storage and retrieval.
@@ -1587,7 +1582,6 @@ class AsyncMemoryClient:
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
- "enable_graph": enable_graph,
"memory_depth": memory_depth,
"usecase_setting": usecase_setting,
"multilingual": multilingual,
diff --git a/mem0/client/project.py b/mem0/client/project.py
index 92a021dec..fe466b7ef 100644
--- a/mem0/client/project.py
+++ b/mem0/client/project.py
@@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
from mem0.client.utils import api_error_handler
from mem0.memory.telemetry import capture_client_event
+
# Exception classes are referenced in docstrings only
logger = logging.getLogger(__name__)
@@ -177,7 +178,6 @@ class BaseProject(ABC):
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
- enable_graph: Optional[bool] = None,
) -> Dict[str, Any]:
"""
Update project settings.
@@ -186,7 +186,6 @@ class BaseProject(ABC):
custom_instructions: New instructions for the project
custom_categories: New categories for the project
retrieval_criteria: New retrieval criteria for the project
- enable_graph: Enable or disable the graph for the project
Returns:
Dictionary containing the API response.
@@ -398,7 +397,6 @@ class Project(BaseProject):
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
- enable_graph: Optional[bool] = None,
multilingual: Optional[bool] = None,
) -> Dict[str, Any]:
"""
@@ -408,7 +406,6 @@ class Project(BaseProject):
custom_instructions: New instructions for the project
custom_categories: New categories for the project
retrieval_criteria: New retrieval criteria for the project
- enable_graph: Enable or disable the graph for the project
multilingual: Whether to use the input language for memory storage and retrieval
Returns:
@@ -425,13 +422,12 @@ class Project(BaseProject):
custom_instructions is None
and custom_categories is None
and retrieval_criteria is None
- and enable_graph is None
and multilingual is None
):
raise ValueError(
"At least one parameter must be provided for update: "
"custom_instructions, custom_categories, retrieval_criteria, "
- "enable_graph, multilingual"
+ "multilingual"
)
payload = self._prepare_params(
@@ -439,7 +435,6 @@ class Project(BaseProject):
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
- "enable_graph": enable_graph,
"multilingual": multilingual,
}
)
@@ -455,7 +450,6 @@ class Project(BaseProject):
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
- "enable_graph": enable_graph,
"multilingual": multilingual,
"sync_type": "sync",
},
@@ -720,7 +714,6 @@ class AsyncProject(BaseProject):
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
- enable_graph: Optional[bool] = None,
multilingual: Optional[bool] = None,
) -> Dict[str, Any]:
"""
@@ -730,7 +723,6 @@ class AsyncProject(BaseProject):
custom_instructions: New instructions for the project
custom_categories: New categories for the project
retrieval_criteria: New retrieval criteria for the project
- enable_graph: Enable or disable the graph for the project
multilingual: Whether to use the input language for memory storage and retrieval
Returns:
@@ -747,13 +739,12 @@ class AsyncProject(BaseProject):
custom_instructions is None
and custom_categories is None
and retrieval_criteria is None
- and enable_graph is None
and multilingual is None
):
raise ValueError(
"At least one parameter must be provided for update: "
"custom_instructions, custom_categories, retrieval_criteria, "
- "enable_graph, multilingual"
+ "multilingual"
)
payload = self._prepare_params(
@@ -761,7 +752,6 @@ class AsyncProject(BaseProject):
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
- "enable_graph": enable_graph,
"multilingual": multilingual,
}
)
@@ -777,7 +767,6 @@ class AsyncProject(BaseProject):
"custom_instructions": custom_instructions,
"custom_categories": custom_categories,
"retrieval_criteria": retrieval_criteria,
- "enable_graph": enable_graph,
"multilingual": multilingual,
"sync_type": "async",
},
diff --git a/mem0/client/types.py b/mem0/client/types.py
index e55b78713..4053d69de 100644
--- a/mem0/client/types.py
+++ b/mem0/client/types.py
@@ -31,7 +31,6 @@ class AddMemoryOptions(EntityOptions):
structured_data_schema: Optional[Dict[str, Any]] = Field(
default=None, description="Schema for structured data extraction"
)
- enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory for this operation")
class SearchMemoryOptions(BaseModel):
@@ -50,7 +49,6 @@ class SearchMemoryOptions(BaseModel):
threshold: Optional[float] = Field(default=None, description="Minimum similarity score threshold")
fields: Optional[List[str]] = Field(default=None, description="Fields to include in the response")
categories: Optional[List[str]] = Field(default=None, description="Categories to filter by")
- enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory for this search")
class GetAllMemoryOptions(BaseModel):
@@ -72,7 +70,6 @@ class GetAllMemoryOptions(BaseModel):
default=None, description="Filter memories created on or before this date (ISO 8601)"
)
categories: Optional[List[str]] = Field(default=None, description="Categories to filter by")
- enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory for retrieval")
class DeleteAllMemoryOptions(EntityOptions):
@@ -96,7 +93,6 @@ class ProjectUpdateOptions(BaseModel):
custom_categories: Optional[List[Dict[str, Any]]] = Field(
default=None, description="Custom categories for classification"
)
- enable_graph: Optional[bool] = Field(default=None, description="Whether to enable graph memory")
memory_depth: Optional[str] = Field(default=None, description="Memory depth configuration")
usecase_setting: Optional[Any] = Field(default=None, description="Use case specific settings")
multilingual: Optional[bool] = Field(default=None, description="Whether to enable multilingual support")
diff --git a/mem0/memory/main.py b/mem0/memory/main.py
index c18b093a0..24ecf50b4 100644
--- a/mem0/memory/main.py
+++ b/mem0/memory/main.py
@@ -281,12 +281,9 @@ class Memory(MemoryBase):
config.reranker.config
)
- self.enable_graph = False
-
if self.config.graph_store.config:
provider = self.config.graph_store.provider
self.graph = GraphStoreFactory.create(provider, self.config)
- self.enable_graph = True
else:
self.graph = None
if MEM0_TELEMETRY:
@@ -477,7 +474,7 @@ class Memory(MemoryBase):
vector_store_result = future1.result()
graph_result = future2.result()
- if self.enable_graph:
+ if self.graph:
return {
"results": vector_store_result,
"relations": graph_result,
@@ -726,7 +723,7 @@ class Memory(MemoryBase):
def _add_to_graph(self, messages, filters):
added_entities = []
- if self.enable_graph:
+ if self.graph:
if filters.get("user_id") is None:
filters["user_id"] = "user"
@@ -821,7 +818,7 @@ class Memory(MemoryBase):
with concurrent.futures.ThreadPoolExecutor() as executor:
future_memories = executor.submit(self._get_all_from_vector_store, effective_filters, top_k)
future_graph_entities = (
- executor.submit(self.graph.get_all, effective_filters, top_k) if self.enable_graph else None
+ executor.submit(self.graph.get_all, effective_filters, top_k) if self.graph else None
)
concurrent.futures.wait(
@@ -831,7 +828,7 @@ class Memory(MemoryBase):
all_memories_result = future_memories.result()
graph_entities_result = future_graph_entities.result() if future_graph_entities else None
- if self.enable_graph:
+ if self.graph:
return {"results": all_memories_result, "relations": graph_entities_result}
return {"results": all_memories_result}
@@ -960,7 +957,7 @@ class Memory(MemoryBase):
with concurrent.futures.ThreadPoolExecutor() as executor:
future_memories = executor.submit(self._search_vector_store, query, effective_filters, top_k, threshold)
future_graph_entities = (
- executor.submit(self.graph.search, query, effective_filters, top_k) if self.enable_graph else None
+ executor.submit(self.graph.search, query, effective_filters, top_k) if self.graph else None
)
concurrent.futures.wait(
@@ -978,7 +975,7 @@ class Memory(MemoryBase):
except Exception as e:
logger.warning(f"Reranking failed, using original results: {e}")
- if self.enable_graph:
+ if self.graph:
return {"results": original_memories, "relations": graph_entities}
return {"results": original_memories}
@@ -1157,7 +1154,7 @@ class Memory(MemoryBase):
raise ValueError(f"Memory with id {memory_id} not found")
# Clean up graph entities before deleting from vector store
- if self.enable_graph:
+ if self.graph:
try:
memory_text = existing_memory.payload.get("data", "")
if memory_text:
@@ -1205,7 +1202,7 @@ class Memory(MemoryBase):
logger.info(f"Deleted {len(memories)} memories")
- if self.enable_graph:
+ if self.graph:
self.graph.delete_all(filters)
return {"message": "Memories deleted successfully!"}
@@ -1407,7 +1404,7 @@ class Memory(MemoryBase):
)
capture_event("mem0.reset", self, {"sync_type": "sync"})
- if self.enable_graph:
+ if self.graph:
try:
self.graph.reset()
except Exception:
@@ -1442,12 +1439,9 @@ class AsyncMemory(MemoryBase):
config.reranker.config
)
- self.enable_graph = False
-
if self.config.graph_store.config:
provider = self.config.graph_store.provider
self.graph = GraphStoreFactory.create(provider, self.config)
- self.enable_graph = True
else:
self.graph = None
@@ -1590,7 +1584,7 @@ class AsyncMemory(MemoryBase):
vector_store_result, graph_result = await asyncio.gather(vector_store_task, graph_task)
- if self.enable_graph:
+ if self.graph:
return {
"results": vector_store_result,
"relations": graph_result,
@@ -1863,7 +1857,7 @@ class AsyncMemory(MemoryBase):
async def _add_to_graph(self, messages, filters):
added_entities = []
- if self.enable_graph:
+ if self.graph:
if filters.get("user_id") is None:
filters["user_id"] = "user"
@@ -1961,7 +1955,7 @@ class AsyncMemory(MemoryBase):
vector_store_task = asyncio.create_task(self._get_all_from_vector_store(effective_filters, top_k))
graph_task = None
- if self.enable_graph:
+ if self.graph:
graph_get_all = getattr(self.graph, "get_all", None)
if callable(graph_get_all):
if asyncio.iscoroutinefunction(graph_get_all):
@@ -2104,7 +2098,7 @@ class AsyncMemory(MemoryBase):
vector_store_task = asyncio.create_task(self._search_vector_store(query, effective_filters, top_k, threshold))
graph_task = None
- if self.enable_graph:
+ if self.graph:
if hasattr(self.graph.search, "__await__"): # Check if graph search is async
graph_task = asyncio.create_task(self.graph.search(query, effective_filters, top_k))
else:
@@ -2127,7 +2121,7 @@ class AsyncMemory(MemoryBase):
except Exception as e:
logger.warning(f"Reranking failed, using original results: {e}")
- if self.enable_graph:
+ if self.graph:
return {"results": original_memories, "relations": graph_entities}
return {"results": original_memories}
@@ -2309,7 +2303,7 @@ class AsyncMemory(MemoryBase):
raise ValueError(f"Memory with id {memory_id} not found")
# Clean up graph entities before deleting from vector store
- if self.enable_graph:
+ if self.graph:
try:
memory_text = existing_memory.payload.get("data", "")
if memory_text:
@@ -2360,7 +2354,7 @@ class AsyncMemory(MemoryBase):
logger.info(f"Deleted {len(memories[0])} memories")
- if self.enable_graph:
+ if self.graph:
await asyncio.to_thread(self.graph.delete_all, filters)
return {"message": "Memories deleted successfully!"}
@@ -2587,7 +2581,7 @@ class AsyncMemory(MemoryBase):
)
capture_event("mem0.reset", self, {"sync_type": "async"})
- if self.enable_graph:
+ if self.graph:
try:
await asyncio.to_thread(self.graph.reset)
except Exception:
diff --git a/tests/test_graph_delete.py b/tests/test_graph_delete.py
deleted file mode 100644
index a54d6d4f6..000000000
--- a/tests/test_graph_delete.py
+++ /dev/null
@@ -1,517 +0,0 @@
-"""Tests for graph cleanup on memory deletion (issue #3245)."""
-
-from unittest.mock import MagicMock, patch
-
-import pytest
-
-from mem0.configs.base import MemoryConfig
-
-
-class MockVectorMemory:
- def __init__(self, memory_id, payload, score=0.8):
- self.id = memory_id
- self.payload = payload
- self.score = score
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_calls_graph_cleanup_when_graph_enabled(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """When graph is enabled, delete() should call graph.delete() with memory text and filters."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- # Enable graph with a mock
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- # Set up vector store to return a memory with graph-relevant data
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1",
- {
- "data": "Alice likes Bob",
- "user_id": "user-1",
- "agent_id": "agent-1",
- "hash": "abc",
- },
- )
-
- memory.delete("mem-1")
-
- # graph.delete should have been called with the memory text and filters
- memory.graph.delete.assert_called_once_with(
- "Alice likes Bob", {"user_id": "user-1", "agent_id": "agent-1"}
- )
-
- # _delete_memory should still have been called (vector store + history cleanup)
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_skips_graph_when_not_enabled(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """When graph is not enabled, delete() should not attempt graph cleanup."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- assert memory.enable_graph is False
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"}
- )
-
- result = memory.delete("mem-1")
-
- assert result == {"message": "Memory deleted successfully!"}
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_continues_if_graph_cleanup_fails(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """If graph cleanup raises an exception, delete() should still succeed."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
- memory.graph.delete.side_effect = RuntimeError("Neo4j connection lost")
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"}
- )
-
- # Should not raise
- result = memory.delete("mem-1")
- assert result == {"message": "Memory deleted successfully!"}
-
- # Vector store deletion should still proceed
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_skips_graph_when_no_user_id(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """Graph cleanup should be skipped if the memory has no user_id."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- # Memory with no user_id
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"data": "Some data", "hash": "abc"}
- )
-
- memory.delete("mem-1")
-
- # graph.delete should NOT have been called since there's no user_id
- memory.graph.delete.assert_not_called()
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_skips_graph_when_no_memory_text(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """Graph cleanup should be skipped if the memory has no text data."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"user_id": "user-1", "hash": "abc"}
- )
-
- memory.delete("mem-1")
-
- memory.graph.delete.assert_not_called()
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_passes_all_filters_to_graph(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """Graph cleanup should include all available filters (user_id, agent_id, run_id)."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1",
- {
- "data": "Alice likes Bob",
- "user_id": "user-1",
- "agent_id": "agent-1",
- "run_id": "run-1",
- "hash": "abc",
- },
- )
-
- memory.delete("mem-1")
-
- memory.graph.delete.assert_called_once_with(
- "Alice likes Bob",
- {"user_id": "user-1", "agent_id": "agent-1", "run_id": "run-1"},
- )
-
-
-@pytest.mark.asyncio
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-async def test_async_delete_calls_graph_cleanup(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """Async delete() should also perform graph cleanup."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import AsyncMemory
-
- config = MemoryConfig()
- memory = AsyncMemory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1",
- {
- "data": "Alice likes Bob",
- "user_id": "user-1",
- "hash": "abc",
- },
- )
-
- result = await memory.delete("mem-1")
-
- assert result == {"message": "Memory deleted successfully!"}
- memory.graph.delete.assert_called_once_with("Alice likes Bob", {"user_id": "user-1"})
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@pytest.mark.asyncio
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-async def test_async_delete_continues_if_graph_cleanup_fails(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """Async delete() should continue even if graph cleanup fails."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import AsyncMemory
-
- config = MemoryConfig()
- memory = AsyncMemory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
- memory.graph.delete.side_effect = RuntimeError("Graph error")
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"}
- )
-
- result = await memory.delete("mem-1")
- assert result == {"message": "Memory deleted successfully!"}
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_raises_for_nonexistent_memory_with_graph_enabled(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """delete() should raise ValueError for non-existent memory even with graph enabled."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- mock_vector_store.get.return_value = None
-
- with pytest.raises(ValueError, match="Memory with id non-existent not found"):
- memory.delete("non-existent")
-
- memory.graph.delete.assert_not_called()
- mock_vector_store.delete.assert_not_called()
-
-
-@pytest.mark.asyncio
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-async def test_async_delete_raises_for_nonexistent_memory_with_graph_enabled(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """Async delete() should raise ValueError for non-existent memory even with graph enabled."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_store.get.return_value = None
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import AsyncMemory
-
- config = MemoryConfig()
- memory = AsyncMemory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- with pytest.raises(ValueError, match="Memory with id non-existent not found"):
- await memory.delete("non-existent")
-
- memory.graph.delete.assert_not_called()
- mock_vector_store.delete.assert_not_called()
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_delete_all_does_not_trigger_per_memory_graph_cleanup(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """delete_all() should use graph.delete_all(), not per-memory graph.delete()."""
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- mem1 = MockVectorMemory("mem-1", {"data": "Alice likes Bob", "user_id": "user-1"})
- mem2 = MockVectorMemory("mem-2", {"data": "Bob likes Charlie", "user_id": "user-1"})
- mock_vector_store.list.return_value = ([mem1, mem2], 2)
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"data": "Alice likes Bob", "user_id": "user-1"}
- )
-
- memory.delete_all(user_id="user-1")
-
- # graph.delete (per-memory) should NOT be called
- memory.graph.delete.assert_not_called()
- # graph.delete_all (bulk) SHOULD be called
- memory.graph.delete_all.assert_called_once_with({"user_id": "user-1"})
-
-
-@patch("mem0.utils.factory.EmbedderFactory.create")
-@patch("mem0.utils.factory.VectorStoreFactory.create")
-@patch("mem0.utils.factory.LlmFactory.create")
-@patch("mem0.memory.storage.SQLiteManager")
-def test_internal_delete_memory_does_not_trigger_graph_cleanup(
- mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory
-):
- """_delete_memory() should NOT call graph.delete() — only the public delete() does.
-
- This ensures that the DELETE branch inside _add_to_vector_store() (which calls
- _delete_memory directly) does not interfere with the parallel graph pipeline
- running in _add_to_graph().
- """
- mock_embedder_factory.return_value = MagicMock()
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
- mock_llm_factory.return_value = MagicMock()
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- memory.enable_graph = True
- memory.graph = MagicMock()
-
- mock_vector_store.get.return_value = MockVectorMemory(
- "mem-1", {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"}
- )
-
- # Call _delete_memory directly (as _add_to_vector_store does for DELETE events)
- memory._delete_memory("mem-1")
-
- # graph.delete should NOT have been called — graph cleanup is only in delete()
- memory.graph.delete.assert_not_called()
- # But vector store deletion should proceed
- mock_vector_store.delete.assert_called_once_with(vector_id="mem-1")
-
-
-def test_graph_memory_delete_calls_internal_methods():
- """Test that MemoryGraph.delete() calls the expected internal pipeline methods."""
- from unittest.mock import patch as _patch
-
- # We need to mock the Neo4j import
- with _patch.dict("sys.modules", {"langchain_neo4j": MagicMock(), "rank_bm25": MagicMock()}):
- from mem0.memory.graph_memory import MemoryGraph
-
- with _patch.object(MemoryGraph, "__init__", return_value=None):
- graph = MemoryGraph.__new__(MemoryGraph)
-
- # Mock the internal methods
- graph._retrieve_nodes_from_data = MagicMock(
- return_value={"alice": "person", "bob": "person"}
- )
- graph._establish_nodes_relations_from_data = MagicMock(
- return_value=[
- {"source": "alice", "destination": "bob", "relationship": "likes"}
- ]
- )
- graph._delete_entities = MagicMock(return_value=[])
-
- filters = {"user_id": "user-1"}
- graph.delete("Alice likes Bob", filters)
-
- graph._retrieve_nodes_from_data.assert_called_once_with("Alice likes Bob", filters)
- graph._establish_nodes_relations_from_data.assert_called_once_with(
- "Alice likes Bob", filters, {"alice": "person", "bob": "person"}
- )
- graph._delete_entities.assert_called_once_with(
- [{"source": "alice", "destination": "bob", "relationship": "likes"}],
- filters,
- )
-
-
-def test_graph_memory_delete_skips_when_no_entities():
- """Test that MemoryGraph.delete() does nothing when no entities are extracted."""
- from unittest.mock import patch as _patch
-
- with _patch.dict("sys.modules", {"langchain_neo4j": MagicMock(), "rank_bm25": MagicMock()}):
- from mem0.memory.graph_memory import MemoryGraph
-
- with _patch.object(MemoryGraph, "__init__", return_value=None):
- graph = MemoryGraph.__new__(MemoryGraph)
-
- graph._retrieve_nodes_from_data = MagicMock(return_value={})
- graph._establish_nodes_relations_from_data = MagicMock()
- graph._delete_entities = MagicMock()
-
- graph.delete("Some text", {"user_id": "user-1"})
-
- graph._retrieve_nodes_from_data.assert_called_once()
- graph._establish_nodes_relations_from_data.assert_not_called()
- graph._delete_entities.assert_not_called()
-
-
-def test_graph_memory_delete_handles_exception():
- """Test that MemoryGraph.delete() catches exceptions without raising."""
- from unittest.mock import patch as _patch
-
- with _patch.dict("sys.modules", {"langchain_neo4j": MagicMock(), "rank_bm25": MagicMock()}):
- from mem0.memory.graph_memory import MemoryGraph
-
- with _patch.object(MemoryGraph, "__init__", return_value=None):
- graph = MemoryGraph.__new__(MemoryGraph)
-
- graph._retrieve_nodes_from_data = MagicMock(
- side_effect=RuntimeError("LLM error")
- )
-
- # Should not raise
- graph.delete("Some text", {"user_id": "user-1"})
diff --git a/tests/test_graph_delete_e2e.py b/tests/test_graph_delete_e2e.py
deleted file mode 100644
index 0169d2ce0..000000000
--- a/tests/test_graph_delete_e2e.py
+++ /dev/null
@@ -1,736 +0,0 @@
-"""
-End-to-end tests for graph cleanup on memory deletion (issue #3245).
-
-Uses a real Kuzu embedded database to verify that graph entities are
-correctly cleaned up when memories are deleted. LLM and embedding calls
-are mocked to provide deterministic entity extraction.
-
-Tests are skipped automatically if kuzu is not installed.
-"""
-
-import shutil
-import tempfile
-from unittest.mock import MagicMock, patch
-
-import pytest
-
-from mem0.configs.base import MemoryConfig
-
-try:
- import kuzu # noqa: F401
- _kuzu_available = True
-except ImportError:
- _kuzu_available = False
-
-requires_kuzu = pytest.mark.skipif(not _kuzu_available, reason="kuzu is not installed")
-
-# ---------------------------------------------------------------------------
-# Helpers
-# ---------------------------------------------------------------------------
-
-
-def _node_count(kuzu_graph):
- """Return total node count in the Kuzu graph."""
- result = kuzu_graph.execute("MATCH (n:Entity) RETURN count(n) AS cnt")
- rows = list(result.rows_as_dict())
- return int(rows[0]["cnt"])
-
-
-def _edge_count(kuzu_graph):
- """Return total edge count in the Kuzu graph."""
- result = kuzu_graph.execute("MATCH ()-[r:CONNECTED_TO]->() RETURN count(r) AS cnt")
- rows = list(result.rows_as_dict())
- return int(rows[0]["cnt"])
-
-
-def _get_edges(kuzu_graph):
- """Return all edges as list of (source, relationship, destination) tuples."""
- result = kuzu_graph.execute(
- "MATCH (s:Entity)-[r:CONNECTED_TO]->(d:Entity) "
- "RETURN s.name AS src, r.name AS rel, d.name AS dst"
- )
- return [(row["src"], row["rel"], row["dst"]) for row in result.rows_as_dict()]
-
-
-def _get_nodes(kuzu_graph):
- """Return all node names."""
- result = kuzu_graph.execute("MATCH (n:Entity) RETURN n.name AS name, n.user_id AS uid")
- return [(row["name"], row["uid"]) for row in result.rows_as_dict()]
-
-
-# ---------------------------------------------------------------------------
-# Fixtures
-# ---------------------------------------------------------------------------
-
-
-class MockVectorMemory:
- """Mimics the object returned by vector_store.get()."""
-
- def __init__(self, memory_id, payload, score=0.8):
- self.id = memory_id
- self.payload = payload
- self.score = score
-
-
-@pytest.fixture
-def kuzu_graph_memory():
- """
- Create a real Kuzu-backed MemoryGraph with mocked LLM and embedder.
- Yields (graph_memory_instance, kuzu_connection) then cleans up.
- """
- import os
-
- import kuzu
-
- tmpdir = tempfile.mkdtemp()
- db_path = os.path.join(tmpdir, "test.kuzu")
- db = kuzu.Database(db_path)
- conn = kuzu.Connection(db)
-
- # We'll construct the MemoryGraph by bypassing __init__ and setting up manually
- from mem0.memory.kuzu_memory import MemoryGraph
-
- mg = MemoryGraph.__new__(MemoryGraph)
-
- # Real Kuzu connection
- mg.db = db
- mg.graph = conn
- mg.node_label = ":Entity"
- mg.rel_label = ":CONNECTED_TO"
- mg.kuzu_create_schema()
-
- # Deterministic embedding: use one-hot-style vectors per entity name
- # to avoid accidental cosine similarity matches between different entities
- embedding_dims = 64
- mg.embedding_dims = embedding_dims
-
- _embed_cache = {}
- _embed_counter = [0]
-
- def deterministic_embed(text):
- """Generate a deterministic, near-orthogonal embedding for each unique text."""
- text_lower = text.lower().strip()
- if text_lower not in _embed_cache:
- # Create a sparse vector — set a unique dimension to 1.0
- vec = [0.0] * embedding_dims
- idx = _embed_counter[0] % embedding_dims
- vec[idx] = 1.0
- # Add small noise to other dims so it's not exactly zero
- import hashlib
-
- h = hashlib.sha256(text_lower.encode()).digest()
- for i in range(embedding_dims):
- vec[i] += float(h[i % len(h)]) / 25500.0 # tiny noise
- norm = sum(v * v for v in vec) ** 0.5
- _embed_cache[text_lower] = [v / norm for v in vec]
- _embed_counter[0] += 1
- return _embed_cache[text_lower]
-
- mock_embedder = MagicMock()
- mock_embedder.embed.side_effect = deterministic_embed
- mock_embedder.config.embedding_dims = embedding_dims
- mg.embedding_model = mock_embedder
-
- # Mock LLM — configured per-test via mock_embedder
- mg.llm = MagicMock()
- mg.llm_provider = "openai"
- mg.user_id = None
- # High threshold so only identical entity names merge, not similar ones
- mg.threshold = 0.99
- mg.config = MagicMock()
- mg.config.graph_store.custom_prompt = None
-
- yield mg, conn
-
- # Cleanup
- conn.close()
- shutil.rmtree(tmpdir, ignore_errors=True)
-
-
-def _setup_llm_for_entities(mg, entities, relations):
- """
- Configure the mock LLM to return specific entities and relations.
-
- entities: list of {"entity": str, "entity_type": str}
- relations: list of {"source": str, "destination": str, "relationship": str}
- """
-
- def generate_response(messages, tools):
- # Detect which tool is being called based on tool definition names
- tool_names = []
- for t in tools:
- if isinstance(t, dict):
- fn = t.get("function", t)
- tool_names.append(fn.get("name", ""))
- else:
- tool_names.append(getattr(t, "name", str(t)))
-
- if any("extract_entities" in n for n in tool_names):
- return {
- "tool_calls": [
- {
- "name": "extract_entities",
- "arguments": {"entities": entities},
- }
- ]
- }
- elif any("establish" in n or "relation" in n for n in tool_names):
- return {
- "tool_calls": [
- {
- "name": "establish_nodes_relations",
- "arguments": {"entities": relations},
- }
- ]
- }
- elif any("delete" in n for n in tool_names):
- # For _get_delete_entities_from_search_output during add() — return nothing to delete
- return {"tool_calls": []}
- return {"tool_calls": []}
-
- mg.llm.generate_response.side_effect = generate_response
-
-
-# ---------------------------------------------------------------------------
-# End-to-end tests
-# ---------------------------------------------------------------------------
-
-
-@requires_kuzu
-class TestKuzuGraphDeleteE2E:
- """End-to-end tests using a real Kuzu database."""
-
- def test_add_creates_nodes_and_edges(self, kuzu_graph_memory):
- """Baseline: verify add() actually creates graph data."""
- mg, conn = kuzu_graph_memory
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
-
- filters = {"user_id": "test_user"}
- mg.add("Alice likes Bob", filters)
-
- assert _node_count(conn) == 2
- assert _edge_count(conn) == 1
- edges = _get_edges(conn)
- assert ("alice", "likes", "bob") in edges
-
- def test_delete_removes_edges_created_by_add(self, kuzu_graph_memory):
- """Core test: delete() should remove the relationships that add() created."""
- mg, conn = kuzu_graph_memory
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
-
- filters = {"user_id": "test_user"}
- mg.add("Alice likes Bob", filters)
-
- assert _edge_count(conn) == 1
-
- # Now delete using the same text — should remove the relationship
- mg.delete("Alice likes Bob", filters)
-
- assert _edge_count(conn) == 0
- # Nodes remain (we don't delete nodes on single memory delete)
- assert _node_count(conn) == 2
-
- def test_delete_only_removes_matching_edges(self, kuzu_graph_memory):
- """delete() should only remove edges matching the extracted relationships."""
- mg, conn = kuzu_graph_memory
-
- # First add: Alice likes Bob
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- filters = {"user_id": "test_user"}
- mg.add("Alice likes Bob", filters)
-
- # Second add: Alice knows Charlie
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Charlie", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Charlie", "relationship": "knows"},
- ],
- )
- mg.add("Alice knows Charlie", filters)
-
- assert _edge_count(conn) == 2
-
- # Delete only the "Alice likes Bob" memory
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- mg.delete("Alice likes Bob", filters)
-
- assert _edge_count(conn) == 1
- edges = _get_edges(conn)
- assert ("alice", "knows", "charlie") in edges
- assert ("alice", "likes", "bob") not in edges
-
- def test_delete_with_different_user_id_does_not_affect_other_users(self, kuzu_graph_memory):
- """delete() scoped to user_id should not touch another user's graph data."""
- mg, conn = kuzu_graph_memory
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
-
- # Add for user1
- mg.add("Alice likes Bob", {"user_id": "user1"})
- # Add same data for user2
- mg.add("Alice likes Bob", {"user_id": "user2"})
-
- assert _edge_count(conn) == 2
-
- # Delete only user1's data
- mg.delete("Alice likes Bob", {"user_id": "user1"})
-
- assert _edge_count(conn) == 1
- # Remaining edge belongs to user2
- nodes = _get_nodes(conn)
- user2_nodes = [n for n in nodes if n[1] == "user2"]
- assert len(user2_nodes) == 2
-
- def test_delete_nonexistent_relationship_is_safe(self, kuzu_graph_memory):
- """delete() on data that doesn't exist in the graph should be a no-op."""
- mg, conn = kuzu_graph_memory
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "hates"},
- ],
- )
-
- filters = {"user_id": "test_user"}
-
- # Nothing in the graph yet
- assert _edge_count(conn) == 0
- assert _node_count(conn) == 0
-
- # Should not raise
- mg.delete("Alice hates Bob", filters)
-
- assert _edge_count(conn) == 0
- assert _node_count(conn) == 0
-
- def test_delete_with_llm_failure_does_not_raise(self, kuzu_graph_memory):
- """If LLM fails during entity extraction, delete() should not raise."""
- mg, conn = kuzu_graph_memory
-
- # Make LLM raise
- mg.llm.generate_response.side_effect = RuntimeError("LLM service down")
-
- filters = {"user_id": "test_user"}
-
- # Should not raise
- mg.delete("Alice likes Bob", filters)
-
- def test_delete_with_empty_entity_extraction(self, kuzu_graph_memory):
- """If LLM returns no entities, delete() should be a no-op."""
- mg, conn = kuzu_graph_memory
-
- # Add real data
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- filters = {"user_id": "test_user"}
- mg.add("Alice likes Bob", filters)
- assert _edge_count(conn) == 1
-
- # Now delete but LLM returns no entities
- _setup_llm_for_entities(mg, entities=[], relations=[])
- mg.delete("some text", filters)
-
- # Data should still be there
- assert _edge_count(conn) == 1
-
- def test_delete_all_removes_everything_for_user(self, kuzu_graph_memory):
- """delete_all() should remove all nodes/edges for a user (baseline behavior)."""
- mg, conn = kuzu_graph_memory
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- filters = {"user_id": "test_user"}
- mg.add("Alice likes Bob", filters)
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Bob", "entity_type": "person"},
- {"entity": "Charlie", "entity_type": "person"},
- ],
- relations=[
- {"source": "Bob", "destination": "Charlie", "relationship": "knows"},
- ],
- )
- mg.add("Bob knows Charlie", filters)
-
- assert _node_count(conn) >= 3
- assert _edge_count(conn) == 2
-
- mg.delete_all(filters)
-
- assert _node_count(conn) == 0
- assert _edge_count(conn) == 0
-
- def test_add_delete_add_cycle(self, kuzu_graph_memory):
- """Verify that add → delete → re-add works correctly."""
- mg, conn = kuzu_graph_memory
-
- _setup_llm_for_entities(
- mg,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- filters = {"user_id": "test_user"}
-
- # Add
- mg.add("Alice likes Bob", filters)
- assert _edge_count(conn) == 1
-
- # Delete
- mg.delete("Alice likes Bob", filters)
- assert _edge_count(conn) == 0
-
- # Re-add
- mg.add("Alice likes Bob", filters)
- assert _edge_count(conn) == 1
- edges = _get_edges(conn)
- assert ("alice", "likes", "bob") in edges
-
-
-@requires_kuzu
-class TestMemoryDeleteWithGraphE2E:
- """
- End-to-end tests for Memory.delete() with graph enabled.
-
- Uses a real Kuzu database for the graph store and mocks for
- the vector store, LLM, and embedder.
- """
-
- @pytest.fixture
- def memory_with_graph(self):
- """Create a Memory instance with a real Kuzu graph backend."""
- import os
-
- import kuzu
-
- tmpdir = tempfile.mkdtemp()
-
- with (
- patch("mem0.utils.factory.EmbedderFactory.create") as mock_embedder_factory,
- patch("mem0.utils.factory.VectorStoreFactory.create") as mock_vector_factory,
- patch("mem0.utils.factory.LlmFactory.create") as mock_llm_factory,
- patch("mem0.memory.storage.SQLiteManager") as mock_sqlite,
- ):
- _mem_embed_cache = {}
- _mem_embed_counter = [0]
-
- def _mem_deterministic_embed(text, *args, **kwargs):
- text_lower = text.lower().strip()
- if text_lower not in _mem_embed_cache:
- import hashlib
-
- vec = [0.0] * 64
- idx = _mem_embed_counter[0] % 64
- vec[idx] = 1.0
- h = hashlib.sha256(text_lower.encode()).digest()
- for i in range(64):
- vec[i] += float(h[i % len(h)]) / 25500.0
- norm = sum(v * v for v in vec) ** 0.5
- _mem_embed_cache[text_lower] = [v / norm for v in vec]
- _mem_embed_counter[0] += 1
- return _mem_embed_cache[text_lower]
-
- mock_embedder = MagicMock()
- mock_embedder.embed.side_effect = _mem_deterministic_embed
- mock_embedder.config.embedding_dims = 64
- mock_embedder_factory.return_value = mock_embedder
-
- mock_vector_store = MagicMock()
- mock_vector_factory.return_value = mock_vector_store
-
- mock_llm = MagicMock()
- mock_llm_factory.return_value = mock_llm
-
- mock_sqlite.return_value = MagicMock()
-
- from mem0.memory.main import Memory
-
- config = MemoryConfig()
- memory = Memory(config)
-
- # Now wire up a real Kuzu graph
- db_path = os.path.join(tmpdir, "test.kuzu")
- db = kuzu.Database(db_path)
- conn = kuzu.Connection(db)
-
- from mem0.memory.kuzu_memory import MemoryGraph as KuzuMemoryGraph
-
- graph = KuzuMemoryGraph.__new__(KuzuMemoryGraph)
- graph.db = db
- graph.graph = conn
- graph.node_label = ":Entity"
- graph.rel_label = ":CONNECTED_TO"
- graph.kuzu_create_schema()
- graph.embedding_dims = 64
- graph.embedding_model = mock_embedder
- graph.llm = mock_llm
- graph.llm_provider = "openai"
- graph.user_id = None
- graph.threshold = 0.99
- graph.config = MagicMock()
- graph.config.graph_store.custom_prompt = None
-
- memory.graph = graph
- memory.enable_graph = True
-
- yield memory, mock_vector_store, mock_llm, conn
-
- conn.close()
- shutil.rmtree(tmpdir, ignore_errors=True)
-
- def test_memory_delete_triggers_graph_cleanup(self, memory_with_graph):
- """
- Full integration: Memory.delete() should clean up both vector store and graph.
- """
- memory, mock_vs, mock_llm, conn = memory_with_graph
-
- # 1. Manually add entities to the graph (simulating what add() would do)
- _setup_llm_for_memory_graph(
- mock_llm,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- memory.graph.add("Alice likes Bob", {"user_id": "user-1"})
- assert _edge_count(conn) == 1
-
- # 2. Set up mock vector store to return this memory
- mock_vs.get.return_value = MockVectorMemory(
- "mem-1",
- {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"},
- )
-
- # 3. Delete the memory
- result = memory.delete("mem-1")
-
- assert result == {"message": "Memory deleted successfully!"}
-
- # 4. Verify graph was cleaned up
- assert _edge_count(conn) == 0
-
- # 5. Verify vector store was also cleaned up
- mock_vs.delete.assert_called_once_with(vector_id="mem-1")
-
- def test_memory_delete_with_graph_preserves_other_users_data(self, memory_with_graph):
- """Deleting user1's memory should not affect user2's graph data."""
- memory, mock_vs, mock_llm, conn = memory_with_graph
-
- _setup_llm_for_memory_graph(
- mock_llm,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
-
- # Add data for two users
- memory.graph.add("Alice likes Bob", {"user_id": "user-1"})
- memory.graph.add("Alice likes Bob", {"user_id": "user-2"})
- assert _edge_count(conn) == 2
-
- # Delete only user-1's memory
- mock_vs.get.return_value = MockVectorMemory(
- "mem-1",
- {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"},
- )
- memory.delete("mem-1")
-
- # user-2's data should be intact
- assert _edge_count(conn) == 1
- nodes = _get_nodes(conn)
- remaining_user_ids = set(uid for _, uid in nodes)
- assert "user-2" in remaining_user_ids
-
- def test_memory_delete_graph_failure_still_deletes_vector(self, memory_with_graph):
- """If graph cleanup fails, vector store deletion should still proceed."""
- memory, mock_vs, mock_llm, conn = memory_with_graph
-
- # Make LLM raise during entity extraction (graph cleanup will fail)
- mock_llm.generate_response.side_effect = RuntimeError("LLM exploded")
-
- mock_vs.get.return_value = MockVectorMemory(
- "mem-1",
- {"data": "Alice likes Bob", "user_id": "user-1", "hash": "abc"},
- )
-
- result = memory.delete("mem-1")
-
- assert result == {"message": "Memory deleted successfully!"}
- mock_vs.delete.assert_called_once_with(vector_id="mem-1")
-
- def test_memory_delete_all_uses_bulk_not_per_memory(self, memory_with_graph):
- """delete_all() should use delete_all() on graph, not per-memory delete()."""
- memory, mock_vs, mock_llm, conn = memory_with_graph
-
- _setup_llm_for_memory_graph(
- mock_llm,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- memory.graph.add("Alice likes Bob", {"user_id": "user-1"})
- assert _edge_count(conn) == 1
-
- # Set up vector store to return memories for deletion
- mem1 = MockVectorMemory("mem-1", {"data": "Alice likes Bob", "user_id": "user-1"})
- mock_vs.list.return_value = ([mem1], 1)
- mock_vs.get.return_value = mem1
-
- memory.delete_all(user_id="user-1")
-
- # After delete_all, graph should be empty (via graph.delete_all)
- assert _edge_count(conn) == 0
- assert _node_count(conn) == 0
-
- def test_memory_delete_nonexistent_raises_without_graph_side_effects(self, memory_with_graph):
- """Deleting a non-existent memory should raise ValueError without touching graph."""
- memory, mock_vs, mock_llm, conn = memory_with_graph
-
- # Add some graph data that should NOT be affected
- _setup_llm_for_memory_graph(
- mock_llm,
- entities=[
- {"entity": "Alice", "entity_type": "person"},
- {"entity": "Bob", "entity_type": "person"},
- ],
- relations=[
- {"source": "Alice", "destination": "Bob", "relationship": "likes"},
- ],
- )
- memory.graph.add("Alice likes Bob", {"user_id": "user-1"})
- assert _edge_count(conn) == 1
-
- # Memory doesn't exist in vector store
- mock_vs.get.return_value = None
-
- with pytest.raises(ValueError, match="Memory with id non-existent not found"):
- memory.delete("non-existent")
-
- # Graph data should be untouched
- assert _edge_count(conn) == 1
-
-
-def _setup_llm_for_memory_graph(mock_llm, entities, relations):
- """Configure mock LLM for the Memory-level graph operations."""
-
- def generate_response(messages, tools):
- tool_names = []
- for t in tools:
- if isinstance(t, dict):
- fn = t.get("function", t)
- tool_names.append(fn.get("name", ""))
- else:
- tool_names.append(getattr(t, "name", str(t)))
-
- if any("extract_entities" in n for n in tool_names):
- return {
- "tool_calls": [
- {
- "name": "extract_entities",
- "arguments": {"entities": entities},
- }
- ]
- }
- elif any("establish" in n or "relation" in n for n in tool_names):
- return {
- "tool_calls": [
- {
- "name": "establish_nodes_relations",
- "arguments": {"entities": relations},
- }
- ]
- }
- elif any("delete" in n for n in tool_names):
- return {"tool_calls": []}
- return {"tool_calls": []}
-
- mock_llm.generate_response.side_effect = generate_response
diff --git a/tests/test_main.py b/tests/test_main.py
index 2a12ce55e..850ae659f 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -70,7 +70,8 @@ def memory_custom_instance():
@pytest.mark.parametrize("version, enable_graph", [("v1.0", False), ("v1.1", True)])
def test_add(memory_instance, version, enable_graph):
memory_instance.config.version = version
- memory_instance.enable_graph = enable_graph
+ if not enable_graph:
+ memory_instance.graph = None
memory_instance._add_to_vector_store = Mock(return_value=[{"memory": "Test memory", "event": "ADD"}])
memory_instance._add_to_graph = Mock(return_value=[])
@@ -123,14 +124,16 @@ def test_get(memory_instance):
@pytest.mark.parametrize("version, enable_graph", [("v1.0", False), ("v1.1", True)])
def test_search(memory_instance, version, enable_graph):
memory_instance.config.version = version
- memory_instance.enable_graph = enable_graph
+ if not enable_graph:
+ memory_instance.graph = None
mock_memories = [
Mock(id="1", payload={"data": "Memory 1", "user_id": "test_user"}, score=0.9),
Mock(id="2", payload={"data": "Memory 2", "user_id": "test_user"}, score=0.8),
]
memory_instance.vector_store.search = Mock(return_value=mock_memories)
memory_instance.embedding_model.embed = Mock(return_value=[0.1, 0.2, 0.3])
- memory_instance.graph.search = Mock(return_value=[{"relation": "test_relation"}])
+ if memory_instance.graph:
+ memory_instance.graph.search = Mock(return_value=[{"relation": "test_relation"}])
result = memory_instance.search("test query", user_id="test_user")
@@ -162,8 +165,6 @@ def test_search(memory_instance, version, enable_graph):
if enable_graph:
memory_instance.graph.search.assert_called_once_with("test query", {"user_id": "test_user"}, 100)
- else:
- memory_instance.graph.search.assert_not_called()
def test_update(memory_instance):
@@ -224,12 +225,14 @@ def test_delete(memory_instance):
@pytest.mark.parametrize("version, enable_graph", [("v1.0", False), ("v1.1", True)])
def test_delete_all(memory_instance, version, enable_graph):
memory_instance.config.version = version
- memory_instance.enable_graph = enable_graph
+ if not enable_graph:
+ memory_instance.graph = None
mock_memories = [Mock(id="1"), Mock(id="2")]
memory_instance.vector_store.list = Mock(return_value=(mock_memories, None))
memory_instance.vector_store.reset = Mock()
memory_instance._delete_memory = Mock()
- memory_instance.graph.delete_all = Mock()
+ if memory_instance.graph:
+ memory_instance.graph.delete_all = Mock()
result = memory_instance.delete_all(user_id="test_user")
@@ -239,8 +242,6 @@ def test_delete_all(memory_instance, version, enable_graph):
if enable_graph:
memory_instance.graph.delete_all.assert_called_once_with({"user_id": "test_user"})
- else:
- memory_instance.graph.delete_all.assert_not_called()
assert result["message"] == "Memories deleted successfully!"
@@ -262,12 +263,14 @@ def test_delete_all(memory_instance, version, enable_graph):
)
def test_get_all(memory_instance, version, enable_graph, expected_result):
memory_instance.config.version = version
- memory_instance.enable_graph = enable_graph
+ if not enable_graph:
+ memory_instance.graph = None
mock_memories = [Mock(id="1", payload={"data": "Memory 1", "user_id": "test_user"})]
memory_instance.vector_store.list = Mock(return_value=(mock_memories, None))
- memory_instance.graph.get_all = Mock(
- return_value=[{"source": "entity1", "relationship": "rel", "target": "entity2"}]
- )
+ if memory_instance.graph:
+ memory_instance.graph.get_all = Mock(
+ return_value=[{"source": "entity1", "relationship": "rel", "target": "entity2"}]
+ )
result = memory_instance.get_all(user_id="test_user")
@@ -290,8 +293,6 @@ def test_get_all(memory_instance, version, enable_graph, expected_result):
if enable_graph:
memory_instance.graph.get_all.assert_called_once_with({"user_id": "test_user"}, 100)
- else:
- memory_instance.graph.get_all.assert_not_called()
def test_custom_prompts(memory_custom_instance):
diff --git a/tests/test_memory.py b/tests/test_memory.py
index 0b0c6e251..adee7bba7 100644
--- a/tests/test_memory.py
+++ b/tests/test_memory.py
@@ -776,8 +776,7 @@ def test_reset_calls_graph_reset_when_graph_enabled(mock_sqlite, mock_llm_factor
config = MemoryConfig()
memory = Memory(config)
- # Simulate graph being enabled
- memory.enable_graph = True
+ # Simulate graph being enabled by setting graph instance
mock_graph = MagicMock()
memory.graph = mock_graph
@@ -801,13 +800,13 @@ def test_reset_skips_graph_when_graph_disabled(mock_sqlite, mock_llm_factory, mo
config = MemoryConfig()
memory = Memory(config)
- # Graph is disabled by default
- memory.enable_graph = False
+ # Graph is disabled by default (graph is None)
+ memory.graph = None
memory.reset()
- # graph attribute may not even exist, but reset should not fail
- assert not hasattr(memory, 'graph') or not memory.enable_graph
+ # graph should remain None after reset
+ assert memory.graph is None
@patch('mem0.utils.factory.EmbedderFactory.create')
@@ -825,7 +824,6 @@ def test_reset_continues_if_graph_reset_fails(mock_sqlite, mock_llm_factory, moc
config = MemoryConfig()
memory = Memory(config)
- memory.enable_graph = True
mock_graph = MagicMock()
mock_graph.reset.side_effect = Exception("Neo4j connection failed")
memory.graph = mock_graph