From 7f0d766c096bc788c3782459fb3afe816ceffcd7 Mon Sep 17 00:00:00 2001 From: Dev Khant Date: Sat, 11 Jan 2025 13:38:20 +0530 Subject: [PATCH] Add support: Custom instruction/categories for projects (#2134) --- .../get-instructions-and-categories.mdx | 4 + .../update-instructions-and-categories.mdx | 6 + docs/features/custom-categories.mdx | 50 +- docs/features/custom-instructions.mdx | 59 ++ docs/mint.json | 4 +- docs/openapi.json | 569 ++++++++++++------ mem0/client/main.py | 157 +++++ 7 files changed, 658 insertions(+), 191 deletions(-) create mode 100644 docs/api-reference/project/get-instructions-and-categories.mdx create mode 100644 docs/api-reference/project/update-instructions-and-categories.mdx create mode 100644 docs/features/custom-instructions.mdx diff --git a/docs/api-reference/project/get-instructions-and-categories.mdx b/docs/api-reference/project/get-instructions-and-categories.mdx new file mode 100644 index 0000000000..94d1fd1199 --- /dev/null +++ b/docs/api-reference/project/get-instructions-and-categories.mdx @@ -0,0 +1,4 @@ +--- +title: 'Get Custom Instructions and Categories' +openapi: get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/ +--- diff --git a/docs/api-reference/project/update-instructions-and-categories.mdx b/docs/api-reference/project/update-instructions-and-categories.mdx new file mode 100644 index 0000000000..c801db6db2 --- /dev/null +++ b/docs/api-reference/project/update-instructions-and-categories.mdx @@ -0,0 +1,6 @@ +--- +title: 'Update Custom Instructions and Categories' +openapi: post /api/v1/orgs/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/ +--- + +Please refer to the [how to use custom instructions/categories](/features/custom-instructions) for more information. diff --git a/docs/features/custom-categories.mdx b/docs/features/custom-categories.mdx index 28ebd71c1f..1170d7021f 100644 --- a/docs/features/custom-categories.mdx +++ b/docs/features/custom-categories.mdx @@ -45,7 +45,55 @@ Mostly cook at home because of gym plan. (fitness, cooking) The more detailed the description of categories is, the better output the user will receive. When custom categories are provided in the `add` API call, they will completely replace the default categories and will be directly assigned to the memory, so make sure to include all categories you want to use. - We will soon release a feature that allows users to set custom categories in `project`. This will allow the functionality where relevant categories are automatically assigned to the memory based on the input text provided. + +## Setting Project-Level Custom Categories + +You can also set custom categories at the project level, which will be applied to all memories added within that project. We will automatically assign relevant categories from your custom set to new memories based on their content. + +Here's how to set custom categories: + + +```python Code +# Update custom categories +new_categories = [ + {"cooking": "For users interested in cooking and culinary experiences"}, + {"fitness": "Content related to fitness and exercise"} +] +response = client.update_custom_instructions_and_categories({"custom_categories": new_categories}) +print(response) +``` + +```json Output +{ + "message": "Updated custom categories" +} +``` + + +You can also retrieve the current custom categories: + + +```python Code +from mem0 import MemoryClient + +client = MemoryClient(api_key="xxx") + +# Get current custom categories +categories = client.get_custom_instructions_and_categories(["custom_categories"]) +print(categories) +``` + +```json Output +{ + "custom_categories": [ + {"cooking": "For users interested in cooking and culinary experiences"}, + {"fitness": "Content related to fitness and exercise"} + ] +} +``` + + +These project-level categories will be automatically applied to all new memories added to the project. ## Default Categories Here is the list of **default categories**. Ensure you review these before creating custom categories to prevent duplication. diff --git a/docs/features/custom-instructions.mdx b/docs/features/custom-instructions.mdx new file mode 100644 index 0000000000..47112642cd --- /dev/null +++ b/docs/features/custom-instructions.mdx @@ -0,0 +1,59 @@ +--- +title: Custom Instructions +description: 'Enhance your product experience by adding custom instructions tailored to your needs' +--- + +## Introduction to Custom Instructions + +Custom instructions allow you to define specific guidelines for your project. This feature helps ensure consistency and provides clear direction for handling project-specific requirements. + +Custom instructions are particularly useful when you want to: +- Define how information should be extracted from conversations +- Specify what types of data should be captured or ignored +- Set rules for categorizing and organizing memories +- Maintain consistent handling of project-specific requirements + +When custom instructions are set at the project level, they will be applied to all new memories added within that project. This ensures that your data is processed according to your defined guidelines across your entire project. + +## Setting Custom Instructions + +You can set custom instructions for your project using the following method: + + +```python Code +# Update custom instructions +prompt = """ +Your task is to extract ONLY health-related information from conversations, including: + +- Medical conditions, symptoms, diagnoses +- Medications, treatments, procedures +- Diet, exercise, sleep habits +- Doctor visits and appointments +- Health metrics (weight, blood pressure, etc) +""" +response = client.update_custom_instructions_and_categories({"custom_instructions": prompt}) +print(response) +``` + +```json Output +{ + "message": "Updated custom instructions" +} +``` + + +You can also retrieve the current custom instructions: + + +```python Code +# Retrieve current custom instructions +response = client.get_custom_instructions_and_categories(["custom_instructions"]) +print(response) +``` + +```json Output +{ + "custom_instructions": "Your task is to extract ONLY health-related information from conversations, including:\n\n- Medical conditions, symptoms, diagnoses\n- Medications, treatments, procedures\n- Diet, exercise, sleep habits\n- Doctor visits and appointments\n- Health metrics (weight, blood pressure, etc)" +} +``` + \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index 8916a5959c..aab41b2f33 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -64,7 +64,7 @@ "platform/quickstart", { "group": "Features", - "pages": ["features/selective-memory", "features/custom-categories", "features/direct-import", "features/async-client", "features/memory-export"] + "pages": ["features/selective-memory", "features/custom-categories", "features/custom-instructions", "features/direct-import", "features/async-client", "features/memory-export"] }, "features/langchain-tools" ] @@ -197,6 +197,8 @@ "api-reference/project/get-project", "api-reference/project/create-project", "api-reference/project/delete-project", + "api-reference/project/get-instructions-and-categories", + "api-reference/project/update-instructions-and-categories", { "group": "Members APIs", "pages":[ diff --git a/docs/openapi.json b/docs/openapi.json index acfa8477b2..97ddff576f 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -394,200 +394,200 @@ } } }, - "/v1/exports/": { - "get": { - "tags": [ - "exports" - ], - "summary": "Export data based on filters", - "description": "Get the latest memory export.", - "operationId": "exports_list", - "parameters": [ - { - "name": "user_id", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Filter exports by user ID" - }, - { - "name": "run_id", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Filter exports by run ID" - }, - { - "name": "session_id", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Filter exports by session ID" - }, - { - "name": "app_id", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Filter exports by app ID" - }, - { - "name": "org_id", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Filter exports by organization ID" - }, - { - "name": "project_id", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Filter exports by project ID" - } - ], - "responses": { - "200": { - "description": "Successful export", - "content": { - "application/json": { - "schema": { - "type": "object", - "description": "Export data response" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "One of the filters: app_id, user_id, agent_id, run_id is required!" - } + "/v1/exports/": { + "get": { + "tags": [ + "exports" + ], + "summary": "Export data based on filters", + "description": "Get the latest memory export.", + "operationId": "exports_list", + "parameters": [ + { + "name": "user_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter exports by user ID" + }, + { + "name": "run_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter exports by run ID" + }, + { + "name": "session_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter exports by session ID" + }, + { + "name": "app_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter exports by app ID" + }, + { + "name": "org_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter exports by organization ID" + }, + { + "name": "project_id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter exports by project ID" + } + ], + "responses": { + "200": { + "description": "Successful export", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Export data response" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "One of the filters: app_id, user_id, agent_id, run_id is required!" + } + } + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "No memory export request found" + } + } + } + } + } } - } } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "No memory export request found" - } + }, + "post": { + "tags": [ + "exports" + ], + "summary": "Create an export job with schema", + "description": "Create a structured export of memories based on a provided schema.", + "operationId": "exports_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["schema"], + "properties": { + "schema": { + "type": "object", + "description": "Schema definition for the export" + }, + "user_id": { + "type": "string", + "description": "Filter exports by user ID" + }, + "run_id": { + "type": "string", + "description": "Filter exports by run ID" + }, + "session_id": { + "type": "string", + "description": "Filter exports by session ID" + }, + "app_id": { + "type": "string", + "description": "Filter exports by app ID" + }, + "org_id": { + "type": "string", + "description": "Filter exports by organization ID" + }, + "project_id": { + "type": "string", + "description": "Filter exports by project ID" + } + } + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Export created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Memory export request received. The export will be ready in a few seconds." + }, + "id": { + "type": "string", + "format": "uuid", + "example": "550e8400-e29b-41d4-a716-446655440000" + } + }, + "required": ["message", "id"] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Schema is required and must be a valid object" + } + } + } + } } - } } - } + } } - } - }, - "post": { - "tags": [ - "exports" - ], - "summary": "Create an export job with schema", - "description": "Create a structured export of memories based on a provided schema.", - "operationId": "exports_create", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["schema"], - "properties": { - "schema": { - "type": "object", - "description": "Schema definition for the export" - }, - "user_id": { - "type": "string", - "description": "Filter exports by user ID" - }, - "run_id": { - "type": "string", - "description": "Filter exports by run ID" - }, - "session_id": { - "type": "string", - "description": "Filter exports by session ID" - }, - "app_id": { - "type": "string", - "description": "Filter exports by app ID" - }, - "org_id": { - "type": "string", - "description": "Filter exports by organization ID" - }, - "project_id": { - "type": "string", - "description": "Filter exports by project ID" - } - } - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Export created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Memory export request received. The export will be ready in a few seconds." - }, - "id": { - "type": "string", - "format": "uuid", - "example": "550e8400-e29b-41d4-a716-446655440000" - } - }, - "required": ["message", "id"] - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Schema is required and must be a valid object" - } - } - } - } - } - } - } - } - }, + }, "/v1/memories/": { "get": { "tags": [ @@ -4044,6 +4044,197 @@ } ] } + }, + "/api/v1/orgs/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/": { + "get": { + "tags": ["custom-instructions-and-categories"], + "summary": "Get custom instructions and categories", + "description": "Retrieve custom categories and instructions for a specific project", + "operationId": "get_custom_instructions_and_categories", + "parameters": [ + { + "name": "org_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Organization ID" + }, + { + "name": "project_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Project ID" + } + ], + "responses": { + "200": { + "description": "Successfully retrieved custom instructions and categories", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "custom_categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of custom categories for the project" + }, + "custom_instructions": { + "type": "string", + "description": "Custom instructions text for the project" + } + }, + "required": ["custom_categories", "custom_instructions"] + } + } + } + }, + "403": { + "description": "Unauthorized access", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Unauthorized to modify custom instructions and categories." + } + } + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "Python", + "source": "import requests\n\nurl = \"https://api.mem0.ai/api/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/\"\nheaders = {\"Authorization\": \"Token \"}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "source": "fetch('https://api.mem0.ai/api/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/', {\n headers: {\n 'Authorization': 'Token '\n }\n})\n.then(response => response.json())\n.then(data => console.log(data))\n.catch(error => console.error(error));" + } + ] + }, + "post": { + "tags": ["custom-instructions-and-categories"], + "summary": "Update custom instructions and categories", + "description": "Update custom categories and instructions for a specific project", + "operationId": "update_custom_instructions_and_categories", + "parameters": [ + { + "name": "org_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Organization ID" + }, + { + "name": "project_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Project ID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "custom_categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of new custom categories for the project" + }, + "custom_instructions": { + "type": "string", + "description": "New custom instructions text for the project" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully updated custom instructions and categories", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Updated custom categories / instructions" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": { + "field_name": ["error message"] + } + } + } + } + }, + "403": { + "description": "Unauthorized access", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Unauthorized to modify custom instructions and categories." + } + } + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "Python", + "source": "import requests\n\nurl = \"https://api.mem0.ai/api/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/\"\nheaders = {\n \"Authorization\": \"Token \",\n \"Content-Type\": \"application/json\"\n}\npayload = {\n \"custom_categories\": [\"new_category1\", \"new_category2\"],\n \"custom_instructions\": \"New custom instructions text\"\n}\n\nresponse = requests.post(url, headers=headers, json=payload)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "source": "fetch('https://api.mem0.ai/api/organizations/{org_id}/projects/{project_id}/custom-instructions-and-categories/', {\n method: 'POST',\n headers: {\n 'Authorization': 'Token ',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n custom_categories: ['new_category1', 'new_category2'],\n custom_instructions: 'New custom instructions text'\n })\n})\n.then(response => response.json())\n.then(data => console.log(data))\n.catch(error => console.error(error));" + } + ] + } } }, "components": { diff --git a/mem0/client/main.py b/mem0/client/main.py index 29349b1dc1..22a2e4b888 100644 --- a/mem0/client/main.py +++ b/mem0/client/main.py @@ -399,6 +399,58 @@ def get_memory_export(self, **kwargs) -> Dict[str, Any]: capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys())}) return response.json() + @api_error_handler + def get_custom_instructions_and_categories(self, fields: List[str]) -> Dict[str, Any]: + """Get instructions or categories for the current project. + + Args: + fields: List of field names to retrieve (e.g. ['custom_instructions', 'custom_categories']) + + Returns: + Dictionary containing the requested instructions or categories. + + Raises: + APIError: If the API request fails. + ValueError: If org_id or project_id are not set. + """ + if not (self.org_id and self.project_id): + raise ValueError("org_id and project_id must be set to access instructions or categories") + + params = self._prepare_params({"fields": fields}) + response = self.client.get( + f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/custom-instructions-and-categories/", + params=params, + ) + response.raise_for_status() + capture_client_event("client.get_custom_instructions_and_categories", self, {"fields": fields}) + return response.json() + + @api_error_handler + def update_custom_instructions_and_categories(self, fields: Dict[str, Any]) -> Dict[str, Any]: + """Update instructions or categories for the current project. + + Args: + fields: Dictionary of fields to update + (e.g. {"custom_categories": "new instructions", "custom_categories": ["cat1"]}) + + Returns: + Dictionary containing the API response. + + Raises: + APIError: If the API request fails. + ValueError: If org_id or project_id are not set. + """ + if not (self.org_id and self.project_id): + raise ValueError("org_id and project_id must be set to update instructions or categories") + + response = self.client.post( + f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/custom-instructions-and-categories/", + json=fields, + ) + response.raise_for_status() + capture_client_event("client.update_custom_instructions_and_categories", self, {"fields": list(fields.keys())}) + return response.json() + def chat(self): """Start a chat with the Mem0 AI. (Not implemented) @@ -604,5 +656,110 @@ async def reset(self) -> Dict[str, str]: capture_client_event("async_client.reset", self.sync_client) return {"message": "Client reset successful. All users and memories deleted."} + @api_error_handler + async def batch_update(self, memories: List[Dict[str, Any]]) -> Dict[str, Any]: + """Batch update memories. + + Args: + memories: List of memory dictionaries to update. Each dictionary must contain: + - memory_id (str): ID of the memory to update + - text (str): New text content for the memory + + Returns: + str: Message indicating the success of the batch update. + + Raises: + APIError: If the API request fails. + """ + response = await self.async_client.put("/v1/batch/", json={"memories": memories}) + response.raise_for_status() + + capture_client_event("async_client.batch_update", self.sync_client) + return response.json() + + @api_error_handler + async def batch_delete(self, memories: List[Dict[str, Any]]) -> Dict[str, Any]: + """Batch delete memories. + + Args: + memories: List of memory dictionaries to delete. Each dictionary must contain: + - memory_id (str): ID of the memory to delete + + Returns: + str: Message indicating the success of the batch deletion. + + Raises: + APIError: If the API request fails. + """ + response = await self.async_client.request("DELETE", "/v1/batch/", json={"memories": memories}) + response.raise_for_status() + + capture_client_event("async_client.batch_delete", self.sync_client) + return response.json() + + @api_error_handler + async def create_memory_export(self, schema: str, **kwargs) -> Dict[str, Any]: + """Create a memory export with the provided schema. + + Args: + schema: JSON schema defining the export structure + **kwargs: Optional filters like user_id, run_id, etc. + + Returns: + Dict containing export request ID and status message + """ + response = await self.async_client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)}) + response.raise_for_status() + capture_client_event( + "async_client.create_memory_export", self.sync_client, {"schema": schema, "keys": list(kwargs.keys())} + ) + return response.json() + + @api_error_handler + async def get_memory_export(self, **kwargs) -> Dict[str, Any]: + """Get a memory export. + + Args: + **kwargs: Filters like user_id to get specific export + + Returns: + Dict containing the exported data + """ + response = await self.async_client.get("/v1/exports/", params=self._prepare_params(kwargs)) + response.raise_for_status() + capture_client_event("async_client.get_memory_export", self.sync_client, {"keys": list(kwargs.keys())}) + return response.json() + + @api_error_handler + async def get_custom_instructions_and_categories(self, fields: List[str]) -> Dict[str, Any]: + if not (self.sync_client.org_id and self.sync_client.project_id): + raise ValueError("org_id and project_id must be set to access instructions or categories") + + params = self.sync_client._prepare_params({"fields": fields}) + response = await self.async_client.get( + f"/api/v1/orgs/organizations/{self.sync_client.org_id}/projects/{self.sync_client.project_id}/custom-instructions-and-categories/", + params=params, + ) + response.raise_for_status() + capture_client_event( + "async_client.get_custom_instructions_and_categories", self.sync_client, {"fields": fields} + ) + return response.json() + + @api_error_handler + async def update_custom_instructions_and_categories(self, fields: Dict[str, Any]) -> Dict[str, Any]: + if not (self.sync_client.org_id and self.sync_client.project_id): + raise ValueError("org_id and project_id must be set to update instructions or categories") + + response = await self.async_client.post( + f"/api/v1/orgs/organizations/{self.sync_client.org_id}/projects/{self.sync_client.project_id}/custom-instructions-and-categories/", + json=fields, + ) + response.raise_for_status() + capture_client_event( + "async_client.update_custom_instructions_and_categories", self.sync_client, {"fields": list(fields.keys())} + ) + return response.json() + async def chat(self): raise NotImplementedError("Chat is not implemented yet")