Skip to content

Commit 87b5358

Browse files
committed
feat: add strategy for fine search
1 parent 94d456b commit 87b5358

File tree

7 files changed

+137
-18
lines changed

7 files changed

+137
-18
lines changed

examples/mem_scheduler/api_w_scheduler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
print(f"Queue maxsize: {getattr(mem_scheduler.memos_message_queue, 'maxsize', 'N/A')}")
1616
print("=====================================\n")
1717

18+
mem_scheduler.memos_message_queue.debug_mode_on()
1819
queue = mem_scheduler.memos_message_queue
1920
queue.clear()
2021

src/memos/mem_scheduler/base_scheduler.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ def initialize_modules(
206206

207207
# start queue monitor if enabled and a bot is set later
208208

209+
def debug_mode_on(self):
210+
self.memos_message_queue.debug_mode_on()
211+
209212
def _cleanup_on_init_failure(self):
210213
"""Clean up resources if initialization fails."""
211214
try:

src/memos/mem_scheduler/memory_manage_modules/retriever.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from memos.mem_scheduler.schemas.general_schemas import (
1212
DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE,
1313
DEFAULT_SCHEDULER_RETRIEVER_RETRIES,
14+
FINE_STRATEGY,
15+
FineStrategy,
1416
TreeTextMemory_FINE_SEARCH_METHOD,
1517
TreeTextMemory_SEARCH_METHOD,
1618
)
@@ -93,9 +95,15 @@ def _build_enhancement_prompt(self, query_history: list[str], batch_texts: list[
9395
if len(query_history) > 1
9496
else query_history[0]
9597
)
96-
text_memories = "\n".join([f"- {mem}" for i, mem in enumerate(batch_texts)])
98+
# Include numbering for rewrite mode to help LLM reference original memory IDs
99+
if FINE_STRATEGY == FineStrategy.REWRITE:
100+
text_memories = "\n".join([f"- [{i}] {mem}" for i, mem in enumerate(batch_texts)])
101+
prompt_name = "memory_rewrite_enhancement"
102+
else:
103+
text_memories = "\n".join([f"- {mem}" for i, mem in enumerate(batch_texts)])
104+
prompt_name = "memory_recreate_enhancement"
97105
return self.build_prompt(
98-
"memory_enhancement",
106+
prompt_name,
99107
query_history=query_history,
100108
memories=text_memories,
101109
)
@@ -109,9 +117,11 @@ def _process_enhancement_batch(
109117
) -> tuple[list[TextualMemoryItem], bool]:
110118
attempt = 0
111119
text_memories = [one.memory for one in memories]
120+
112121
prompt = self._build_enhancement_prompt(
113122
query_history=query_history, batch_texts=text_memories
114123
)
124+
115125
llm_response = None
116126
while attempt <= max(0, retries) + 1:
117127
try:
@@ -121,14 +131,51 @@ def _process_enhancement_batch(
121131
# create new
122132
enhanced_memories = []
123133
user_id = memories[0].metadata.user_id
124-
for new_mem in processed_text_memories:
125-
enhanced_memories.append(
126-
TextualMemoryItem(
127-
memory=new_mem, metadata=TextualMemoryMetadata(user_id=user_id)
134+
if FINE_STRATEGY == FineStrategy.RECREATE:
135+
for new_mem in processed_text_memories:
136+
enhanced_memories.append(
137+
TextualMemoryItem(
138+
memory=new_mem, metadata=TextualMemoryMetadata(user_id=user_id)
139+
)
128140
)
129-
)
141+
elif FINE_STRATEGY == FineStrategy.REWRITE:
142+
# Parse index from each processed line and rewrite corresponding original memory
143+
def _parse_index_and_text(s: str) -> tuple[int | None, str]:
144+
import re
145+
146+
s = (s or "").strip()
147+
# Preferred: [index] text
148+
m = re.match(r"^\s*\[(\d+)\]\s*(.+)$", s)
149+
if m:
150+
return int(m.group(1)), m.group(2).strip()
151+
# Fallback: index: text or index - text
152+
m = re.match(r"^\s*(\d+)\s*[:\-\)]\s*(.+)$", s)
153+
if m:
154+
return int(m.group(1)), m.group(2).strip()
155+
return None, s
156+
157+
idx_to_original = dict(enumerate(memories))
158+
for j, item in enumerate(processed_text_memories):
159+
idx, new_text = _parse_index_and_text(item)
160+
if idx is not None and idx in idx_to_original:
161+
orig = idx_to_original[idx]
162+
else:
163+
# Fallback: align by order if index missing/invalid
164+
orig = memories[j] if j < len(memories) else None
165+
if not orig:
166+
continue
167+
enhanced_memories.append(
168+
TextualMemoryItem(
169+
id=orig.id,
170+
memory=new_text,
171+
metadata=orig.metadata,
172+
)
173+
)
174+
else:
175+
logger.error(f"Fine search strategy {FINE_STRATEGY} not exists")
176+
130177
logger.info(
131-
f"[enhance_memories_with_query] ✅ done | prompt={prompt} | llm_response={llm_response}"
178+
f"[enhance_memories_with_query] ✅ done | Strategy={FINE_STRATEGY} | prompt={prompt} | llm_response={llm_response}"
132179
)
133180
return enhanced_memories, True
134181
else:

src/memos/mem_scheduler/schemas/general_schemas.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
from enum import Enum
24
from pathlib import Path
35
from typing import NewType
@@ -11,6 +13,13 @@ class SearchMode(str, Enum):
1113
MIXTURE = "mixture"
1214

1315

16+
class FineStrategy(str, Enum):
17+
"""Enumeration for fine strategies."""
18+
19+
REWRITE = "rewrite"
20+
RECREATE = "recreate"
21+
22+
1423
FILE_PATH = Path(__file__).absolute()
1524
BASE_DIR = FILE_PATH.parent.parent.parent.parent.parent
1625

@@ -74,3 +83,17 @@ class SearchMode(str, Enum):
7483
# new types
7584
UserID = NewType("UserID", str)
7685
MemCubeID = NewType("CubeID", str)
86+
87+
# algorithm strategies
88+
DEFAULT_FINE_STRATEGY = FineStrategy.REWRITE
89+
90+
# Read fine strategy from environment variable `FINE_STRATEGY`.
91+
# If provided and valid, use it; otherwise fall back to default.
92+
_env_fine_strategy = os.getenv("FINE_STRATEGY")
93+
if _env_fine_strategy:
94+
try:
95+
FINE_STRATEGY = FineStrategy(_env_fine_strategy)
96+
except ValueError:
97+
FINE_STRATEGY = DEFAULT_FINE_STRATEGY
98+
else:
99+
FINE_STRATEGY = DEFAULT_FINE_STRATEGY

src/memos/mem_scheduler/task_schedule_modules/redis_queue.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,6 @@ def get(
177177
try:
178178
stream_key = self.get_stream_key(user_id=user_id, mem_cube_id=mem_cube_id)
179179

180-
if stream_key not in self.seen_streams:
181-
self.seen_streams.add(stream_key)
182-
self._ensure_consumer_group(stream_key=stream_key)
183-
184180
# Calculate timeout for Redis
185181
redis_timeout = None
186182
if block and timeout is not None:
@@ -204,6 +200,7 @@ def get(
204200
logger.warning(
205201
f"Consumer group or stream missing for '{stream_key}/{self.consumer_group}'. Attempting to create and retry."
206202
)
203+
self._ensure_consumer_group(stream_key=stream_key)
207204
messages = self._redis_conn.xreadgroup(
208205
self.consumer_group,
209206
self.consumer_name,
@@ -354,10 +351,9 @@ def clear(self) -> None:
354351

355352
for stream_key in stream_keys:
356353
# Delete the entire stream
357-
self._redis_conn.delete(self.stream_key_prefix)
358-
logger.info(f"Cleared Redis stream: {self.stream_key_prefix}")
359-
# Recreate the consumer group
360-
self._ensure_consumer_group(stream_key=stream_key)
354+
self._redis_conn.delete(stream_key)
355+
logger.info(f"Cleared Redis stream: {stream_key}")
356+
361357
except Exception as e:
362358
logger.error(f"Failed to clear Redis queue: {e}")
363359

src/memos/mem_scheduler/task_schedule_modules/task_queue.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ def __init__(
3535

3636
self.disabled_handlers = disabled_handlers
3737

38+
def debug_mode_on(self):
39+
self.memos_message_queue.stream_key_prefix = (
40+
f"debug_mode:{self.memos_message_queue.stream_key_prefix}"
41+
)
42+
3843
def get_stream_keys(self) -> list[str]:
3944
if isinstance(self.memos_message_queue, SchedulerRedisQueue):
4045
return self.memos_message_queue.get_stream_keys()

src/memos/templates/mem_scheduler_prompts.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@
390390
- Focus on whether the memories can fully answer the query without additional information
391391
"""
392392

393-
MEMORY_ENHANCEMENT_PROMPT = """
393+
MEMORY_RECREATE_ENHANCEMENT_PROMPT = """
394394
You are a knowledgeable and precise AI assistant.
395395
396396
# GOAL
@@ -427,6 +427,49 @@
427427
Final Output:
428428
"""
429429

430+
# Rewrite version: return enhanced memories with original IDs
431+
MEMORY_REWRITE_ENHANCEMENT_PROMPT = """
432+
You are a knowledgeable and precise AI assistant.
433+
434+
# GOAL
435+
Transform raw memories into clean, query-relevant facts — preserving timestamps and resolving ambiguities without inference. Return each enhanced fact with the ID of the original memory being modified.
436+
437+
# RULES & THINKING STEPS
438+
1. Keep ONLY what’s relevant to the user’s query. Delete irrelevant memories entirely.
439+
2. Preserve ALL explicit timestamps (e.g., “on October 6”, “daily”, “after injury”).
440+
3. Resolve all ambiguities using only memory content:
441+
- Pronouns → full name: “she” → “Melanie”
442+
- Vague nouns → specific detail: “home” → “her childhood home in Guangzhou”
443+
- “the user” → identity from context (e.g., “Melanie” if travel/running memories)
444+
4. Never invent, assume, or extrapolate.
445+
5. Each output line must be a standalone, clear, factual statement.
446+
6. Output format: one line per fact, starting with "- ", no extra text.
447+
448+
# IMPORTANT FOR REWRITE
449+
- Each output line MUST include the original memory’s ID shown in the input list.
450+
- Use the index shown for each original memory (e.g., "[0]", "[1]") as the ID to reference which memory you are rewriting.
451+
- For every rewritten line, prefix with the corresponding index in square brackets.
452+
453+
# OUTPUT FORMAT (STRICT)
454+
Return ONLY the following block, with **one enhanced memory per line**.
455+
Each line MUST start with "- " (dash + space) AND include index in square brackets.
456+
457+
Wrap the final output inside:
458+
<answer>
459+
- [index] enhanced memory 1
460+
- [index] enhanced memory 2
461+
...
462+
</answer>
463+
464+
## User Query
465+
{query_history}
466+
467+
## Original Memories
468+
{memories}
469+
470+
Final Output:
471+
"""
472+
430473
# One-sentence prompt for recalling missing information to answer the query (English)
431474
ENLARGE_RECALL_PROMPT_ONE_SENTENCE = """
432475
You are a precise AI assistant. Your job is to analyze the user's query and the available memories to identify what specific information is missing to fully answer the query.
@@ -471,7 +514,8 @@
471514
"memory_redundancy_filtering": MEMORY_REDUNDANCY_FILTERING_PROMPT,
472515
"memory_combined_filtering": MEMORY_COMBINED_FILTERING_PROMPT,
473516
"memory_answer_ability_evaluation": MEMORY_ANSWER_ABILITY_EVALUATION_PROMPT,
474-
"memory_enhancement": MEMORY_ENHANCEMENT_PROMPT,
517+
"memory_recreate_enhancement": MEMORY_RECREATE_ENHANCEMENT_PROMPT,
518+
"memory_rewrite_enhancement": MEMORY_REWRITE_ENHANCEMENT_PROMPT,
475519
"enlarge_recall": ENLARGE_RECALL_PROMPT_ONE_SENTENCE,
476520
}
477521

0 commit comments

Comments
 (0)