Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions src/memos/mem_reader/read_pref_memory/process_preference_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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

Expand Down
74 changes: 74 additions & 0 deletions tests/mem_reader/test_preference_memory.py
Original file line number Diff line number Diff line change
@@ -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."