From 14169cd08bd6748c79b25d9962f29b46267970e2 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Tue, 10 Feb 2026 16:07:56 +0800 Subject: [PATCH 1/5] fix: fix bug in handle_get_memories_dashboard --- src/memos/api/handlers/memory_handler.py | 22 ++++++++++--- .../read_skill_memory/process_skill_memory.py | 32 ++++++++----------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/memos/api/handlers/memory_handler.py b/src/memos/api/handlers/memory_handler.py index a3430d475..df9f70e77 100644 --- a/src/memos/api/handlers/memory_handler.py +++ b/src/memos/api/handlers/memory_handler.py @@ -412,6 +412,12 @@ def handle_get_memories_dashboard( get_mem_req: GetMemoryDashboardRequest, naive_mem_cube: NaiveMemCube ) -> GetMemoryResponse: results: dict[str, Any] = {"text_mem": [], "pref_mem": [], "tool_mem": [], "skill_mem": []} + # for statistics + total_text_nodes, total_tool_nodes, total_skill_nodes, total_preference_nodes = 0, 0, 0, 0 + total_tool_nodes = 0 + total_skill_nodes = 0 + total_preference_nodes = 0 + text_memory_type = ["WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"] text_memories_info = naive_mem_cube.text_mem.get_all( user_name=get_mem_req.mem_cube_id, @@ -421,7 +427,7 @@ def handle_get_memories_dashboard( filter=get_mem_req.filter, memory_type=text_memory_type, ) - text_memories, _ = text_memories_info["nodes"], text_memories_info["total_nodes"] + text_memories, total_text_nodes = text_memories_info["nodes"], text_memories_info["total_nodes"] # Group text memories by cube_id from metadata.user_name text_mem_by_cube: dict[str, list] = {} @@ -453,7 +459,7 @@ def handle_get_memories_dashboard( filter=get_mem_req.filter, memory_type=["ToolSchemaMemory", "ToolTrajectoryMemory"], ) - tool_memories, _ = ( + tool_memories, total_tool_nodes = ( tool_memories_info["nodes"], tool_memories_info["total_nodes"], ) @@ -488,7 +494,7 @@ def handle_get_memories_dashboard( filter=get_mem_req.filter, memory_type=["SkillMemory"], ) - skill_memories, _ = ( + skill_memories, total_skill_nodes = ( skill_memories_info["nodes"], skill_memories_info["total_nodes"], ) @@ -515,7 +521,6 @@ def handle_get_memories_dashboard( ] preferences: list[TextualMemoryItem] = [] - total_preference_nodes = 0 format_preferences = [] if get_mem_req.include_preference and naive_mem_cube.pref_mem is not None: @@ -578,4 +583,13 @@ def handle_get_memories_dashboard( "skill_mem": results.get("skill_mem", []), } + # statistics + statistics = { + "total_text_nodes": total_text_nodes, + "total_tool_nodes": total_tool_nodes, + "total_skill_nodes": total_skill_nodes, + "total_preference_nodes": total_preference_nodes, + } + filtered_results["statistics"] = statistics + return GetMemoryResponse(message="Memories retrieved successfully", data=filtered_results) diff --git a/src/memos/mem_reader/read_skill_memory/process_skill_memory.py b/src/memos/mem_reader/read_skill_memory/process_skill_memory.py index fa799e759..5699c9447 100644 --- a/src/memos/mem_reader/read_skill_memory/process_skill_memory.py +++ b/src/memos/mem_reader/read_skill_memory/process_skill_memory.py @@ -641,26 +641,22 @@ def _rewrite_query(task_type: str, messages: MessageList, llm: BaseLLM, rewrite_ ) prompt = [{"role": "user", "content": prompt_content}] - # Call LLM to rewrite the query with retry logic - for attempt in range(3): - try: - response_text = llm.generate(prompt) - # Clean up response (remove any markdown formatting if present) - response_text = response_text.strip() - logger.info(f"[PROCESS_SKILLS] Rewritten query for task '{task_type}': {response_text}") - return response_text - except Exception as e: + # Call LLM to rewrite the query + try: + response_text = llm.generate(prompt) + # Clean up response (remove any markdown formatting if present) + if response_text and isinstance(response_text, str): + return response_text.strip() + else: logger.warning( - f"[PROCESS_SKILLS] LLM query rewrite failed (attempt {attempt + 1}): {e}" + "[PROCESS_SKILLS] LLM returned empty or invalid response, returning first message content" ) - if attempt == 2: - logger.warning( - "[PROCESS_SKILLS] LLM query rewrite failed after 3 retries, returning first message content" - ) - return messages[0]["content"] if messages else "" - - # Fallback (should not reach here due to return in exception handling) - return messages[0]["content"] if messages else "" + return messages[0]["content"] if messages else "" + except Exception as e: + logger.warning( + f"[PROCESS_SKILLS] LLM query rewrite failed: {e}, returning first message content" + ) + return messages[0]["content"] if messages else "" @require_python_package( From 471a6d019f4a578dab1ba4a7732412b8ff944625 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Tue, 10 Feb 2026 16:25:10 +0800 Subject: [PATCH 2/5] feat: chat complete, stream, business add relativity --- src/memos/api/handlers/chat_handler.py | 3 +++ src/memos/api/product_models.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/memos/api/handlers/chat_handler.py b/src/memos/api/handlers/chat_handler.py index cd33a7aeb..2e9032f11 100644 --- a/src/memos/api/handlers/chat_handler.py +++ b/src/memos/api/handlers/chat_handler.py @@ -130,6 +130,7 @@ def handle_chat_complete(self, chat_req: APIChatCompleteRequest) -> dict[str, An include_preference=chat_req.include_preference, pref_top_k=chat_req.pref_top_k, filter=chat_req.filter, + relativity=chat_req.relativity, ) search_response = self.search_handler.handle_search_memories(search_req) @@ -269,6 +270,7 @@ def generate_chat_response() -> Generator[str, None, None]: include_preference=chat_req.include_preference, pref_top_k=chat_req.pref_top_k, filter=chat_req.filter, + relativity=chat_req.relativity, ) search_response = self.search_handler.handle_search_memories(search_req) @@ -811,6 +813,7 @@ def generate_chat_response() -> Generator[str, None, None]: include_preference=chat_req.include_preference, pref_top_k=chat_req.pref_top_k, filter=chat_req.filter, + relativity=chat_req.relativity, ) search_response = self.search_handler.handle_search_memories(search_req) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index c41526e33..6fc03e735 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -98,6 +98,15 @@ class ChatRequest(BaseRequest): add_message_on_answer: bool = Field(True, description="Add dialogs to memory after chat") manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") + relativity: float = Field( + 0.0, + ge=0, + description=( + "Relevance threshold for recalled memories. " + "Only memories with metadata.relativity >= relativity will be returned. " + "Use 0 to disable threshold filtering. Default: 0.3." + ), + ) # ==== Filter conditions ==== filter: dict[str, Any] | None = Field( @@ -775,6 +784,15 @@ class APIChatCompleteRequest(BaseRequest): add_message_on_answer: bool = Field(True, description="Add dialogs to memory after chat") manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") + relativity: float = Field( + 0.0, + ge=0, + description=( + "Relevance threshold for recalled memories. " + "Only memories with metadata.relativity >= relativity will be returned. " + "Use 0 to disable threshold filtering. Default: 0.3." + ), + ) # ==== Filter conditions ==== filter: dict[str, Any] | None = Field( From f7f7c729aa812ca9dda2f10332ebe1203d2f32a4 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Tue, 10 Feb 2026 16:55:58 +0800 Subject: [PATCH 3/5] fix: fix skill mem llm call return None problem --- .../mem_reader/read_skill_memory/process_skill_memory.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/memos/mem_reader/read_skill_memory/process_skill_memory.py b/src/memos/mem_reader/read_skill_memory/process_skill_memory.py index 5699c9447..d691e53a3 100644 --- a/src/memos/mem_reader/read_skill_memory/process_skill_memory.py +++ b/src/memos/mem_reader/read_skill_memory/process_skill_memory.py @@ -52,6 +52,9 @@ def _generate_content_by_llm(llm: BaseLLM, prompt_template: str, **kwargs) -> An try: prompt = prompt_template.format(**kwargs) response = llm.generate([{"role": "user", "content": prompt}]) + if not response: + logger.warning("[PROCESS_SKILLS] LLM returned empty or invalid response") + return {} if "json" in prompt_template.lower() else "" if "json" in prompt_template.lower(): response = response.replace("```json", "").replace("```", "").strip() return json.loads(response) @@ -436,6 +439,9 @@ def _extract_skill_memory_by_llm( skills_llm = os.getenv("SKILLS_LLM", None) llm_kwargs = {"model_name_or_path": skills_llm} if skills_llm else {} response_text = llm.generate(prompt, **llm_kwargs) + if not response_text: + logger.warning("[PROCESS_SKILLS] LLM returned empty or invalid response") + continue # Clean up response (remove Markdown code blocks if present) logger.info(f"[Skill Memory]: response_text {response_text}") response_text = response_text.strip() @@ -561,6 +567,9 @@ def _extract_skill_memory_by_llm_md( skills_llm = os.getenv("SKILLS_LLM", None) llm_kwargs = {"model_name_or_path": skills_llm} if skills_llm else {} response_text = llm.generate(prompt, **llm_kwargs) + if not response_text: + logger.warning("[PROCESS_SKILLS] LLM returned empty or invalid response") + continue # Clean up response (remove Markdown code blocks if present) logger.info(f"[Skill Memory]: response_text {response_text}") response_text = response_text.strip() From 15fcb67f01f9792c7faa21cb132a546c857c4186 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Thu, 12 Feb 2026 16:05:48 +0800 Subject: [PATCH 4/5] fix: pref info bug in get memory --- src/memos/api/handlers/memory_handler.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/memos/api/handlers/memory_handler.py b/src/memos/api/handlers/memory_handler.py index df9f70e77..73b1ac698 100644 --- a/src/memos/api/handlers/memory_handler.py +++ b/src/memos/api/handlers/memory_handler.py @@ -345,6 +345,25 @@ def handle_get_memories( ) format_preferences = [format_memory_item(item, save_sources=False) for item in preferences] + # For each returned item, tackle with metadata.info project_id / + # operation / manager_user_id + for item in format_preferences: + if not isinstance(item, dict): + continue + metadata = item.get("metadata") + if not isinstance(metadata, dict): + continue + info = metadata.get("info") + if not isinstance(info, dict): + continue + + for key in ("project_id", "operation", "manager_user_id"): + if key not in info: + continue + value = info.pop(key) + if key not in metadata: + metadata[key] = value + results = post_process_pref_mem( results, format_preferences, get_mem_req.mem_cube_id, get_mem_req.include_preference ) From 968f098131b804a7bcb447dffc26b817b6020707 Mon Sep 17 00:00:00 2001 From: "yuan.wang" Date: Thu, 12 Feb 2026 16:47:27 +0800 Subject: [PATCH 5/5] fix: info field problem --- src/memos/api/handlers/memory_handler.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/memos/api/handlers/memory_handler.py b/src/memos/api/handlers/memory_handler.py index 73b1ac698..ef56c7489 100644 --- a/src/memos/api/handlers/memory_handler.py +++ b/src/memos/api/handlers/memory_handler.py @@ -202,6 +202,20 @@ def handle_get_memory(memory_id: str, naive_mem_cube: NaiveMemCube) -> GetMemory # Get the data from whichever memory source succeeded data = (memory or pref).model_dump() if (memory or pref) else None + if data is not None: + # For each returned item, tackle with metadata.info project_id / + # operation / manager_user_id + metadata = data.get("metadata", None) + if metadata is not None and isinstance(metadata, dict): + info = metadata.get("info", None) + if info is not None and isinstance(info, dict): + for key in ("project_id", "operation", "manager_user_id"): + if key not in info: + continue + value = info.pop(key) + if key not in metadata: + metadata[key] = value + return GetMemoryResponse( message="Memory retrieved successfully" if data @@ -241,6 +255,25 @@ def handle_get_memory_by_ids( except Exception: continue + # For each returned item, tackle with metadata.info project_id / + # operation / manager_user_id + for item in memories: + if not isinstance(item, dict): + continue + metadata = item.get("metadata") + if not isinstance(metadata, dict): + continue + info = metadata.get("info") + if not isinstance(info, dict): + continue + + for key in ("project_id", "operation", "manager_user_id"): + if key not in info: + continue + value = info.pop(key) + if key not in metadata: + metadata[key] = value + return GetMemoryResponse( message="Memories retrieved successfully", code=200, data={"memories": memories} )