From a384d1cd6482af2cf552bf18bb5fd791d4a0c65a Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Thu, 25 Dec 2025 20:51:56 +0800 Subject: [PATCH 1/9] add get_user_names_by_memory_ids api --- src/memos/api/product_models.py | 13 ++++++++++++ src/memos/api/routers/server_router.py | 28 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index adcb68a96..3c7070ec9 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -1168,3 +1168,16 @@ class AllStatusResponse(BaseResponse[AllStatusResponseData]): """Response model for full scheduler status operations.""" message: str = "Scheduler status summary retrieved successfully" + + +# ─── Internal API Endpoints Models (for internal use) ─────────────────────────────────────────────────── + + +class GetUserNamesByMemoryIdsRequest(BaseRequest): + """Request model for getting user names by memory ids.""" + + memory_ids: list[str] = Field(..., description="Memory IDs") + + +class GetUserNamesByMemoryIdsResponse(BaseResponse[dict[str, list[str]]]): + """Response model for getting user names by memory ids.""" diff --git a/src/memos/api/routers/server_router.py b/src/memos/api/routers/server_router.py index e87e006dd..07c42bbb2 100644 --- a/src/memos/api/routers/server_router.py +++ b/src/memos/api/routers/server_router.py @@ -36,6 +36,8 @@ GetMemoryPlaygroundRequest, GetMemoryRequest, GetMemoryResponse, + GetUserNamesByMemoryIdsRequest, + GetUserNamesByMemoryIdsResponse, MemoryResponse, SearchResponse, StatusResponse, @@ -43,6 +45,7 @@ SuggestionResponse, TaskQueueResponse, ) +from memos.graph_dbs.polardb import PolarDBGraphDB from memos.log import get_logger from memos.mem_scheduler.base_scheduler import BaseScheduler from memos.mem_scheduler.utils.status_tracker import TaskStatusTracker @@ -84,6 +87,7 @@ redis_client = components["redis_client"] status_tracker = TaskStatusTracker(redis_client=redis_client) embedder = components["embedder"] +graph_db = components["graph_db"] # ============================================================================= @@ -329,3 +333,27 @@ def feedback_memories(feedback_req: APIFeedbackRequest): This endpoint uses the class-based FeedbackHandler for better code organization. """ return feedback_handler.handle_feedback_memories(feedback_req) + + +# ============================================================================= +# Other API Endpoints (for internal use) +# ============================================================================= + + +@router.get( + "/get_user_names_by_memory_ids", + summary="Get user names by memory ids", + response_model=GetUserNamesByMemoryIdsResponse, +) +def get_user_names_by_memory_ids(memory_ids: GetUserNamesByMemoryIdsRequest): + """Get user names by memory ids.""" + if not isinstance(graph_db, PolarDBGraphDB): + raise HTTPException( + status_code=400, + detail=( + "graph_db must be an instance of PolarDBGraphDB to use " + "get_user_names_by_memory_ids" + f"current graph_db is: {graph_db.__class__.__name__}" + ), + ) + return graph_db.get_user_names_by_memory_ids(memory_ids=memory_ids) From 6a13c1c43d73e62a79faf62d18381859bdf24b7b Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Sun, 4 Jan 2026 12:00:24 +0800 Subject: [PATCH 2/9] modify delete api --- src/memos/api/handlers/memory_handler.py | 13 ++++--------- src/memos/api/product_models.py | 2 +- src/memos/api/routers/server_router.py | 2 +- src/memos/memories/textual/preference.py | 9 +++++++++ src/memos/memories/textual/tree.py | 9 ++++++++- src/memos/vec_dbs/milvus.py | 8 ++++++++ 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/memos/api/handlers/memory_handler.py b/src/memos/api/handlers/memory_handler.py index a744e16e2..ef829d757 100644 --- a/src/memos/api/handlers/memory_handler.py +++ b/src/memos/api/handlers/memory_handler.py @@ -246,8 +246,7 @@ def handle_delete_memories(delete_mem_req: DeleteMemoryRequest, naive_mem_cube: try: if delete_mem_req.memory_ids is not None: - for cube_id in delete_mem_req.writable_cube_ids: - naive_mem_cube.text_mem.delete(delete_mem_req.memory_ids, user_name=cube_id) + naive_mem_cube.text_mem.delete_by_memory_ids(delete_mem_req.memory_ids) if naive_mem_cube.pref_mem is not None: naive_mem_cube.pref_mem.delete(delete_mem_req.memory_ids) elif delete_mem_req.file_ids is not None: @@ -255,13 +254,9 @@ def handle_delete_memories(delete_mem_req: DeleteMemoryRequest, naive_mem_cube: writable_cube_ids=delete_mem_req.writable_cube_ids, file_ids=delete_mem_req.file_ids ) elif delete_mem_req.filter is not None: - # TODO: Implement deletion by filter - # Need to find memories matching filter and delete them - logger.warning("Deletion by filter not implemented yet") - return DeleteMemoryResponse( - message="Deletion by filter not implemented yet", - data={"status": "failure"}, - ) + naive_mem_cube.text_mem.delete_by_filter(filter=delete_mem_req.filter) + if naive_mem_cube.pref_mem is not None: + naive_mem_cube.pref_mem.delete_by_filter(filter=delete_mem_req.filter) except Exception as e: logger.error(f"Failed to delete memories: {e}", exc_info=True) return DeleteMemoryResponse( diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index 25e0d809d..c52d9e8d2 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -784,7 +784,7 @@ class GetMemoryRequest(BaseRequest): class DeleteMemoryRequest(BaseRequest): """Request model for deleting memories.""" - writable_cube_ids: list[str] = Field(..., description="Writable cube IDs") + writable_cube_ids: list[str] = Field(None, description="Writable cube IDs") memory_ids: list[str] | None = Field(None, description="Memory IDs") file_ids: list[str] | None = Field(None, description="File IDs") filter: dict[str, Any] | None = Field(None, description="Filter for the memory") diff --git a/src/memos/api/routers/server_router.py b/src/memos/api/routers/server_router.py index 07c42bbb2..c3b05e823 100644 --- a/src/memos/api/routers/server_router.py +++ b/src/memos/api/routers/server_router.py @@ -340,7 +340,7 @@ def feedback_memories(feedback_req: APIFeedbackRequest): # ============================================================================= -@router.get( +@router.post( "/get_user_names_by_memory_ids", summary="Get user names by memory ids", response_model=GetUserNamesByMemoryIdsResponse, diff --git a/src/memos/memories/textual/preference.py b/src/memos/memories/textual/preference.py index cb4f00735..a34315918 100644 --- a/src/memos/memories/textual/preference.py +++ b/src/memos/memories/textual/preference.py @@ -314,6 +314,15 @@ def delete(self, memory_ids: list[str]) -> None: for collection_name in collection_list: self.vector_db.delete(collection_name, memory_ids) + def delete_by_filter(self, filter: dict[str, Any]) -> None: + """Delete memories by filter. + Args: + filter (dict[str, Any]): Filter criteria. + """ + collection_list = self.vector_db.config.collection_name + for collection_name in collection_list: + self.vector_db.delete_by_filter(collection_name=collection_name, filter=filter) + def delete_with_collection_name(self, collection_name: str, memory_ids: list[str]) -> None: """Delete memories by their IDs and collection name. Args: diff --git a/src/memos/memories/textual/tree.py b/src/memos/memories/textual/tree.py index e576c0ea9..c486e6cf6 100644 --- a/src/memos/memories/textual/tree.py +++ b/src/memos/memories/textual/tree.py @@ -347,6 +347,13 @@ def delete(self, memory_ids: list[str], user_name: str | None = None) -> None: except Exception as e: logger.warning(f"TreeTextMemory.delete_hard: failed to delete {mid}: {e}") + def delete_by_memory_ids(self, memory_ids: list[str]) -> None: + """Delete memories by memory_ids.""" + try: + self.graph_store.delete_node_by_prams(memory_ids=memory_ids) + except Exception as e: + logger.error(f"An error occurred while deleting memories by memory_ids: {e}") + def delete_all(self) -> None: """Delete all memories and their relationships from the graph store.""" try: @@ -358,7 +365,7 @@ def delete_all(self) -> None: def delete_by_filter( self, - writable_cube_ids: list[str], + writable_cube_ids: list[str] | None = None, file_ids: list[str] | None = None, filter: dict | None = None, ) -> None: diff --git a/src/memos/vec_dbs/milvus.py b/src/memos/vec_dbs/milvus.py index ecbca5815..5dacf0499 100644 --- a/src/memos/vec_dbs/milvus.py +++ b/src/memos/vec_dbs/milvus.py @@ -646,3 +646,11 @@ def delete(self, collection_name: str, ids: list[str]) -> None: collection_name=collection_name, ids=ids, ) + + def delete_by_filter(self, collection_name: str, filter: dict[str, Any]) -> None: + """Delete items from the vector database by filter.""" + expr = self._dict_to_expr(filter) if filter else "" + self.client.delete( + collection_name=collection_name, + filter=expr, + ) From 6376e390deb95e542b09a962ce9429f6f6d56e1c Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Sun, 4 Jan 2026 15:36:38 +0800 Subject: [PATCH 3/9] modify bug --- src/memos/api/product_models.py | 2 +- src/memos/api/routers/server_router.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index c52d9e8d2..f0a4e333b 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -1195,5 +1195,5 @@ class GetUserNamesByMemoryIdsRequest(BaseRequest): memory_ids: list[str] = Field(..., description="Memory IDs") -class GetUserNamesByMemoryIdsResponse(BaseResponse[dict[str, list[str]]]): +class GetUserNamesByMemoryIdsResponse(BaseResponse[dict[str, str | None]]): """Response model for getting user names by memory ids.""" diff --git a/src/memos/api/routers/server_router.py b/src/memos/api/routers/server_router.py index c3b05e823..7c0f3ea8f 100644 --- a/src/memos/api/routers/server_router.py +++ b/src/memos/api/routers/server_router.py @@ -345,7 +345,7 @@ def feedback_memories(feedback_req: APIFeedbackRequest): summary="Get user names by memory ids", response_model=GetUserNamesByMemoryIdsResponse, ) -def get_user_names_by_memory_ids(memory_ids: GetUserNamesByMemoryIdsRequest): +def get_user_names_by_memory_ids(request: GetUserNamesByMemoryIdsRequest): """Get user names by memory ids.""" if not isinstance(graph_db, PolarDBGraphDB): raise HTTPException( @@ -356,4 +356,9 @@ def get_user_names_by_memory_ids(memory_ids: GetUserNamesByMemoryIdsRequest): f"current graph_db is: {graph_db.__class__.__name__}" ), ) - return graph_db.get_user_names_by_memory_ids(memory_ids=memory_ids) + result = graph_db.get_user_names_by_memory_ids(memory_ids=request.memory_ids) + return GetUserNamesByMemoryIdsResponse( + code=200, + message="Successfully", + data=result, + ) From 7e66350726f10e98f2b5fa3d86ba423ddfca8488 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Sun, 4 Jan 2026 17:08:38 +0800 Subject: [PATCH 4/9] add extract limit in implicit memory --- src/memos/templates/prefer_complete_prompt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/memos/templates/prefer_complete_prompt.py b/src/memos/templates/prefer_complete_prompt.py index 3315e061e..a69f1babb 100644 --- a/src/memos/templates/prefer_complete_prompt.py +++ b/src/memos/templates/prefer_complete_prompt.py @@ -77,6 +77,7 @@ * **Contextual signals**: What do the user's choices, comparisons, exclusions, or scenario selections reveal about their deeper preferences? - Do not treat explicitly stated preferences as implicit preferences; this prompt is only for inferring preferences that are not directly mentioned. - Go beyond surface-level facts to understand the user's hidden possibilities and underlying logic. +- **Important**: For Assistant's responses or suggestions, they can only be extracted as the user's implicit preferences if there is evidence in subsequent conversation that the user implicitly accepted them (e.g., adoption, agreement, acting on the suggestion, etc.). Assistant suggestions alone do not constitute user preferences. Requirements: 1. Only make inferences when there is sufficient evidence in the conversation; avoid unsupported or far-fetched guesses. @@ -117,6 +118,7 @@ * **情境信号**:用户的选择、比较、排除或场景选择揭示了什么样的深层偏好? - 不要将明确陈述的偏好视为隐式偏好;此提示仅用于推断未直接提及的偏好。 - 超越表面事实,理解用户的隐藏可能性和背后的逻辑。 +- **特别注意**:对于Assistant的回答内容或建议,只有在后续对话中用户表现出隐含接受(如采纳、认同、按建议行动等)的情况下,才能将相关内容提取为用户的隐式偏好。单纯的Assistant建议本身不构成用户偏好。 要求: 1. 仅在对话中有充分证据时进行推断;避免无根据或牵强的猜测。 From c53d7b1bd8873a6c12706ee1e6fe66c33e1eda09 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Sun, 4 Jan 2026 20:33:46 +0800 Subject: [PATCH 5/9] close internet search in chat api, modify implicit pref prompt --- src/memos/api/handlers/chat_handler.py | 14 ++++++++++++++ src/memos/templates/prefer_complete_prompt.py | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/memos/api/handlers/chat_handler.py b/src/memos/api/handlers/chat_handler.py index 3e9d1e5ec..caeba0ca1 100644 --- a/src/memos/api/handlers/chat_handler.py +++ b/src/memos/api/handlers/chat_handler.py @@ -138,6 +138,13 @@ def handle_chat_complete(self, chat_req: APIChatCompleteRequest) -> dict[str, An if text_mem_results and text_mem_results[0].get("memories"): memories_list = text_mem_results[0]["memories"] + # Drop internet memories forced + memories_list = [ + mem + for mem in memories_list + if mem.get("metadata", {}).get("memory_type") != "OuterMemory" + ] + # Filter memories by threshold filtered_memories = self._filter_memories_by_threshold( memories_list, chat_req.threshold or 0.5 @@ -277,6 +284,13 @@ def generate_chat_response() -> Generator[str, None, None]: if text_mem_results and text_mem_results[0].get("memories"): memories_list = text_mem_results[0]["memories"] + # Drop internet memories forced + memories_list = [ + mem + for mem in memories_list + if mem.get("metadata", {}).get("memory_type") != "OuterMemory" + ] + # Filter memories by threshold filtered_memories = self._filter_memories_by_threshold(memories_list) diff --git a/src/memos/templates/prefer_complete_prompt.py b/src/memos/templates/prefer_complete_prompt.py index a69f1babb..a67f0c12c 100644 --- a/src/memos/templates/prefer_complete_prompt.py +++ b/src/memos/templates/prefer_complete_prompt.py @@ -77,7 +77,7 @@ * **Contextual signals**: What do the user's choices, comparisons, exclusions, or scenario selections reveal about their deeper preferences? - Do not treat explicitly stated preferences as implicit preferences; this prompt is only for inferring preferences that are not directly mentioned. - Go beyond surface-level facts to understand the user's hidden possibilities and underlying logic. -- **Important**: For Assistant's responses or suggestions, they can only be extracted as the user's implicit preferences if there is evidence in subsequent conversation that the user implicitly accepted them (e.g., adoption, agreement, acting on the suggestion, etc.). Assistant suggestions alone do not constitute user preferences. +- For Assistant's responses or suggestions, they can only be extracted as the user's implicit preferences if there is evidence in subsequent conversation that the user implicitly accepted them (e.g., adoption, agreement, acting on the suggestion, etc.). Assistant suggestions alone do not constitute user preferences. Requirements: 1. Only make inferences when there is sufficient evidence in the conversation; avoid unsupported or far-fetched guesses. @@ -118,7 +118,7 @@ * **情境信号**:用户的选择、比较、排除或场景选择揭示了什么样的深层偏好? - 不要将明确陈述的偏好视为隐式偏好;此提示仅用于推断未直接提及的偏好。 - 超越表面事实,理解用户的隐藏可能性和背后的逻辑。 -- **特别注意**:对于Assistant的回答内容或建议,只有在后续对话中用户表现出隐含接受(如采纳、认同、按建议行动等)的情况下,才能将相关内容提取为用户的隐式偏好。单纯的Assistant建议本身不构成用户偏好。 +- 对于Assistant的回答内容或建议,只有在后续对话中用户表现出隐含接受(如采纳、认同、按建议行动等)的情况下,才能将相关内容提取为用户的隐式偏好。单纯的Assistant建议本身不构成用户偏好。 要求: 1. 仅在对话中有充分证据时进行推断;避免无根据或牵强的猜测。 From 326c5edf42e6ff12eb62eb63f610ccbe94fddbb5 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Mon, 5 Jan 2026 11:39:57 +0800 Subject: [PATCH 6/9] modify bug --- src/memos/api/handlers/chat_handler.py | 8 ++++---- .../memories/textual/tree_text_memory/retrieve/utils.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/memos/api/handlers/chat_handler.py b/src/memos/api/handlers/chat_handler.py index caeba0ca1..812cf2793 100644 --- a/src/memos/api/handlers/chat_handler.py +++ b/src/memos/api/handlers/chat_handler.py @@ -482,7 +482,7 @@ def generate_chat_response() -> Generator[str, None, None]: # get preference string pref_string = search_response.data.get("pref_string", "") - yield f"data: {json.dumps({'type': 'reference', 'data': reference})}\n\n" + yield f"data: {json.dumps({'type': 'reference', 'data': reference}, ensure_ascii=False)}\n\n" # Prepare preference markdown string if chat_req.include_preference: @@ -586,7 +586,7 @@ def generate_chat_response() -> Generator[str, None, None]: internet_reference = self._get_internet_reference( search_response.data.get("text_mem")[0]["memories"] ) - yield f"data: {json.dumps({'type': 'reference', 'data': reference})}\n\n" + yield f"data: {json.dumps({'type': 'reference', 'data': reference}, ensure_ascii=False)}\n\n" # Step 2: Build system prompt with memories lang = detect_lang(chat_req.query) @@ -684,7 +684,7 @@ def generate_chat_response() -> Generator[str, None, None]: if chat_req.internet_search or parsed_goal.internet_search: # Yield internet reference after text response - yield f"data: {json.dumps({'type': 'internet_reference', 'data': internet_reference})}\n\n" + yield f"data: {json.dumps({'type': 'internet_reference', 'data': internet_reference}, ensure_ascii=False)}\n\n" # Calculate timing time_end = time.time() @@ -697,7 +697,7 @@ def generate_chat_response() -> Generator[str, None, None]: current_messages.append({"role": "assistant", "content": full_response}) further_suggestion = self._get_further_suggestion(current_messages) self.logger.info(f"[PLAYGROUND CHAT] further_suggestion: {further_suggestion}") - yield f"data: {json.dumps({'type': 'suggestion', 'data': further_suggestion})}\n\n" + yield f"data: {json.dumps({'type': 'suggestion', 'data': further_suggestion}, ensure_ascii=False)}\n\n" yield f"data: {json.dumps({'type': 'end'})}\n\n" diff --git a/src/memos/memories/textual/tree_text_memory/retrieve/utils.py b/src/memos/memories/textual/tree_text_memory/retrieve/utils.py index bcd47b078..54caa20f7 100644 --- a/src/memos/memories/textual/tree_text_memory/retrieve/utils.py +++ b/src/memos/memories/textual/tree_text_memory/retrieve/utils.py @@ -27,7 +27,7 @@ "tags": [...], "goal_type": "retrieval | qa | generation", "rephrased_instruction": "...", # return an empty string if the original instruction is easy enough to understand - "internet_search": True/False, + "internet_search": true/false, "memories": ["...", "...", ...] } """ From f6cad997fc4cd0489b3706ec86fb137292bdc244 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Mon, 5 Jan 2026 14:22:45 +0800 Subject: [PATCH 7/9] add a new internal method for check cube id exist --- src/memos/api/product_models.py | 10 ++++++++++ src/memos/api/routers/server_router.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index f0a4e333b..ee7a45c2d 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -1197,3 +1197,13 @@ class GetUserNamesByMemoryIdsRequest(BaseRequest): class GetUserNamesByMemoryIdsResponse(BaseResponse[dict[str, str | None]]): """Response model for getting user names by memory ids.""" + + +class ExistMemCubeIdRequest(BaseRequest): + """Request model for checking if mem cube id exists.""" + + mem_cube_id: str = Field(..., description="Mem cube ID") + + +class ExistMemCubeIdResponse(BaseResponse[dict[str, bool]]): + """Response model for checking if mem cube id exists.""" diff --git a/src/memos/api/routers/server_router.py b/src/memos/api/routers/server_router.py index 7c0f3ea8f..c60e84253 100644 --- a/src/memos/api/routers/server_router.py +++ b/src/memos/api/routers/server_router.py @@ -33,6 +33,8 @@ ChatRequest, DeleteMemoryRequest, DeleteMemoryResponse, + ExistMemCubeIdRequest, + ExistMemCubeIdResponse, GetMemoryPlaygroundRequest, GetMemoryRequest, GetMemoryResponse, @@ -362,3 +364,17 @@ def get_user_names_by_memory_ids(request: GetUserNamesByMemoryIdsRequest): message="Successfully", data=result, ) + + +@router.post( + "/exist_mem_cube_id", + summary="Check if mem cube id exists", + response_model=ExistMemCubeIdResponse, +) +def exist_mem_cube_id(request: ExistMemCubeIdRequest): + """Check if mem cube id exists.""" + return ExistMemCubeIdResponse( + code=200, + message="Successfully", + data=graph_db.exist_user_name(user_name=request.mem_cube_id), + ) From 298844ceb751fdb4e0bac3560dddaae7b236f48a Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Tue, 6 Jan 2026 16:15:31 +0800 Subject: [PATCH 8/9] modify code --- src/memos/api/routers/server_router.py | 8 ++++++++ src/memos/memories/textual/preference.py | 2 +- src/memos/memories/textual/simple_preference.py | 8 ++++---- src/memos/vec_dbs/milvus.py | 6 ++---- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/memos/api/routers/server_router.py b/src/memos/api/routers/server_router.py index c60e84253..a4052d313 100644 --- a/src/memos/api/routers/server_router.py +++ b/src/memos/api/routers/server_router.py @@ -90,6 +90,7 @@ status_tracker = TaskStatusTracker(redis_client=redis_client) embedder = components["embedder"] graph_db = components["graph_db"] +vector_db = components["vector_db"] # ============================================================================= @@ -359,6 +360,13 @@ def get_user_names_by_memory_ids(request: GetUserNamesByMemoryIdsRequest): ), ) result = graph_db.get_user_names_by_memory_ids(memory_ids=request.memory_ids) + if vector_db: + prefs = [] + for collection_name in ["explicit_preference", "implicit_preference"]: + prefs.extend( + vector_db.get_by_ids(collection_name=collection_name, ids=request.memory_ids) + ) + result.update({pref.id: pref.payload.get("mem_cube_id", None) for pref in prefs}) return GetUserNamesByMemoryIdsResponse( code=200, message="Successfully", diff --git a/src/memos/memories/textual/preference.py b/src/memos/memories/textual/preference.py index a34315918..78f4d6e28 100644 --- a/src/memos/memories/textual/preference.py +++ b/src/memos/memories/textual/preference.py @@ -248,7 +248,7 @@ def get_all(self) -> list[TextualMemoryItem]: Returns: list[TextualMemoryItem]: List of all memories. """ - all_collections = self.vector_db.list_collections() + all_collections = ["explicit_preference", "implicit_preference"] all_memories = {} for collection_name in all_collections: items = self.vector_db.get_all(collection_name) diff --git a/src/memos/memories/textual/simple_preference.py b/src/memos/memories/textual/simple_preference.py index ee37d638c..cc1781f06 100644 --- a/src/memos/memories/textual/simple_preference.py +++ b/src/memos/memories/textual/simple_preference.py @@ -90,7 +90,7 @@ def get_with_collection_name( return None return TextualMemoryItem( id=res.id, - memory=res.payload.get("dialog_str", ""), + memory=res.memory, metadata=PreferenceTextualMemoryMetadata(**res.payload), ) except Exception as e: @@ -116,7 +116,7 @@ def get_by_ids_with_collection_name( return [ TextualMemoryItem( id=memo.id, - memory=memo.payload.get("dialog_str", ""), + memory=memo.memory, metadata=PreferenceTextualMemoryMetadata(**memo.payload), ) for memo in res @@ -132,14 +132,14 @@ def get_all(self) -> list[TextualMemoryItem]: Returns: list[TextualMemoryItem]: List of all memories. """ - all_collections = self.vector_db.list_collections() + all_collections = ["explicit_preference", "implicit_preference"] all_memories = {} for collection_name in all_collections: items = self.vector_db.get_all(collection_name) all_memories[collection_name] = [ TextualMemoryItem( id=memo.id, - memory=memo.payload.get("dialog_str", ""), + memory=memo.memory, metadata=PreferenceTextualMemoryMetadata(**memo.payload), ) for memo in items diff --git a/src/memos/vec_dbs/milvus.py b/src/memos/vec_dbs/milvus.py index 5dacf0499..cc8909d34 100644 --- a/src/memos/vec_dbs/milvus.py +++ b/src/memos/vec_dbs/milvus.py @@ -457,14 +457,13 @@ def get_by_id(self, collection_name: str, id: str) -> MilvusVecDBItem | None: return None entity = results[0] - payload = {k: v for k, v in entity.items() if k not in ["id", "vector", "score"]} return MilvusVecDBItem( id=entity["id"], memory=entity.get("memory"), original_text=entity.get("original_text"), vector=entity.get("vector"), - payload=payload, + payload=entity.get("payload", {}), ) def get_by_ids(self, collection_name: str, ids: list[str]) -> list[MilvusVecDBItem]: @@ -479,14 +478,13 @@ def get_by_ids(self, collection_name: str, ids: list[str]) -> list[MilvusVecDBIt items = [] for entity in results: - payload = {k: v for k, v in entity.items() if k not in ["id", "vector", "score"]} items.append( MilvusVecDBItem( id=entity["id"], memory=entity.get("memory"), original_text=entity.get("original_text"), vector=entity.get("vector"), - payload=payload, + payload=entity.get("payload", {}), ) ) From d58f1d793348e09daeb7bea9f94be788ebda00b9 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Thu, 8 Jan 2026 15:13:25 +0800 Subject: [PATCH 9/9] add get memory by memory id api --- src/memos/api/handlers/memory_handler.py | 43 ++++++++++++++++++++++++ src/memos/api/routers/server_router.py | 8 +++++ 2 files changed, 51 insertions(+) diff --git a/src/memos/api/handlers/memory_handler.py b/src/memos/api/handlers/memory_handler.py index ef829d757..90c0886f7 100644 --- a/src/memos/api/handlers/memory_handler.py +++ b/src/memos/api/handlers/memory_handler.py @@ -176,6 +176,49 @@ def handle_get_subgraph( raise +def handle_get_memory(memory_id: str, naive_mem_cube: NaiveMemCube) -> GetMemoryResponse: + """ + Handler for getting a single memory by its ID. + + Tries to retrieve from text memory first, then preference memory if not found. + + Args: + memory_id: The ID of the memory to retrieve + naive_mem_cube: Memory cube instance + + Returns: + GetMemoryResponse with the memory data + """ + + try: + memory = naive_mem_cube.text_mem.get(memory_id) + except Exception: + memory = None + + # If not found in text memory, try preference memory + pref = None + if memory is None and naive_mem_cube.pref_mem is not None: + collection_names = ["explicit_preference", "implicit_preference"] + for collection_name in collection_names: + try: + pref = naive_mem_cube.pref_mem.get_with_collection_name(collection_name, memory_id) + if pref is not None: + break + except Exception: + continue + + # Get the data from whichever memory source succeeded + data = (memory or pref).model_dump() if (memory or pref) else None + + return GetMemoryResponse( + message="Memory retrieved successfully" + if data + else f"Memory with ID {memory_id} not found", + code=200, + data=data, + ) + + def handle_get_memories( get_mem_req: GetMemoryRequest, naive_mem_cube: NaiveMemCube ) -> GetMemoryResponse: diff --git a/src/memos/api/routers/server_router.py b/src/memos/api/routers/server_router.py index a4052d313..8371c41b9 100644 --- a/src/memos/api/routers/server_router.py +++ b/src/memos/api/routers/server_router.py @@ -314,6 +314,14 @@ def get_memories(memory_req: GetMemoryRequest): ) +@router.get("/get_memory/{memory_id}", summary="Get memory by id", response_model=GetMemoryResponse) +def get_memory_by_id(memory_id: str): + return handlers.memory_handler.handle_get_memory( + memory_id=memory_id, + naive_mem_cube=naive_mem_cube, + ) + + @router.post( "/delete_memory", summary="Delete memories for user", response_model=DeleteMemoryResponse )