feat(sdk, docs): expose agent_custom_instructions for agent-scoped extraction (#6809)
This commit is contained in:
@@ -95,6 +95,12 @@ client.project.update(
|
|||||||
custom_instructions="..."
|
custom_instructions="..."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Separate extraction instructions for agent-scoped memories
|
||||||
|
# (see /platform/features/custom-instructions)
|
||||||
|
client.project.update(
|
||||||
|
agent_custom_instructions="..."
|
||||||
|
)
|
||||||
|
|
||||||
# Use the input language for memory storage and retrieval
|
# Use the input language for memory storage and retrieval
|
||||||
client.project.update(multilingual=True)
|
client.project.update(multilingual=True)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ mode: "wide"
|
|||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Python">
|
<Tab title="Python">
|
||||||
|
|
||||||
|
<Update label="2026-08-05" description="v2.0.17">
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
- **Client:** Add `agent_custom_instructions` to `project.update()`/`update_project()` (sync and async) and to the `ProjectUpdateOptions` and `AddMemoryOptions` typed models. It sets a second extraction instruction set that applies only to agent-scoped memories: an add passing `agent_id` without `user_id` uses it, one passing both splits by attribution, and while it is unset `custom_instructions` continues to apply to every memory ([#6809](https://github.com/mem0ai/mem0/pull/6809))
|
||||||
|
|
||||||
|
</Update>
|
||||||
|
|
||||||
<Update label="2026-08-04" description="v2.0.16">
|
<Update label="2026-08-04" description="v2.0.16">
|
||||||
|
|
||||||
**New Features:**
|
**New Features:**
|
||||||
@@ -1189,6 +1196,13 @@ See the [OSS v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-
|
|||||||
|
|
||||||
<Tab title="TypeScript">
|
<Tab title="TypeScript">
|
||||||
|
|
||||||
|
<Update label="2026-08-05" description="v3.1.5">
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
- **Client:** Add `agentCustomInstructions` to `PromptUpdatePayload`, `AddMemoryOptions`, and `ProjectResponse`. It sets a second extraction instruction set that applies only to agent-scoped memories: an add passing `agentId` without `userId` uses it, one passing both splits by attribution, and while it is unset `customInstructions` continues to apply to every memory ([#6809](https://github.com/mem0ai/mem0/pull/6809))
|
||||||
|
|
||||||
|
</Update>
|
||||||
|
|
||||||
<Update label="2026-08-04" description="v3.1.4">
|
<Update label="2026-08-04" description="v3.1.4">
|
||||||
|
|
||||||
**New Features:**
|
**New Features:**
|
||||||
|
|||||||
@@ -2784,6 +2784,10 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Project-level instructions that guide extraction for this call."
|
"description": "Project-level instructions that guide extraction for this call."
|
||||||
},
|
},
|
||||||
|
"agent_custom_instructions": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Extraction instructions for agent-scoped memories, overriding the project-level setting for this call. Applied when `agent_id` is sent without `user_id`; when both are sent it governs the assistant-attributed memories while `custom_instructions` governs the rest."
|
||||||
|
},
|
||||||
"custom_categories": {
|
"custom_categories": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Category catalog for this call. Replaces the project-level list rather than merging with it. Omit to fall back to the project list, then the default catalog.",
|
"description": "Category catalog for this call. Replaces the project-level list rather than merging with it. Omit to fall back to the project list, then the default catalog.",
|
||||||
@@ -5805,6 +5809,11 @@
|
|||||||
},
|
},
|
||||||
"description": "Custom instructions for memory processing in this project"
|
"description": "Custom instructions for memory processing in this project"
|
||||||
},
|
},
|
||||||
|
"agent_custom_instructions": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Extraction instructions for agent-scoped memories. Falls back to `custom_instructions` when unset. Send an empty string to clear it."
|
||||||
|
},
|
||||||
"custom_categories": {
|
"custom_categories": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -7431,6 +7440,12 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
"agent_custom_instructions": {
|
||||||
|
"description": "Extraction instructions that apply only to agent-scoped memories. Used when `agent_id` is sent without `user_id`; when both are sent it governs the assistant-attributed memories while `custom_instructions` governs the rest. Falls back to `custom_instructions` when unset.",
|
||||||
|
"title": "Agent custom instructions",
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
"immutable": {
|
"immutable": {
|
||||||
"description": "Whether the memory is immutable.",
|
"description": "Whether the memory is immutable.",
|
||||||
"title": "Immutable",
|
"title": "Immutable",
|
||||||
|
|||||||
@@ -95,6 +95,100 @@ Exclude:
|
|||||||
- [Irrelevant information]
|
- [Irrelevant information]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Agent Custom Instructions
|
||||||
|
|
||||||
|
`custom_instructions` applies to every memory your project extracts, no matter whose it is. But what is worth remembering about an agent is rarely what is worth remembering about a user: an agent's useful memories are things like which tools fail, which retry strategies work, and how a given environment behaves, not personal preferences.
|
||||||
|
|
||||||
|
`agent_custom_instructions` is an optional second set of extraction rules that applies only to agent-scoped memories.
|
||||||
|
|
||||||
|
Available from Python SDK `v2.0.17` and TypeScript SDK `v3.1.5`. Upgrade first if you are on an earlier release.
|
||||||
|
|
||||||
|
Set it on the project alongside `custom_instructions`:
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
```python Python
|
||||||
|
client.project.update(
|
||||||
|
custom_instructions="Extract the user's preferences, goals, and constraints.",
|
||||||
|
agent_custom_instructions=(
|
||||||
|
"Extract operational lessons for the agent:\n"
|
||||||
|
"- Tools that failed and the error returned\n"
|
||||||
|
"- Retry or fallback strategies that worked\n"
|
||||||
|
"- Environment quirks worth recalling on the next run\n\n"
|
||||||
|
"Exclude: user preferences, personal details."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript JavaScript
|
||||||
|
await client.updateProject({
|
||||||
|
customInstructions: "Extract the user's preferences, goals, and constraints.",
|
||||||
|
agentCustomInstructions: `Extract operational lessons for the agent:
|
||||||
|
- Tools that failed and the error returned
|
||||||
|
- Retry or fallback strategies that worked
|
||||||
|
- Environment quirks worth recalling on the next run
|
||||||
|
|
||||||
|
Exclude: user preferences, personal details.`,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
### Which instructions apply
|
||||||
|
|
||||||
|
Which set governs an `add()` call depends on the entity IDs you pass with it:
|
||||||
|
|
||||||
|
| The add call passes | Instructions applied |
|
||||||
|
|---------------------|----------------------|
|
||||||
|
| `user_id` only | `custom_instructions` |
|
||||||
|
| `agent_id` only | `agent_custom_instructions` |
|
||||||
|
| `user_id` **and** `agent_id` | `agent_custom_instructions` govern the memories attributed to the assistant; `custom_instructions` govern the rest |
|
||||||
|
|
||||||
|
`agent_custom_instructions` is unset by default. While it is unset, `custom_instructions` applies to every memory, so projects that don't set it behave exactly as they did before.
|
||||||
|
|
||||||
|
### Overriding for a single call
|
||||||
|
|
||||||
|
Both fields are also accepted per request, overriding the project setting for that `add()` only:
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
```python Python
|
||||||
|
client.add(
|
||||||
|
messages,
|
||||||
|
filters={"agent_id": "support-agent"},
|
||||||
|
agent_custom_instructions="Only remember which tools errored and why.",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript JavaScript
|
||||||
|
await client.add(messages, {
|
||||||
|
agentId: "support-agent",
|
||||||
|
agentCustomInstructions: "Only remember which tools errored and why.",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
### Reading and clearing
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
```python Python
|
||||||
|
# Read the current value
|
||||||
|
response = client.project.get(fields=["agent_custom_instructions"])
|
||||||
|
print(response["agent_custom_instructions"])
|
||||||
|
|
||||||
|
# Clear it; agent memories fall back to custom_instructions
|
||||||
|
client.project.update(agent_custom_instructions="")
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript JavaScript
|
||||||
|
// Read the current value
|
||||||
|
const response = await client.getProject({
|
||||||
|
fields: ["agentCustomInstructions"],
|
||||||
|
});
|
||||||
|
console.log(response.agentCustomInstructions);
|
||||||
|
|
||||||
|
// Clear it; agent memories fall back to customInstructions
|
||||||
|
await client.updateProject({ agentCustomInstructions: "" });
|
||||||
|
```
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
## Real-World Examples
|
## Real-World Examples
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mem0ai",
|
"name": "mem0ai",
|
||||||
"version": "3.1.4",
|
"version": "3.1.5",
|
||||||
"description": "The Memory Layer For Your AI Apps",
|
"description": "The Memory Layer For Your AI Apps",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface AddMemoryOptions extends EntityOptions {
|
|||||||
infer?: boolean;
|
infer?: boolean;
|
||||||
customCategories?: custom_categories[];
|
customCategories?: custom_categories[];
|
||||||
customInstructions?: string;
|
customInstructions?: string;
|
||||||
|
agentCustomInstructions?: string;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
expirationDate?: string;
|
expirationDate?: string;
|
||||||
structuredDataSchema?: Record<string, any>;
|
structuredDataSchema?: Record<string, any>;
|
||||||
@@ -60,6 +61,7 @@ export interface ProjectOptions {
|
|||||||
|
|
||||||
export interface PromptUpdatePayload {
|
export interface PromptUpdatePayload {
|
||||||
customInstructions?: string;
|
customInstructions?: string;
|
||||||
|
agentCustomInstructions?: string;
|
||||||
customCategories?: custom_categories[];
|
customCategories?: custom_categories[];
|
||||||
version?: string;
|
version?: string;
|
||||||
memoryDepth?: string | null;
|
memoryDepth?: string | null;
|
||||||
@@ -174,6 +176,7 @@ export interface PaginatedMemories {
|
|||||||
|
|
||||||
export interface ProjectResponse {
|
export interface ProjectResponse {
|
||||||
customInstructions?: string;
|
customInstructions?: string;
|
||||||
|
agentCustomInstructions?: string;
|
||||||
// The API returns category objects (`[{ "<name>": "<description>" }]`),
|
// The API returns category objects (`[{ "<name>": "<description>" }]`),
|
||||||
// not bare strings (see issue #5738).
|
// not bare strings (see issue #5738).
|
||||||
customCategories?: custom_categories[];
|
customCategories?: custom_categories[];
|
||||||
|
|||||||
@@ -74,6 +74,35 @@ describe("MemoryClient - add()", () => {
|
|||||||
expect(getFetchBody(call!).expiration_date).toBe("2030-01-31");
|
expect(getFetchBody(call!).expiration_date).toBe("2030-01-31");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("serializes agentCustomInstructions as agent_custom_instructions", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] });
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.add([{ role: "user", content: "test" }], {
|
||||||
|
agentId: "a1",
|
||||||
|
agentCustomInstructions: "Remember tool failures",
|
||||||
|
});
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/v3/memories/add/", "POST");
|
||||||
|
expect(getFetchBody(call!).agent_custom_instructions).toBe(
|
||||||
|
"Remember tool failures",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("omits agent_custom_instructions when it is not passed", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] });
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.add([{ role: "user", content: "test" }], { userId: "u1" });
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/v3/memories/add/", "POST");
|
||||||
|
expect(getFetchBody(call!)).not.toHaveProperty("agent_custom_instructions");
|
||||||
|
});
|
||||||
|
|
||||||
test("throws an error when given an empty messages array", async () => {
|
test("throws an error when given an empty messages array", async () => {
|
||||||
setupMockFetch();
|
setupMockFetch();
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,84 @@ describe("MemoryClient - updateProject()", () => {
|
|||||||
"Updated instructions",
|
"Updated instructions",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("camelCases agentCustomInstructions into agent_custom_instructions", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/api/v1/orgs/organizations/", {
|
||||||
|
status: 200,
|
||||||
|
body: { message: "Updated" },
|
||||||
|
});
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.ping();
|
||||||
|
await client.updateProject({
|
||||||
|
agentCustomInstructions: "Remember tool failures",
|
||||||
|
});
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/api/v1/orgs/organizations/", "PATCH");
|
||||||
|
expect(getFetchBody(call!).agent_custom_instructions).toBe(
|
||||||
|
"Remember tool failures",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sends both instruction sets in one PATCH", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/api/v1/orgs/organizations/", {
|
||||||
|
status: 200,
|
||||||
|
body: { message: "Updated" },
|
||||||
|
});
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.ping();
|
||||||
|
await client.updateProject({
|
||||||
|
customInstructions: "Remember user preferences",
|
||||||
|
agentCustomInstructions: "Remember tool failures",
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = getFetchBody(
|
||||||
|
findFetchCall(mock, "/api/v1/orgs/organizations/", "PATCH")!,
|
||||||
|
);
|
||||||
|
expect(body.custom_instructions).toBe("Remember user preferences");
|
||||||
|
expect(body.agent_custom_instructions).toBe("Remember tool failures");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("an empty string round-trips, so the field can be cleared", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/api/v1/orgs/organizations/", {
|
||||||
|
status: 200,
|
||||||
|
body: { message: "Updated" },
|
||||||
|
});
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.ping();
|
||||||
|
await client.updateProject({ agentCustomInstructions: "" });
|
||||||
|
|
||||||
|
const body = getFetchBody(
|
||||||
|
findFetchCall(mock, "/api/v1/orgs/organizations/", "PATCH")!,
|
||||||
|
);
|
||||||
|
expect(body).toHaveProperty("agent_custom_instructions", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("omits agent_custom_instructions when it is not passed", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/api/v1/orgs/organizations/", {
|
||||||
|
status: 200,
|
||||||
|
body: { message: "Updated" },
|
||||||
|
});
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.ping();
|
||||||
|
await client.updateProject({ customInstructions: "Be helpful" });
|
||||||
|
|
||||||
|
const body = getFetchBody(
|
||||||
|
findFetchCall(mock, "/api/v1/orgs/organizations/", "PATCH")!,
|
||||||
|
);
|
||||||
|
expect(body).not.toHaveProperty("agent_custom_instructions");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── feedback() ─────────────────────────────────────────
|
// ─── feedback() ─────────────────────────────────────────
|
||||||
|
|||||||
@@ -734,6 +734,7 @@ class MemoryClient:
|
|||||||
memory_depth: Optional[str] = None,
|
memory_depth: Optional[str] = None,
|
||||||
usecase_setting: Optional[str] = None,
|
usecase_setting: Optional[str] = None,
|
||||||
multilingual: Optional[bool] = None,
|
multilingual: Optional[bool] = None,
|
||||||
|
agent_custom_instructions: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Update the project settings.
|
"""Update the project settings.
|
||||||
|
|
||||||
@@ -744,6 +745,7 @@ class MemoryClient:
|
|||||||
memory_depth: Memory depth for the project.
|
memory_depth: Memory depth for the project.
|
||||||
usecase_setting: Usecase setting for the project.
|
usecase_setting: Usecase setting for the project.
|
||||||
multilingual: Whether to use the input language for memory storage and retrieval.
|
multilingual: Whether to use the input language for memory storage and retrieval.
|
||||||
|
agent_custom_instructions: New extraction instructions for agent-scoped memories.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing the API response.
|
Dictionary containing the API response.
|
||||||
@@ -768,6 +770,7 @@ class MemoryClient:
|
|||||||
"memory_depth": memory_depth,
|
"memory_depth": memory_depth,
|
||||||
"usecase_setting": usecase_setting,
|
"usecase_setting": usecase_setting,
|
||||||
"multilingual": multilingual,
|
"multilingual": multilingual,
|
||||||
|
"agent_custom_instructions": agent_custom_instructions,
|
||||||
}.items()
|
}.items()
|
||||||
if v is not None
|
if v is not None
|
||||||
},
|
},
|
||||||
@@ -1636,6 +1639,7 @@ class AsyncMemoryClient:
|
|||||||
memory_depth: Optional[str] = None,
|
memory_depth: Optional[str] = None,
|
||||||
usecase_setting: Optional[str] = None,
|
usecase_setting: Optional[str] = None,
|
||||||
multilingual: Optional[bool] = None,
|
multilingual: Optional[bool] = None,
|
||||||
|
agent_custom_instructions: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Update the project settings.
|
"""Update the project settings.
|
||||||
|
|
||||||
@@ -1646,6 +1650,7 @@ class AsyncMemoryClient:
|
|||||||
memory_depth: Memory depth for the project.
|
memory_depth: Memory depth for the project.
|
||||||
usecase_setting: Usecase setting for the project.
|
usecase_setting: Usecase setting for the project.
|
||||||
multilingual: Whether to use the input language for memory storage and retrieval.
|
multilingual: Whether to use the input language for memory storage and retrieval.
|
||||||
|
agent_custom_instructions: New extraction instructions for agent-scoped memories.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing the API response.
|
Dictionary containing the API response.
|
||||||
@@ -1670,6 +1675,7 @@ class AsyncMemoryClient:
|
|||||||
"memory_depth": memory_depth,
|
"memory_depth": memory_depth,
|
||||||
"usecase_setting": usecase_setting,
|
"usecase_setting": usecase_setting,
|
||||||
"multilingual": multilingual,
|
"multilingual": multilingual,
|
||||||
|
"agent_custom_instructions": agent_custom_instructions,
|
||||||
}.items()
|
}.items()
|
||||||
if v is not None
|
if v is not None
|
||||||
},
|
},
|
||||||
|
|||||||
+28
-4
@@ -177,6 +177,7 @@ class BaseProject(ABC):
|
|||||||
self,
|
self,
|
||||||
custom_instructions: Optional[str] = None,
|
custom_instructions: Optional[str] = None,
|
||||||
custom_categories: Optional[List[str]] = None,
|
custom_categories: Optional[List[str]] = None,
|
||||||
|
agent_custom_instructions: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Update project settings.
|
Update project settings.
|
||||||
@@ -184,6 +185,7 @@ class BaseProject(ABC):
|
|||||||
Args:
|
Args:
|
||||||
custom_instructions: New instructions for the project
|
custom_instructions: New instructions for the project
|
||||||
custom_categories: New categories for the project
|
custom_categories: New categories for the project
|
||||||
|
agent_custom_instructions: New extraction instructions for agent-scoped memories
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing the API response.
|
Dictionary containing the API response.
|
||||||
@@ -396,6 +398,7 @@ class Project(BaseProject):
|
|||||||
custom_categories: Optional[List[str]] = None,
|
custom_categories: Optional[List[str]] = None,
|
||||||
multilingual: Optional[bool] = None,
|
multilingual: Optional[bool] = None,
|
||||||
decay: Optional[bool] = None,
|
decay: Optional[bool] = None,
|
||||||
|
agent_custom_instructions: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Update project settings.
|
Update project settings.
|
||||||
@@ -407,6 +410,7 @@ class Project(BaseProject):
|
|||||||
decay: Toggle Memory Decay for this project. When True, search-time
|
decay: Toggle Memory Decay for this project. When True, search-time
|
||||||
ranking boosts recently-used memories and gently dampens stale ones; when
|
ranking boosts recently-used memories and gently dampens stale ones; when
|
||||||
False, ranking is restored to the pre-decay behaviour. Off by default.
|
False, ranking is restored to the pre-decay behaviour. Off by default.
|
||||||
|
agent_custom_instructions: New extraction instructions for agent-scoped memories
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing the API response.
|
Dictionary containing the API response.
|
||||||
@@ -418,10 +422,17 @@ class Project(BaseProject):
|
|||||||
NetworkError: If network connectivity issues occur.
|
NetworkError: If network connectivity issues occur.
|
||||||
ValueError: If org_id or project_id are not set.
|
ValueError: If org_id or project_id are not set.
|
||||||
"""
|
"""
|
||||||
if custom_instructions is None and custom_categories is None and multilingual is None and decay is None:
|
if (
|
||||||
|
custom_instructions is None
|
||||||
|
and custom_categories is None
|
||||||
|
and multilingual is None
|
||||||
|
and decay is None
|
||||||
|
and agent_custom_instructions is None
|
||||||
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"At least one parameter must be provided for update: "
|
"At least one parameter must be provided for update: "
|
||||||
"custom_instructions, custom_categories, multilingual, decay"
|
"custom_instructions, custom_categories, multilingual, decay, "
|
||||||
|
"agent_custom_instructions"
|
||||||
)
|
)
|
||||||
|
|
||||||
payload = self._prepare_params(
|
payload = self._prepare_params(
|
||||||
@@ -430,6 +441,7 @@ class Project(BaseProject):
|
|||||||
"custom_categories": custom_categories,
|
"custom_categories": custom_categories,
|
||||||
"multilingual": multilingual,
|
"multilingual": multilingual,
|
||||||
"decay": decay,
|
"decay": decay,
|
||||||
|
"agent_custom_instructions": agent_custom_instructions,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = self._client.patch(
|
response = self._client.patch(
|
||||||
@@ -445,6 +457,7 @@ class Project(BaseProject):
|
|||||||
"custom_categories": custom_categories,
|
"custom_categories": custom_categories,
|
||||||
"multilingual": multilingual,
|
"multilingual": multilingual,
|
||||||
"decay": decay,
|
"decay": decay,
|
||||||
|
"agent_custom_instructions": agent_custom_instructions,
|
||||||
"sync_type": "sync",
|
"sync_type": "sync",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -709,6 +722,7 @@ class AsyncProject(BaseProject):
|
|||||||
custom_categories: Optional[List[str]] = None,
|
custom_categories: Optional[List[str]] = None,
|
||||||
multilingual: Optional[bool] = None,
|
multilingual: Optional[bool] = None,
|
||||||
decay: Optional[bool] = None,
|
decay: Optional[bool] = None,
|
||||||
|
agent_custom_instructions: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Update project settings.
|
Update project settings.
|
||||||
@@ -720,6 +734,7 @@ class AsyncProject(BaseProject):
|
|||||||
decay: Toggle Memory Decay for this project. When True, search-time
|
decay: Toggle Memory Decay for this project. When True, search-time
|
||||||
ranking boosts recently-used memories and gently dampens stale ones; when
|
ranking boosts recently-used memories and gently dampens stale ones; when
|
||||||
False, ranking is restored to the pre-decay behaviour. Off by default.
|
False, ranking is restored to the pre-decay behaviour. Off by default.
|
||||||
|
agent_custom_instructions: New extraction instructions for agent-scoped memories
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing the API response.
|
Dictionary containing the API response.
|
||||||
@@ -731,10 +746,17 @@ class AsyncProject(BaseProject):
|
|||||||
NetworkError: If network connectivity issues occur.
|
NetworkError: If network connectivity issues occur.
|
||||||
ValueError: If org_id or project_id are not set.
|
ValueError: If org_id or project_id are not set.
|
||||||
"""
|
"""
|
||||||
if custom_instructions is None and custom_categories is None and multilingual is None and decay is None:
|
if (
|
||||||
|
custom_instructions is None
|
||||||
|
and custom_categories is None
|
||||||
|
and multilingual is None
|
||||||
|
and decay is None
|
||||||
|
and agent_custom_instructions is None
|
||||||
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"At least one parameter must be provided for update: "
|
"At least one parameter must be provided for update: "
|
||||||
"custom_instructions, custom_categories, multilingual, decay"
|
"custom_instructions, custom_categories, multilingual, decay, "
|
||||||
|
"agent_custom_instructions"
|
||||||
)
|
)
|
||||||
|
|
||||||
payload = self._prepare_params(
|
payload = self._prepare_params(
|
||||||
@@ -743,6 +765,7 @@ class AsyncProject(BaseProject):
|
|||||||
"custom_categories": custom_categories,
|
"custom_categories": custom_categories,
|
||||||
"multilingual": multilingual,
|
"multilingual": multilingual,
|
||||||
"decay": decay,
|
"decay": decay,
|
||||||
|
"agent_custom_instructions": agent_custom_instructions,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
response = await self._client.patch(
|
response = await self._client.patch(
|
||||||
@@ -758,6 +781,7 @@ class AsyncProject(BaseProject):
|
|||||||
"custom_categories": custom_categories,
|
"custom_categories": custom_categories,
|
||||||
"multilingual": multilingual,
|
"multilingual": multilingual,
|
||||||
"decay": decay,
|
"decay": decay,
|
||||||
|
"agent_custom_instructions": agent_custom_instructions,
|
||||||
"sync_type": "async",
|
"sync_type": "async",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class AddMemoryOptions(BaseModel):
|
|||||||
default=None, description="Custom categories for memory classification"
|
default=None, description="Custom categories for memory classification"
|
||||||
)
|
)
|
||||||
custom_instructions: Optional[str] = Field(default=None, description="Custom instructions for fact extraction")
|
custom_instructions: Optional[str] = Field(default=None, description="Custom instructions for fact extraction")
|
||||||
|
agent_custom_instructions: Optional[str] = Field(
|
||||||
|
default=None, description="Custom instructions for fact extraction from agent-scoped memories"
|
||||||
|
)
|
||||||
timestamp: Optional[int] = Field(default=None, description="Unix timestamp for the memory")
|
timestamp: Optional[int] = Field(default=None, description="Unix timestamp for the memory")
|
||||||
expiration_date: Optional[str] = Field(default=None, description="Expiration date in YYYY-MM-DD format")
|
expiration_date: Optional[str] = Field(default=None, description="Expiration date in YYYY-MM-DD format")
|
||||||
structured_data_schema: Optional[Dict[str, Any]] = Field(
|
structured_data_schema: Optional[Dict[str, Any]] = Field(
|
||||||
@@ -107,6 +110,9 @@ class ProjectUpdateOptions(BaseModel):
|
|||||||
"""Options for project update operations."""
|
"""Options for project update operations."""
|
||||||
|
|
||||||
custom_instructions: Optional[str] = Field(default=None, description="Custom instructions for fact extraction")
|
custom_instructions: Optional[str] = Field(default=None, description="Custom instructions for fact extraction")
|
||||||
|
agent_custom_instructions: Optional[str] = Field(
|
||||||
|
default=None, description="Custom instructions for fact extraction from agent-scoped memories"
|
||||||
|
)
|
||||||
custom_categories: Optional[List[Dict[str, Any]]] = Field(
|
custom_categories: Optional[List[Dict[str, Any]]] = Field(
|
||||||
default=None, description="Custom categories for classification"
|
default=None, description="Custom categories for classification"
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mem0ai"
|
name = "mem0ai"
|
||||||
version = "2.0.16"
|
version = "2.0.17"
|
||||||
description = "Long-term memory for AI Agents"
|
description = "Long-term memory for AI Agents"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Mem0", email = "support@mem0.ai" }
|
{ name = "Mem0", email = "support@mem0.ai" }
|
||||||
|
|||||||
@@ -440,3 +440,51 @@ class TestValidateApiKeyHttpError:
|
|||||||
|
|
||||||
assert not isinstance(exc_info.value, requests.exceptions.JSONDecodeError)
|
assert not isinstance(exc_info.value, requests.exceptions.JSONDecodeError)
|
||||||
assert "Error:" in str(exc_info.value)
|
assert "Error:" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddAgentCustomInstructions:
|
||||||
|
"""Per-request override of the project-level setting, forwarded to the add payload."""
|
||||||
|
|
||||||
|
def _mock_add(self, client):
|
||||||
|
response = MagicMock()
|
||||||
|
response.json.return_value = {"results": []}
|
||||||
|
response.raise_for_status.return_value = None
|
||||||
|
client.client.post.return_value = response
|
||||||
|
return response
|
||||||
|
|
||||||
|
def test_kwarg_reaches_the_add_payload(self, mock_memory_client):
|
||||||
|
self._mock_add(mock_memory_client)
|
||||||
|
|
||||||
|
mock_memory_client.add(
|
||||||
|
"hello",
|
||||||
|
filters={"agent_id": "a1"},
|
||||||
|
agent_custom_instructions="remember tool failures",
|
||||||
|
)
|
||||||
|
|
||||||
|
_, kwargs = mock_memory_client.client.post.call_args
|
||||||
|
assert kwargs["json"]["agent_custom_instructions"] == "remember tool failures"
|
||||||
|
|
||||||
|
def test_typed_option_reaches_the_add_payload(self, mock_memory_client):
|
||||||
|
from mem0.client.types import AddMemoryOptions
|
||||||
|
|
||||||
|
self._mock_add(mock_memory_client)
|
||||||
|
|
||||||
|
mock_memory_client.add(
|
||||||
|
"hello",
|
||||||
|
AddMemoryOptions(
|
||||||
|
filters={"agent_id": "a1"},
|
||||||
|
agent_custom_instructions="remember tool failures",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
_, kwargs = mock_memory_client.client.post.call_args
|
||||||
|
assert kwargs["json"]["agent_custom_instructions"] == "remember tool failures"
|
||||||
|
|
||||||
|
def test_absent_when_not_passed(self, mock_memory_client):
|
||||||
|
"""Callers that don't use the feature send an unchanged payload."""
|
||||||
|
self._mock_add(mock_memory_client)
|
||||||
|
|
||||||
|
mock_memory_client.add("hello", filters={"user_id": "u1"})
|
||||||
|
|
||||||
|
_, kwargs = mock_memory_client.client.post.call_args
|
||||||
|
assert "agent_custom_instructions" not in kwargs["json"]
|
||||||
|
|||||||
+46
-2
@@ -3,8 +3,8 @@ parameter-passthrough surface.
|
|||||||
|
|
||||||
Verifies the kwarg → JSON payload mapping for every supported field
|
Verifies the kwarg → JSON payload mapping for every supported field
|
||||||
(``custom_instructions``, ``custom_categories``, ``multilingual``,
|
(``custom_instructions``, ``custom_categories``, ``multilingual``,
|
||||||
``decay``), the ValueError when no field is provided, and the
|
``decay``, ``agent_custom_instructions``), the ValueError when no field
|
||||||
URL/method shape. The HTTP layer is mocked.
|
is provided, and the URL/method shape. The HTTP layer is mocked.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
@@ -84,6 +84,50 @@ class TestProjectUpdateDecay:
|
|||||||
assert args[0] == "/api/v1/orgs/organizations/org1/projects/proj1/"
|
assert args[0] == "/api/v1/orgs/organizations/org1/projects/proj1/"
|
||||||
|
|
||||||
|
|
||||||
|
class TestProjectUpdateAgentCustomInstructions:
|
||||||
|
def test_agent_custom_instructions_sent_in_payload(self, project):
|
||||||
|
proj, http = project
|
||||||
|
proj.update(agent_custom_instructions="remember tool failures")
|
||||||
|
assert _patch_payload(http) == {"agent_custom_instructions": "remember tool failures"}
|
||||||
|
|
||||||
|
def test_agent_custom_instructions_alone_satisfies_the_guard(self, project):
|
||||||
|
"""It is a standalone field, so setting only it must not raise."""
|
||||||
|
proj, http = project
|
||||||
|
proj.update(agent_custom_instructions="remember tool failures")
|
||||||
|
assert http.patch.called
|
||||||
|
|
||||||
|
def test_empty_string_round_trips_to_clear_the_field(self, project):
|
||||||
|
"""An empty string must survive the ``is not None`` filter, not be dropped as falsy."""
|
||||||
|
proj, http = project
|
||||||
|
proj.update(agent_custom_instructions="")
|
||||||
|
assert _patch_payload(http) == {"agent_custom_instructions": ""}
|
||||||
|
|
||||||
|
def test_combined_with_custom_instructions(self, project):
|
||||||
|
"""Both sets in one PATCH: the split-instruction configuration."""
|
||||||
|
proj, http = project
|
||||||
|
proj.update(
|
||||||
|
custom_instructions="remember user preferences",
|
||||||
|
agent_custom_instructions="remember tool failures",
|
||||||
|
)
|
||||||
|
assert _patch_payload(http) == {
|
||||||
|
"custom_instructions": "remember user preferences",
|
||||||
|
"agent_custom_instructions": "remember tool failures",
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_omitted_when_none(self, project):
|
||||||
|
"""Callers that don't pass it must send an unchanged payload."""
|
||||||
|
proj, http = project
|
||||||
|
proj.update(custom_instructions="be concise")
|
||||||
|
payload = _patch_payload(http)
|
||||||
|
assert payload == {"custom_instructions": "be concise"}
|
||||||
|
assert "agent_custom_instructions" not in payload
|
||||||
|
|
||||||
|
def test_no_args_raises_with_agent_instructions_in_message(self, project):
|
||||||
|
proj, _ = project
|
||||||
|
with pytest.raises(ValueError, match=r"agent_custom_instructions"):
|
||||||
|
proj.update()
|
||||||
|
|
||||||
|
|
||||||
class TestProjectUpdateBackwardsCompat:
|
class TestProjectUpdateBackwardsCompat:
|
||||||
def test_multilingual_only_still_works(self, project):
|
def test_multilingual_only_still_works(self, project):
|
||||||
"""Pre-decay callers (multilingual only) keep working unchanged."""
|
"""Pre-decay callers (multilingual only) keep working unchanged."""
|
||||||
|
|||||||
Reference in New Issue
Block a user