mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-10-07 03:00:27 +00:00
228 lines
8.5 KiB
Python
228 lines
8.5 KiB
Python
"""Fuse reranking operation for personal memories.
|
|
|
|
This module provides functionality to rerank memory nodes by combining scores,
|
|
memory types, and temporal relevance to improve retrieval quality.
|
|
"""
|
|
|
|
from typing import Dict, List
|
|
|
|
from flowllm.core.context import C
|
|
from flowllm.core.op import BaseAsyncOp
|
|
from loguru import logger
|
|
|
|
from reme_ai.constants.common_constants import EXTRACT_TIME_DICT
|
|
from reme_ai.schema.memory import BaseMemory
|
|
|
|
|
|
@C.register_op()
|
|
class FuseRerankOp(BaseAsyncOp):
|
|
"""
|
|
Reranks the memory nodes by scores, types, and temporal relevance. Formats the top-K reranked nodes to print.
|
|
"""
|
|
|
|
file_path: str = __file__
|
|
|
|
@staticmethod
|
|
def match_memory_time(extract_time_dict: Dict[str, str], memory: BaseMemory):
|
|
"""
|
|
Determines whether the memory is relevant based on time matching.
|
|
|
|
Args:
|
|
extract_time_dict: Dictionary containing extracted time information
|
|
memory: Memory object to check for time relevance
|
|
|
|
Returns:
|
|
Tuple of (match_event_flag, match_msg_flag) indicating temporal matches
|
|
"""
|
|
if extract_time_dict:
|
|
match_event_flag = True
|
|
for k, v in extract_time_dict.items():
|
|
event_value = memory.metadata.get(f"event_{k}", "")
|
|
if event_value in ["-1", v]:
|
|
continue
|
|
match_event_flag = False
|
|
break
|
|
|
|
match_msg_flag = True
|
|
for k, v in extract_time_dict.items():
|
|
msg_value = memory.metadata.get(f"msg_{k}", "")
|
|
if msg_value == v:
|
|
continue
|
|
match_msg_flag = False
|
|
break
|
|
else:
|
|
match_event_flag = False
|
|
match_msg_flag = False
|
|
|
|
memory.metadata["match_event_flag"] = str(int(match_event_flag))
|
|
memory.metadata["match_msg_flag"] = str(int(match_msg_flag))
|
|
return match_event_flag, match_msg_flag
|
|
|
|
async def async_execute(self):
|
|
"""
|
|
Executes the reranking process on memories considering their scores, types, and temporal relevance.
|
|
|
|
This method performs the following steps:
|
|
1. Retrieves extraction time data and a list of memories from the context.
|
|
2. Reranks memories based on a combination of their original score, type,
|
|
and temporal alignment with extracted events/messages.
|
|
3. Selects the top-K reranked memories according to the predefined threshold.
|
|
4. Formats the final list of memories for output.
|
|
5. Sets both response.answer and response.metadata["memory_list"]
|
|
"""
|
|
# Get operation parameters
|
|
fuse_score_threshold = self.op_params.get("fuse_score_threshold", 0.1)
|
|
fuse_ratio_dict = self.op_params.get(
|
|
"fuse_ratio_dict",
|
|
{
|
|
"conversation": 0.5,
|
|
"observation": 1,
|
|
"obs_customized": 1.2,
|
|
"insight": 2.0,
|
|
},
|
|
)
|
|
fuse_time_ratio = self.op_params.get("fuse_time_ratio", 2.0)
|
|
output_memory_max_count = self.op_params.get("output_memory_max_count", 5)
|
|
|
|
# Parse input parameters from the context
|
|
extract_time_dict: Dict[str, str] = self.context.get(EXTRACT_TIME_DICT, {})
|
|
memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
|
|
|
|
# Check if memories are available; warn and return if not
|
|
if not memory_list:
|
|
logger.warning("No memories available for fuse reranking")
|
|
self.context.response.answer = ""
|
|
self.context.response.metadata["memory_list"] = []
|
|
return
|
|
|
|
logger.info(f"Fuse reranking {len(memory_list)} memories with time dict: {bool(extract_time_dict)}")
|
|
|
|
# Perform reranking based on score, type, and time relevance
|
|
reranked_memories = self._apply_fuse_reranking(
|
|
memory_list,
|
|
extract_time_dict,
|
|
fuse_score_threshold,
|
|
fuse_ratio_dict,
|
|
fuse_time_ratio,
|
|
)
|
|
|
|
# Sort and select top-k memories
|
|
reranked_memories = sorted(
|
|
reranked_memories,
|
|
key=lambda x: x.score or 0.0,
|
|
reverse=True,
|
|
)[:output_memory_max_count]
|
|
|
|
logger.info(f"Final reranked memories: {len(reranked_memories)}")
|
|
|
|
# Format memories for output
|
|
formatted_memories = self._format_memories_for_output(reranked_memories)
|
|
|
|
# Store results in context - both answer and metadata as required
|
|
self.context.response.metadata["memory_list"] = reranked_memories
|
|
self.context.response.answer = "\n".join(formatted_memories)
|
|
|
|
def _apply_fuse_reranking(
|
|
self,
|
|
memory_list: List[BaseMemory],
|
|
extract_time_dict: Dict[str, str],
|
|
fuse_score_threshold: float,
|
|
fuse_ratio_dict: Dict[str, float],
|
|
fuse_time_ratio: float,
|
|
) -> List[BaseMemory]:
|
|
"""
|
|
Apply fuse reranking logic to memories.
|
|
|
|
Args:
|
|
memory_list: List of memories to rerank
|
|
extract_time_dict: Dictionary containing extracted time information
|
|
fuse_score_threshold: Minimum score threshold for memories
|
|
fuse_ratio_dict: Dictionary mapping memory types to score multipliers
|
|
fuse_time_ratio: Multiplier for time-relevant memories
|
|
|
|
Returns:
|
|
List of reranked memories with updated scores
|
|
"""
|
|
reranked_memories = []
|
|
|
|
for memory in memory_list:
|
|
# Skip memories below the fuse score threshold
|
|
memory_score = memory.score or 0.0
|
|
if memory_score < fuse_score_threshold:
|
|
continue
|
|
|
|
# Calculate type-based adjustment factor
|
|
memory_type = memory.metadata.get("memory_type", "default")
|
|
if memory_type not in fuse_ratio_dict:
|
|
logger.debug(f"Memory type '{memory_type}' not in fuse_ratio_dict, using default 0.1")
|
|
type_ratio: float = fuse_ratio_dict.get(memory_type, 0.1)
|
|
|
|
# Determine time relevance adjustment factor
|
|
match_event_flag, match_msg_flag = self.match_memory_time(extract_time_dict, memory)
|
|
time_ratio: float = fuse_time_ratio if match_event_flag or match_msg_flag else 1.0
|
|
|
|
# Apply reranking score adjustments
|
|
original_score = memory_score
|
|
memory.score = memory_score * type_ratio * time_ratio
|
|
|
|
logger.debug(
|
|
f"Memory reranked: {original_score:.3f} -> {memory.score:.3f} "
|
|
f"(type={type_ratio}, time={time_ratio})",
|
|
)
|
|
|
|
reranked_memories.append(memory)
|
|
|
|
return reranked_memories
|
|
|
|
def _format_memories_for_output(self, memories: List[BaseMemory]) -> List[str]:
|
|
"""
|
|
Format memories for final output.
|
|
|
|
Args:
|
|
memories: List of memories to format
|
|
|
|
Returns:
|
|
List of formatted memory strings
|
|
"""
|
|
formatted_memories = []
|
|
|
|
for memory in memories:
|
|
# Log reranking details
|
|
logger.info(
|
|
f"Final memory: Score={memory.score:.3f}, "
|
|
f"Event={memory.metadata.get('match_event_flag', '0')}, "
|
|
f"Msg={memory.metadata.get('match_msg_flag', '0')}, "
|
|
f"Content={memory.content[:50]}...",
|
|
)
|
|
|
|
# Format memory with timestamp if available
|
|
formatted_content = self._format_memory_with_timestamp(memory, self.language)
|
|
formatted_memories.append(formatted_content)
|
|
|
|
return formatted_memories
|
|
|
|
@staticmethod
|
|
def _format_memory_with_timestamp(memory, language: str = "en") -> str:
|
|
"""
|
|
Format memory content with timestamp if available.
|
|
|
|
Args:
|
|
memory: Memory object
|
|
language: Language for formatting
|
|
|
|
Returns:
|
|
Formatted memory content string
|
|
"""
|
|
try:
|
|
if hasattr(memory, "timestamp") and memory.timestamp:
|
|
from reme_ai.utils.datetime_handler import DatetimeHandler
|
|
|
|
dt_handler = DatetimeHandler(memory.timestamp)
|
|
datetime_str = dt_handler.datetime_format("%Y-%m-%d %H:%M:%S")
|
|
weekday = dt_handler.get_dt_info_dict(language)["weekday"]
|
|
return f"[{datetime_str} {weekday}] {memory.content}"
|
|
else:
|
|
return memory.content
|
|
except Exception as e:
|
|
logger.warning(f"Failed to format memory with timestamp: {e}")
|
|
return memory.content
|