reme-ai 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. reme_ai/__init__.py +6 -0
  2. reme_ai/app.py +17 -0
  3. reme_ai/config/__init__.py +0 -0
  4. reme_ai/config/config_parser.py +6 -0
  5. reme_ai/constants/__init__.py +7 -0
  6. reme_ai/constants/common_constants.py +48 -0
  7. reme_ai/constants/language_constants.py +215 -0
  8. reme_ai/enumeration/__init__.py +0 -0
  9. reme_ai/enumeration/language_constants.py +215 -0
  10. reme_ai/react/__init__.py +1 -0
  11. reme_ai/react/simple_react_op.py +21 -0
  12. reme_ai/retrieve/__init__.py +2 -0
  13. reme_ai/retrieve/personal/__init__.py +17 -0
  14. reme_ai/retrieve/personal/extract_time_op.py +97 -0
  15. reme_ai/retrieve/personal/fuse_rerank_op.py +180 -0
  16. reme_ai/retrieve/personal/print_memory_op.py +131 -0
  17. reme_ai/retrieve/personal/read_message_op.py +52 -0
  18. reme_ai/retrieve/personal/retrieve_memory_op.py +13 -0
  19. reme_ai/retrieve/personal/semantic_rank_op.py +170 -0
  20. reme_ai/retrieve/personal/set_query_op.py +37 -0
  21. reme_ai/retrieve/task/__init__.py +4 -0
  22. reme_ai/retrieve/task/build_query_op.py +38 -0
  23. reme_ai/retrieve/task/merge_memory_op.py +27 -0
  24. reme_ai/retrieve/task/rerank_memory_op.py +149 -0
  25. reme_ai/retrieve/task/rewrite_memory_op.py +149 -0
  26. reme_ai/schema/__init__.py +1 -0
  27. reme_ai/schema/memory.py +144 -0
  28. reme_ai/summary/__init__.py +2 -0
  29. reme_ai/summary/personal/__init__.py +8 -0
  30. reme_ai/summary/personal/contra_repeat_op.py +143 -0
  31. reme_ai/summary/personal/get_observation_op.py +147 -0
  32. reme_ai/summary/personal/get_observation_with_time_op.py +165 -0
  33. reme_ai/summary/personal/get_reflection_subject_op.py +179 -0
  34. reme_ai/summary/personal/info_filter_op.py +177 -0
  35. reme_ai/summary/personal/load_today_memory_op.py +117 -0
  36. reme_ai/summary/personal/long_contra_repeat_op.py +210 -0
  37. reme_ai/summary/personal/update_insight_op.py +244 -0
  38. reme_ai/summary/task/__init__.py +10 -0
  39. reme_ai/summary/task/comparative_extraction_op.py +233 -0
  40. reme_ai/summary/task/failure_extraction_op.py +73 -0
  41. reme_ai/summary/task/memory_deduplication_op.py +163 -0
  42. reme_ai/summary/task/memory_validation_op.py +108 -0
  43. reme_ai/summary/task/pdf_preprocess_op_wrapper.py +50 -0
  44. reme_ai/summary/task/simple_comparative_summary_op.py +71 -0
  45. reme_ai/summary/task/simple_summary_op.py +67 -0
  46. reme_ai/summary/task/success_extraction_op.py +73 -0
  47. reme_ai/summary/task/trajectory_preprocess_op.py +76 -0
  48. reme_ai/summary/task/trajectory_segmentation_op.py +118 -0
  49. reme_ai/utils/__init__.py +0 -0
  50. reme_ai/utils/datetime_handler.py +345 -0
  51. reme_ai/utils/miner_u_pdf_processor.py +726 -0
  52. reme_ai/utils/op_utils.py +115 -0
  53. reme_ai/vector_store/__init__.py +6 -0
  54. reme_ai/vector_store/delete_memory_op.py +25 -0
  55. reme_ai/vector_store/recall_vector_store_op.py +36 -0
  56. reme_ai/vector_store/update_memory_freq_op.py +33 -0
  57. reme_ai/vector_store/update_memory_utility_op.py +32 -0
  58. reme_ai/vector_store/update_vector_store_op.py +32 -0
  59. reme_ai/vector_store/vector_store_action_op.py +55 -0
  60. reme_ai-0.1.0.dist-info/METADATA +218 -0
  61. reme_ai-0.1.0.dist-info/RECORD +65 -0
  62. reme_ai-0.1.0.dist-info/WHEEL +5 -0
  63. reme_ai-0.1.0.dist-info/entry_points.txt +2 -0
  64. reme_ai-0.1.0.dist-info/licenses/LICENSE +201 -0
  65. reme_ai-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,180 @@
1
+ from typing import Dict, List
2
+
3
+ from flowllm import C, BaseLLMOp
4
+ from loguru import logger
5
+
6
+ from reme_ai.constants.common_constants import EXTRACT_TIME_DICT
7
+ from reme_ai.schema.memory import BaseMemory
8
+
9
+
10
+ @C.register_op()
11
+ class FuseRerankOp(BaseLLMOp):
12
+ """
13
+ Reranks the memory nodes by scores, types, and temporal relevance. Formats the top-K reranked nodes to print.
14
+ """
15
+ file_path: str = __file__
16
+
17
+ @staticmethod
18
+ def match_memory_time(extract_time_dict: Dict[str, str], memory: BaseMemory):
19
+ """
20
+ Determines whether the memory is relevant based on time matching.
21
+ """
22
+ if extract_time_dict:
23
+ match_event_flag = True
24
+ for k, v in extract_time_dict.items():
25
+ event_value = memory.metadata.get(f"event_{k}", "")
26
+ if event_value in ["-1", v]:
27
+ continue
28
+ else:
29
+ match_event_flag = False
30
+ break
31
+
32
+ match_msg_flag = True
33
+ for k, v in extract_time_dict.items():
34
+ msg_value = memory.metadata.get(f"msg_{k}", "")
35
+ if msg_value == v:
36
+ continue
37
+ else:
38
+ match_msg_flag = False
39
+ break
40
+ else:
41
+ match_event_flag = False
42
+ match_msg_flag = False
43
+
44
+ memory.metadata["match_event_flag"] = str(int(match_event_flag))
45
+ memory.metadata["match_msg_flag"] = str(int(match_msg_flag))
46
+ return match_event_flag, match_msg_flag
47
+
48
+ def execute(self):
49
+ """
50
+ Executes the reranking process on memories considering their scores, types, and temporal relevance.
51
+
52
+ This method performs the following steps:
53
+ 1. Retrieves extraction time data and a list of memories from the context.
54
+ 2. Reranks memories based on a combination of their original score, type,
55
+ and temporal alignment with extracted events/messages.
56
+ 3. Selects the top-K reranked memories according to the predefined threshold.
57
+ 4. Formats the final list of memories for output.
58
+ 5. Sets both response.answer and response.metadata["memory_list"]
59
+ """
60
+ # Get operation parameters
61
+ fuse_score_threshold = self.op_params.get("fuse_score_threshold", 0.1)
62
+ fuse_ratio_dict = self.op_params.get("fuse_ratio_dict", {
63
+ "conversation": 0.5,
64
+ "observation": 1,
65
+ "obs_customized": 1.2,
66
+ "insight": 2.0
67
+ })
68
+ fuse_time_ratio = self.op_params.get("fuse_time_ratio", 2.0)
69
+ output_memory_max_count = self.op_params.get("output_memory_max_count", 5)
70
+
71
+ # Parse input parameters from the context
72
+ extract_time_dict: Dict[str, str] = self.context.get(EXTRACT_TIME_DICT, {})
73
+ memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
74
+
75
+ # Check if memories are available; warn and return if not
76
+ if not memory_list:
77
+ logger.warning("No memories available for fuse reranking")
78
+ self.context.response.answer = ""
79
+ self.context.response.metadata["memory_list"] = []
80
+ return
81
+
82
+ logger.info(f"Fuse reranking {len(memory_list)} memories with time dict: {bool(extract_time_dict)}")
83
+
84
+ # Perform reranking based on score, type, and time relevance
85
+ reranked_memories = self._apply_fuse_reranking(
86
+ memory_list, extract_time_dict, fuse_score_threshold,
87
+ fuse_ratio_dict, fuse_time_ratio
88
+ )
89
+
90
+ # Sort and select top-k memories
91
+ reranked_memories = sorted(reranked_memories,
92
+ key=lambda x: x.score or 0.0,
93
+ reverse=True)[:output_memory_max_count]
94
+
95
+ logger.info(f"Final reranked memories: {len(reranked_memories)}")
96
+
97
+ # Format memories for output
98
+ formatted_memories = self._format_memories_for_output(reranked_memories)
99
+
100
+ # Store results in context - both answer and metadata as required
101
+ self.context.response.metadata["memory_list"] = reranked_memories
102
+ self.context.response.answer = "\n".join(formatted_memories)
103
+
104
+ def _apply_fuse_reranking(self,
105
+ memory_list: List[BaseMemory],
106
+ extract_time_dict: Dict[str, str],
107
+ fuse_score_threshold: float,
108
+ fuse_ratio_dict: Dict[str, float],
109
+ fuse_time_ratio: float) -> List[BaseMemory]:
110
+ """Apply fuse reranking logic to memories"""
111
+ reranked_memories = []
112
+
113
+ for memory in memory_list:
114
+ # Skip memories below the fuse score threshold
115
+ memory_score = memory.score or 0.0
116
+ if memory_score < fuse_score_threshold:
117
+ continue
118
+
119
+ # Calculate type-based adjustment factor
120
+ memory_type = memory.metadata.get("memory_type", "default")
121
+ if memory_type not in fuse_ratio_dict:
122
+ logger.debug(f"Memory type '{memory_type}' not in fuse_ratio_dict, using default 0.1")
123
+ type_ratio: float = fuse_ratio_dict.get(memory_type, 0.1)
124
+
125
+ # Determine time relevance adjustment factor
126
+ match_event_flag, match_msg_flag = self.match_memory_time(extract_time_dict, memory)
127
+ time_ratio: float = fuse_time_ratio if match_event_flag or match_msg_flag else 1.0
128
+
129
+ # Apply reranking score adjustments
130
+ original_score = memory_score
131
+ memory.score = memory_score * type_ratio * time_ratio
132
+
133
+ logger.debug(f"Memory reranked: {original_score:.3f} -> {memory.score:.3f} "
134
+ f"(type={type_ratio}, time={time_ratio})")
135
+
136
+ reranked_memories.append(memory)
137
+
138
+ return reranked_memories
139
+
140
+ def _format_memories_for_output(self, memories: List[BaseMemory]) -> List[str]:
141
+ """Format memories for final output"""
142
+ formatted_memories = []
143
+
144
+ for memory in memories:
145
+ # Log reranking details
146
+ logger.info(f"Final memory: Score={memory.score:.3f}, "
147
+ f"Event={memory.metadata.get('match_event_flag', '0')}, "
148
+ f"Msg={memory.metadata.get('match_msg_flag', '0')}, "
149
+ f"Content={memory.content[:50]}...")
150
+
151
+ # Format memory with timestamp if available
152
+ formatted_content = self._format_memory_with_timestamp(memory, self.language)
153
+ formatted_memories.append(formatted_content)
154
+
155
+ return formatted_memories
156
+
157
+ @staticmethod
158
+ def _format_memory_with_timestamp(memory, language: str = "en") -> str:
159
+ """
160
+ Format memory content with timestamp if available.
161
+
162
+ Args:
163
+ memory: Memory object
164
+ language: Language for formatting
165
+
166
+ Returns:
167
+ Formatted memory content string
168
+ """
169
+ try:
170
+ if hasattr(memory, 'timestamp') and memory.timestamp:
171
+ from reme_ai.utils.datetime_handler import DatetimeHandler
172
+ dt_handler = DatetimeHandler(memory.timestamp)
173
+ datetime_str = dt_handler.datetime_format("%Y-%m-%d %H:%M:%S")
174
+ weekday = dt_handler.get_dt_info_dict(language)["weekday"]
175
+ return f"[{datetime_str} {weekday}] {memory.content}"
176
+ else:
177
+ return memory.content
178
+ except Exception as e:
179
+ logger.warning(f"Failed to format memory with timestamp: {e}")
180
+ return memory.content
@@ -0,0 +1,131 @@
1
+ from typing import List
2
+
3
+ from flowllm import C, BaseOp
4
+ from loguru import logger
5
+
6
+ from reme_ai.schema.memory import BaseMemory
7
+
8
+
9
+ @C.register_op()
10
+ class PrintMemoryOp(BaseOp):
11
+ """
12
+ Formats the memories to print.
13
+ """
14
+ file_path: str = __file__
15
+
16
+ def execute(self):
17
+ """
18
+ Executes the primary function, it involves:
19
+ 1. Fetches the memories.
20
+ 2. Formats them for printing.
21
+ 3. Set the formatted string back into the context
22
+ """
23
+ # Get memory list from context
24
+ memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
25
+
26
+ if not memory_list:
27
+ logger.info("No memories to print")
28
+ self.context.response.answer = "No memories found."
29
+ return
30
+
31
+ logger.info(f"Formatting {len(memory_list)} memories for printing")
32
+
33
+ # Format memories for printing
34
+ formatted_memories = self._format_memories_for_print(memory_list)
35
+
36
+ # Store result in context
37
+ self.context.response.answer = formatted_memories
38
+ logger.info(f"Formatted memories: {formatted_memories}")
39
+
40
+ @staticmethod
41
+ def _format_memories_for_print(memories: List[BaseMemory]) -> str:
42
+ """Format memories for printing"""
43
+ if not memories:
44
+ return "No memories available."
45
+
46
+ formatted_memories = []
47
+
48
+ for i, memory in enumerate(memories, 1):
49
+ memory_text = f"Memory {i}:\n"
50
+ memory_text += f" When to use: {memory.when_to_use}\n"
51
+ memory_text += f" Content: {memory.content}\n"
52
+
53
+ # Add additional metadata if available
54
+ if hasattr(memory, 'metadata') and memory.metadata:
55
+ metadata_items = []
56
+ for key, value in memory.metadata.items():
57
+ if key not in ['when_to_use', 'content']:
58
+ metadata_items.append(f"{key}: {value}")
59
+ if metadata_items:
60
+ memory_text += f" Metadata: {', '.join(metadata_items)}\n"
61
+
62
+ formatted_memories.append(memory_text)
63
+
64
+ return "\n".join(formatted_memories)
65
+
66
+ @staticmethod
67
+ def format_memories_for_output(memories: List) -> str:
68
+ """
69
+ Format memory list for output string.
70
+
71
+ Args:
72
+ memories: List of memory objects
73
+
74
+ Returns:
75
+ Formatted string
76
+ """
77
+ if not memories:
78
+ return ""
79
+
80
+ formatted_parts = []
81
+ for i, memory in enumerate(memories, 1):
82
+ when_to_use = getattr(memory, 'when_to_use', '') or memory.get('when_to_use', '')
83
+ content = getattr(memory, 'content', '') or memory.get('content', '')
84
+
85
+ part = f"Memory {i}:\n"
86
+ if when_to_use:
87
+ part += f"When to use: {when_to_use}\n"
88
+ if content:
89
+ part += f"Content: {content}\n"
90
+
91
+ formatted_parts.append(part)
92
+
93
+ return "\n".join(formatted_parts)
94
+
95
+ @staticmethod
96
+ def format_memories_for_simple_output(memories: List) -> str:
97
+ """
98
+ Format memory list for simple flow output.
99
+
100
+ Args:
101
+ memories: List of memory objects
102
+
103
+ Returns:
104
+ Formatted string suitable for response.answer
105
+ """
106
+ if not memories:
107
+ return "No relevant memories found."
108
+
109
+ content_parts = ["Previous Memory"]
110
+
111
+ for memory in memories:
112
+ # Safely get field values
113
+ when_to_use = getattr(memory, 'when_to_use', '') or memory.get('when_to_use', '')
114
+ content = getattr(memory, 'content', '') or memory.get('content', '')
115
+
116
+ # Skip memories with empty content
117
+ if not content:
118
+ continue
119
+
120
+ # Format individual memory
121
+ memory_text = f"- when_to_use: {when_to_use}\n content: {content}"
122
+ content_parts.append(memory_text)
123
+
124
+ # If no valid memories, return empty message
125
+ if len(content_parts) == 1: # Only title
126
+ return "No relevant memories with valid content found."
127
+
128
+ content_parts.append("\nPlease consider the helpful parts from these in answering the question, "
129
+ "to make the response more comprehensive and substantial.")
130
+
131
+ return "\n".join(content_parts)
@@ -0,0 +1,52 @@
1
+ from typing import List
2
+
3
+ from flowllm import C, BaseOp
4
+ from flowllm.schema.message import Message
5
+ from loguru import logger
6
+
7
+
8
+ @C.register_op()
9
+ class ReadMessageOp(BaseOp):
10
+ """
11
+ Fetches unmemorized chat messages.
12
+ """
13
+ file_path: str = __file__
14
+
15
+ def execute(self):
16
+ """
17
+ Executes the primary function to fetch unmemorized chat messages.
18
+ """
19
+ # Get chat messages from context
20
+ chat_messages = self.context.chat_messages
21
+ target_name = self.context.target_name
22
+ contextual_msg_max_count = self.op_params.get('contextual_msg_max_count', 10)
23
+
24
+ chat_messages_not_memorized: List[List[Message]] = []
25
+ for messages in chat_messages:
26
+ if not messages:
27
+ continue
28
+
29
+ if hasattr(messages[0], 'memorized') and messages[0].memorized:
30
+ continue
31
+
32
+ contain_flag = False
33
+
34
+ for msg in messages:
35
+ if hasattr(msg, 'role_name') and msg.role_name == target_name:
36
+ contain_flag = True
37
+ break
38
+
39
+ if contain_flag:
40
+ chat_messages_not_memorized.append(messages)
41
+
42
+ chat_message_scatter = []
43
+ for messages in chat_messages_not_memorized[-contextual_msg_max_count:]:
44
+ chat_message_scatter.extend(messages)
45
+
46
+ # Sort by time_created if available
47
+ if chat_message_scatter and hasattr(chat_message_scatter[0], 'time_created'):
48
+ chat_message_scatter.sort(key=lambda _: _.time_created)
49
+
50
+ # Store result in context
51
+ self.context.chat_messages = chat_message_scatter
52
+ logger.info(f"Retrieved {len(chat_message_scatter)} unmemorized chat messages")
@@ -0,0 +1,13 @@
1
+ from flowllm import C
2
+
3
+ from reme_ai.vector_store import RecallVectorStoreOp
4
+
5
+
6
+ @C.register_op()
7
+ class RetrieveMemoryOp(RecallVectorStoreOp):
8
+ """
9
+ Retrieves memories based on specified criteria such as status, type, and timestamp.
10
+ Processes these memories concurrently, sorts them by similarity, and logs the activity,
11
+ facilitating efficient memory retrieval operations within a given scope.
12
+ """
13
+ file_path: str = __file__
@@ -0,0 +1,170 @@
1
+ import json
2
+ import re
3
+ from typing import List
4
+
5
+ from flowllm import C, BaseLLMOp
6
+ from loguru import logger
7
+
8
+ from reme_ai.schema import Message, Role
9
+ from reme_ai.schema.memory import BaseMemory
10
+
11
+
12
+ @C.register_op()
13
+ class SemanticRankOp(BaseLLMOp):
14
+ """
15
+ The SemanticRankOp class processes queries by retrieving memory nodes,
16
+ removing duplicates, ranking them based on semantic relevance using a model,
17
+ assigning scores, sorting the nodes, and storing the ranked nodes back,
18
+ while logging relevant information.
19
+ """
20
+ file_path: str = __file__
21
+
22
+ def execute(self):
23
+ """
24
+ Executes the primary workflow of the SemanticRankOp which includes:
25
+ - Retrieves query and memory list from context.
26
+ - Removes duplicate memories.
27
+ - Ranks memories semantically using LLM.
28
+ - Assigns scores to memories.
29
+ - Sorts memories by score.
30
+ - Saves the ranked memories back to context.
31
+
32
+ If no memories are retrieved or if the ranking fails,
33
+ appropriate warnings are logged.
34
+ """
35
+ # Get memory list from context - previous op guarantees this exists
36
+ memory_list: List[BaseMemory] = self.context.response.metadata["memory_list"]
37
+ query: str = self.context.query
38
+
39
+ # Get parameters from op_params
40
+ enable_ranker: bool = self.op_params.get("enable_ranker", True)
41
+ output_memory_max_count: int = self.op_params.get("output_memory_max_count", 10)
42
+
43
+ if not memory_list:
44
+ logger.warning("Memory list is empty!")
45
+ return
46
+
47
+ logger.info(f"Semantic ranking {len(memory_list)} memories for query: {query[:100]}...")
48
+
49
+ if not enable_ranker or len(memory_list) <= output_memory_max_count:
50
+ # Use original scores if ranker is disabled or memory count is small
51
+ logger.info("Skipping semantic ranking - using original scores")
52
+ else:
53
+ # Remove duplicates based on content
54
+ memory_dict = {memory.content.strip(): memory for memory in memory_list if memory.content.strip()}
55
+ memory_list = list(memory_dict.values())
56
+ logger.info(f"After deduplication: {len(memory_list)} memories")
57
+
58
+ # Perform semantic ranking using LLM
59
+ ranked_memories = self._semantic_rank_memories(query, memory_list)
60
+ if ranked_memories:
61
+ memory_list = ranked_memories
62
+
63
+ # Sort by score
64
+ memory_list = sorted(memory_list, key=lambda m: m.score, reverse=True)
65
+
66
+ # Log top ranked memories
67
+ logger.info(f"Semantic ranking completed for query: {query[:50]}...")
68
+ for i, memory in enumerate(memory_list[:5]): # Log top 5
69
+ logger.info(f"Top {i + 1}: Score={memory.score:.3f}, Content={memory.content[:80]}...")
70
+
71
+ # Save ranked memories back to context
72
+ self.context.response.metadata["memory_list"] = memory_list
73
+
74
+ def _semantic_rank_memories(self, query: str, memories: List[BaseMemory]) -> List[BaseMemory]:
75
+ """
76
+ Use LLM to semantically rank memories based on relevance to the query
77
+ """
78
+ if not memories:
79
+ return memories
80
+
81
+ # Format memories for ranking
82
+ formatted_memories = SemanticRankOp.format_memories_for_llm_ranking(memories)
83
+
84
+ # Create prompt for semantic ranking
85
+ prompt = f"""Given the query: "{query}"
86
+
87
+ Please rank the following memories by their semantic relevance to the query.
88
+ Rate each memory on a scale of 0.0 to 1.0 where 1.0 is most relevant.
89
+
90
+ Memories:
91
+ {formatted_memories}
92
+
93
+ Please respond in JSON format:
94
+ {{"rankings": [{{"index": 0, "score": 0.8}}, {{"index": 1, "score": 0.6}}, ...]}}"""
95
+
96
+ response = self.llm.chat([Message(role=Role.USER, content=prompt)])
97
+
98
+ if not response or not response.content:
99
+ logger.warning("LLM ranking failed, using original order")
100
+ return memories
101
+
102
+ # Parse and apply ranking results
103
+ rankings = SemanticRankOp.parse_llm_ranking_response(response.content)
104
+
105
+ if rankings:
106
+ applied_count = SemanticRankOp.apply_semantic_scores_to_memories(memories, rankings)
107
+ logger.info(f"Successfully applied semantic rankings to {applied_count} memories")
108
+ else:
109
+ logger.warning("Failed to parse ranking results")
110
+
111
+ return memories
112
+
113
+ @staticmethod
114
+ def parse_llm_ranking_response(response: str) -> List[dict]:
115
+ """Parse LLM ranking response to extract rankings."""
116
+ try:
117
+ # Try to extract JSON blocks
118
+ json_pattern = r'```json\s*([\s\S]*?)\s*```'
119
+ json_blocks = re.findall(json_pattern, response)
120
+
121
+ if json_blocks:
122
+ parsed = json.loads(json_blocks[0])
123
+ if isinstance(parsed, dict) and "rankings" in parsed:
124
+ return parsed["rankings"]
125
+
126
+ # Fallback: try to parse the entire response as JSON
127
+ parsed = json.loads(response)
128
+ if isinstance(parsed, dict) and "rankings" in parsed:
129
+ return parsed["rankings"]
130
+
131
+ except json.JSONDecodeError:
132
+ logger.warning("Failed to parse ranking response as JSON")
133
+
134
+ return []
135
+
136
+ @staticmethod
137
+ def apply_semantic_scores_to_memories(memories: List, rankings: List[dict]) -> int:
138
+ """Apply semantic ranking scores to memory objects."""
139
+ applied_count = 0
140
+
141
+ for ranking in rankings:
142
+ idx = ranking.get("index", -1)
143
+ score = ranking.get("score", 0.0)
144
+
145
+ if 0 <= idx < len(memories):
146
+ # Set score on memory object
147
+ if hasattr(memories[idx], 'score'):
148
+ memories[idx].score = score
149
+ applied_count += 1
150
+ else:
151
+ # Add score as metadata if score attribute doesn't exist
152
+ if not hasattr(memories[idx], 'metadata'):
153
+ memories[idx].metadata = {}
154
+ memories[idx].metadata['semantic_score'] = score
155
+ applied_count += 1
156
+
157
+ return applied_count
158
+
159
+ @staticmethod
160
+ def format_memories_for_llm_ranking(memories: List) -> str:
161
+ """Format memories for LLM ranking input."""
162
+ formatted_memories = []
163
+
164
+ for i, memory in enumerate(memories):
165
+ memory_text = f"Memory {i}:\n"
166
+ memory_text += f"When to use: {memory.when_to_use}\n"
167
+ memory_text += f"Content: {memory.content}\n"
168
+ formatted_memories.append(memory_text)
169
+
170
+ return "\n---\n".join(formatted_memories)
@@ -0,0 +1,37 @@
1
+ import datetime
2
+ from typing import Tuple
3
+
4
+ from flowllm import C, BaseOp
5
+ from loguru import logger
6
+
7
+ from reme_ai.constants.common_constants import QUERY_WITH_TS
8
+
9
+
10
+ @C.register_op()
11
+ class SetQueryOp(BaseOp):
12
+ """
13
+ The `SetQueryOp` class is responsible for setting a query and its associated timestamp
14
+ into the context, utilizing either provided parameters or details from the context.
15
+ """
16
+
17
+ def execute(self):
18
+ """
19
+ Executes the operation's primary function, which involves determining the query and its
20
+ timestamp, then storing these values within the context.
21
+
22
+ Input requirement: self.context.query must exist (flow input requirement)
23
+ """
24
+ # Flow guarantees query exists - use it directly
25
+ query: str = self.context.query
26
+ timestamp: int = int(datetime.datetime.now().timestamp())
27
+
28
+ # Set timestamp if provided in op_params
29
+ _timestamp = self.op_params.get("timestamp")
30
+ if _timestamp and isinstance(_timestamp, int):
31
+ timestamp = _timestamp
32
+
33
+ # Store the query and its timestamp in the context
34
+ query_with_ts: Tuple[str, int] = (query, timestamp)
35
+ self.context[QUERY_WITH_TS] = query_with_ts
36
+
37
+ logger.info(f"Set query with timestamp: query='{query}', timestamp={timestamp}")
@@ -0,0 +1,4 @@
1
+ from .build_query_op import BuildQueryOp
2
+ from .merge_memory_op import MergeMemoryOp
3
+ from .rerank_memory_op import RerankMemoryOp
4
+ from .rewrite_memory_op import RewriteMemoryOp
@@ -0,0 +1,38 @@
1
+ from flowllm import C, BaseLLMOp
2
+ from flowllm.utils.llm_utils import merge_messages_content
3
+ from loguru import logger
4
+
5
+ from reme_ai.schema import Message, Role
6
+
7
+
8
+ @C.register_op()
9
+ class BuildQueryOp(BaseLLMOp):
10
+ file_path: str = __file__
11
+
12
+ def execute(self):
13
+ if "query" in self.context:
14
+ query = self.context.query
15
+
16
+ elif "messages" in self.context:
17
+ if self.op_params.get("enable_llm_build", True):
18
+ execution_process = merge_messages_content(self.context.messages)
19
+ prompt = self.prompt_format(prompt_name="query_build", execution_process=execution_process)
20
+ message = self.llm.chat(messages=[Message(role=Role.USER, content=prompt)])
21
+ query = message.content
22
+
23
+ else:
24
+ context_parts = []
25
+ message_summaries = []
26
+ for message in self.context.messages[-3:]: # Last 3 messages
27
+ content = message.content[:200] + "..." if len(message.content) > 200 else message.content
28
+ message_summaries.append(f"- {message.role.value}: {content}")
29
+ if message_summaries:
30
+ context_parts.append("Recent messages:\n" + "\n".join(message_summaries))
31
+
32
+ query = "\n\n".join(context_parts)
33
+
34
+ else:
35
+ raise RuntimeError("query or messages is required!")
36
+
37
+ logger.info(f"build.query={query}")
38
+ self.context.query = query
@@ -0,0 +1,27 @@
1
+ from typing import List
2
+
3
+ from flowllm import C, BaseOp
4
+ from loguru import logger
5
+
6
+ from reme_ai.schema.memory import BaseMemory
7
+
8
+
9
+ @C.register_op()
10
+ class MergeMemoryOp(BaseOp):
11
+
12
+ def execute(self):
13
+ memory_list: List[BaseMemory] = self.context.response.metadata["memory_list"]
14
+
15
+ if not memory_list:
16
+ return
17
+
18
+ content_collector = ["Previous Memory"]
19
+ for memory in memory_list:
20
+ if not memory.content:
21
+ continue
22
+
23
+ content_collector.append(f"- {memory.when_to_use} {memory.content}\n")
24
+ content_collector.append("Please consider the helpful parts from these in answering the question, "
25
+ "to make the response more comprehensive and substantial.")
26
+ self.context.response.answer = "\n".join(content_collector)
27
+ logger.info(f"response.answer={self.context.response.answer}")