From abcbe06439df7e3904e0189bceb740e9ed1b7324 Mon Sep 17 00:00:00 2001 From: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:03:43 +0800 Subject: [PATCH] fix: filter empty preference memory items --- .../process_preference_memory.py | 31 +++++--- tests/mem_reader/test_preference_memory.py | 74 +++++++++++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 tests/mem_reader/test_preference_memory.py diff --git a/src/memos/mem_reader/read_pref_memory/process_preference_memory.py b/src/memos/mem_reader/read_pref_memory/process_preference_memory.py index 5c896ca90..411ad631f 100644 --- a/src/memos/mem_reader/read_pref_memory/process_preference_memory.py +++ b/src/memos/mem_reader/read_pref_memory/process_preference_memory.py @@ -39,7 +39,9 @@ def _extract_explicit_preference(qa_pair_str: str, llm) -> list[dict[str, Any]] response = llm.generate([{"role": "user", "content": prompt}]) if not response: logger.info( - f"[prefer_extractor]: (Error) LLM response content is {response} when extracting explicit preference" + "[prefer_extractor]: (Error) LLM response content is %s when extracting " + "explicit preference", + response, ) return None response = response.strip().replace("```json", "").replace("```", "").strip() @@ -68,7 +70,9 @@ def _extract_implicit_preference(qa_pair_str: str, llm) -> list[dict[str, Any]] response = llm.generate([{"role": "user", "content": prompt}]) if not response: logger.info( - f"[prefer_extractor]: (Error) LLM response content is {response} when extracting implicit preference" + "[prefer_extractor]: (Error) LLM response content is %s when extracting " + "implicit preference", + response, ) return None response = response.strip().replace("```json", "").replace("```", "").strip() @@ -135,6 +139,8 @@ def _create_preference_memory_item( # Extract sources from fast_item sources = getattr(fast_item.metadata, "sources", []) if fast_item else [] + preference = _normalized_preference(preference_data) + # Create metadata metadata = TreeNodeTextualMemoryMetadata( memory_type="PreferenceMemory", @@ -150,7 +156,7 @@ def _create_preference_memory_item( background="", # Preference-specific fields preference_type=preference_type, - preference=preference_data.get("preference", ""), + preference=preference, reasoning=preference_data.get("reasoning", ""), topic=preference_data.get("topic", ""), # User-specific fields @@ -162,6 +168,11 @@ def _create_preference_memory_item( return TextualMemoryItem(id=str(uuid.uuid4()), memory=context_summary, metadata=metadata) +def _normalized_preference(preference_data: dict[str, Any]) -> str: + preference = preference_data.get("preference") + return preference.strip() if isinstance(preference, str) else "" + + def _process_single_chunk_explicit( original_text: str, fast_item: TextualMemoryItem | None, @@ -180,10 +191,8 @@ def _process_single_chunk_explicit( memories = [] for pref in explicit_pref: - normalized_pref = _normalize_preference_data(pref) - if not normalized_pref: + if not _normalized_preference(pref): continue - memory = _create_preference_memory_item( preference_data=normalized_pref, preference_type="explicit_preference", @@ -215,10 +224,8 @@ def _process_single_chunk_implicit( memories = [] for pref in implicit_pref: - normalized_pref = _normalize_preference_data(pref) - if not normalized_pref: + if not _normalized_preference(pref): continue - memory = _create_preference_memory_item( preference_data=normalized_pref, preference_type="implicit_preference", @@ -302,7 +309,11 @@ def process_preference_fine( except Exception as e: task_type, chunk = futures[future] logger.warning( - f"[process_preference_fine] Error processing {task_type} chunk, original text: {chunk}: {e}" + "[process_preference_fine] Error processing %s chunk, original text: " + "%s: %s", + task_type, + chunk, + e, ) continue diff --git a/tests/mem_reader/test_preference_memory.py b/tests/mem_reader/test_preference_memory.py new file mode 100644 index 000000000..061a152a6 --- /dev/null +++ b/tests/mem_reader/test_preference_memory.py @@ -0,0 +1,74 @@ +import json + +from unittest.mock import MagicMock + +from memos.mem_reader.read_pref_memory.process_preference_memory import ( + _process_single_chunk_explicit, + _process_single_chunk_implicit, +) + + +def test_explicit_preference_processing_skips_empty_preference_items() -> None: + llm = MagicMock() + llm.generate.return_value = json.dumps( + [ + { + "explicit_preference": " ", + "context_summary": "Empty preference should not be stored.", + "reasoning": "No usable preference.", + "topic": "style", + }, + { + "explicit_preference": " Prefers concise answers. ", + "context_summary": "The user asked for concise answers.", + "reasoning": "The user explicitly requested concise answers.", + "topic": "style", + }, + ] + ) + embedder = MagicMock() + embedder.embed.return_value = [[0.1, 0.2]] + + memories = _process_single_chunk_explicit( + "user: keep answers short", + fast_item=None, + info={"user_id": "u1", "session_id": "s1"}, + llm=llm, + embedder=embedder, + ) + + assert len(memories) == 1 + assert memories[0].metadata.preference == "Prefers concise answers." + + +def test_implicit_preference_processing_skips_missing_preference_items() -> None: + llm = MagicMock() + llm.generate.return_value = json.dumps( + [ + { + "implicit_preference": "", + "context_summary": "No implicit preference can be inferred.", + "reasoning": "Insufficient evidence.", + "topic": "movies", + }, + { + "implicit_preference": "Prefers science fiction movies.", + "context_summary": "The user repeatedly chose science fiction movies.", + "reasoning": "Repeated choices indicate a preference.", + "topic": "movies", + }, + ] + ) + embedder = MagicMock() + embedder.embed.return_value = [[0.1, 0.2]] + + memories = _process_single_chunk_implicit( + "user: let's watch another space movie", + fast_item=None, + info={"user_id": "u1", "session_id": "s1"}, + llm=llm, + embedder=embedder, + ) + + assert len(memories) == 1 + assert memories[0].metadata.preference == "Prefers science fiction movies."