MemoryOS 0.2.0__py3-none-any.whl → 0.2.1__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.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/METADATA +66 -26
- {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/RECORD +80 -56
- memoryos-0.2.1.dist-info/entry_points.txt +3 -0
- memos/__init__.py +1 -1
- memos/api/config.py +471 -0
- memos/api/exceptions.py +28 -0
- memos/api/mcp_serve.py +502 -0
- memos/api/product_api.py +35 -0
- memos/api/product_models.py +159 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +358 -0
- memos/chunkers/sentence_chunker.py +8 -2
- memos/cli.py +113 -0
- memos/configs/embedder.py +27 -0
- memos/configs/graph_db.py +83 -2
- memos/configs/llm.py +47 -0
- memos/configs/mem_cube.py +1 -1
- memos/configs/mem_scheduler.py +91 -5
- memos/configs/memory.py +5 -4
- memos/dependency.py +52 -0
- memos/embedders/ark.py +92 -0
- memos/embedders/factory.py +4 -0
- memos/embedders/sentence_transformer.py +8 -2
- memos/embedders/universal_api.py +32 -0
- memos/graph_dbs/base.py +2 -2
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/neo4j.py +331 -122
- memos/graph_dbs/neo4j_community.py +300 -0
- memos/llms/base.py +9 -0
- memos/llms/deepseek.py +54 -0
- memos/llms/factory.py +10 -1
- memos/llms/hf.py +170 -13
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +4 -0
- memos/llms/openai.py +67 -1
- memos/llms/qwen.py +63 -0
- memos/llms/vllm.py +153 -0
- memos/mem_cube/general.py +77 -16
- memos/mem_cube/utils.py +102 -0
- memos/mem_os/core.py +131 -41
- memos/mem_os/main.py +93 -11
- memos/mem_os/product.py +1098 -35
- memos/mem_os/utils/default_config.py +352 -0
- memos/mem_os/utils/format_utils.py +1154 -0
- memos/mem_reader/simple_struct.py +5 -5
- memos/mem_scheduler/base_scheduler.py +467 -36
- memos/mem_scheduler/general_scheduler.py +125 -244
- memos/mem_scheduler/modules/base.py +9 -0
- memos/mem_scheduler/modules/dispatcher.py +68 -2
- memos/mem_scheduler/modules/misc.py +39 -0
- memos/mem_scheduler/modules/monitor.py +228 -49
- memos/mem_scheduler/modules/rabbitmq_service.py +317 -0
- memos/mem_scheduler/modules/redis_service.py +32 -22
- memos/mem_scheduler/modules/retriever.py +250 -23
- memos/mem_scheduler/modules/schemas.py +189 -7
- memos/mem_scheduler/mos_for_test_scheduler.py +143 -0
- memos/mem_scheduler/utils.py +51 -2
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/memories/activation/item.py +25 -0
- memos/memories/activation/kv.py +10 -3
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/factory.py +2 -0
- memos/memories/textual/general.py +7 -5
- memos/memories/textual/tree.py +9 -5
- memos/memories/textual/tree_text_memory/organize/conflict.py +5 -3
- memos/memories/textual/tree_text_memory/organize/manager.py +26 -18
- memos/memories/textual/tree_text_memory/organize/redundancy.py +25 -44
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +11 -13
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +73 -51
- memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +6 -5
- memos/parsers/markitdown.py +8 -2
- memos/templates/mem_reader_prompts.py +65 -23
- memos/templates/mem_scheduler_prompts.py +96 -47
- memos/templates/tree_reorganize_prompts.py +85 -30
- memos/vec_dbs/base.py +12 -0
- memos/vec_dbs/qdrant.py +46 -20
- {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/LICENSE +0 -0
- {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/WHEEL +0 -0
|
@@ -2,11 +2,9 @@ import asyncio
|
|
|
2
2
|
import threading
|
|
3
3
|
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
from redis import Redis
|
|
9
|
-
|
|
7
|
+
from memos.dependency import require_python_package
|
|
10
8
|
from memos.log import get_logger
|
|
11
9
|
from memos.mem_scheduler.modules.base import BaseSchedulerModule
|
|
12
10
|
|
|
@@ -15,6 +13,11 @@ logger = get_logger(__name__)
|
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
class RedisSchedulerModule(BaseSchedulerModule):
|
|
16
|
+
@require_python_package(
|
|
17
|
+
import_name="redis",
|
|
18
|
+
install_command="pip install redis",
|
|
19
|
+
install_link="https://redis.readthedocs.io/en/stable/",
|
|
20
|
+
)
|
|
18
21
|
def __init__(self):
|
|
19
22
|
"""
|
|
20
23
|
intent_detector: Object used for intent recognition (such as the above IntentDetector)
|
|
@@ -35,23 +38,25 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
35
38
|
self._redis_listener_loop: asyncio.AbstractEventLoop | None = None
|
|
36
39
|
|
|
37
40
|
@property
|
|
38
|
-
def redis(self) ->
|
|
41
|
+
def redis(self) -> Any:
|
|
39
42
|
return self._redis_conn
|
|
40
43
|
|
|
41
44
|
@redis.setter
|
|
42
|
-
def redis(self, value:
|
|
45
|
+
def redis(self, value: Any) -> None:
|
|
43
46
|
self._redis_conn = value
|
|
44
47
|
|
|
45
48
|
def initialize_redis(
|
|
46
49
|
self, redis_host: str = "localhost", redis_port: int = 6379, redis_db: int = 0
|
|
47
50
|
):
|
|
51
|
+
import redis
|
|
52
|
+
|
|
48
53
|
self.redis_host = redis_host
|
|
49
54
|
self.redis_port = redis_port
|
|
50
55
|
self.redis_db = redis_db
|
|
51
56
|
|
|
52
57
|
try:
|
|
53
58
|
logger.debug(f"Connecting to Redis at {redis_host}:{redis_port}/{redis_db}")
|
|
54
|
-
self._redis_conn = Redis(
|
|
59
|
+
self._redis_conn = redis.Redis(
|
|
55
60
|
host=self.redis_host, port=self.redis_port, db=self.redis_db, decode_responses=True
|
|
56
61
|
)
|
|
57
62
|
# test conn
|
|
@@ -63,21 +68,21 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
63
68
|
self._redis_conn.xtrim("user:queries:stream", self.query_list_capacity)
|
|
64
69
|
return self._redis_conn
|
|
65
70
|
|
|
66
|
-
async def
|
|
71
|
+
async def redis_add_message_stream(self, message: dict):
|
|
67
72
|
logger.debug(f"add_message_stream: {message}")
|
|
68
73
|
return self._redis_conn.xadd("user:queries:stream", message)
|
|
69
74
|
|
|
70
|
-
async def
|
|
75
|
+
async def redis_consume_message_stream(self, message: dict):
|
|
71
76
|
logger.debug(f"consume_message_stream: {message}")
|
|
72
77
|
|
|
73
|
-
def
|
|
78
|
+
def _redis_run_listener_async(self, handler: Callable):
|
|
74
79
|
"""Run the async listener in a separate thread"""
|
|
75
80
|
self._redis_listener_loop = asyncio.new_event_loop()
|
|
76
81
|
asyncio.set_event_loop(self._redis_listener_loop)
|
|
77
82
|
|
|
78
83
|
async def listener_wrapper():
|
|
79
84
|
try:
|
|
80
|
-
await self.
|
|
85
|
+
await self.__redis_listen_query_stream(handler)
|
|
81
86
|
except Exception as e:
|
|
82
87
|
logger.error(f"Listener thread error: {e}")
|
|
83
88
|
finally:
|
|
@@ -85,8 +90,12 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
85
90
|
|
|
86
91
|
self._redis_listener_loop.run_until_complete(listener_wrapper())
|
|
87
92
|
|
|
88
|
-
async def
|
|
93
|
+
async def __redis_listen_query_stream(
|
|
94
|
+
self, handler=None, last_id: str = "$", block_time: int = 2000
|
|
95
|
+
):
|
|
89
96
|
"""Internal async stream listener"""
|
|
97
|
+
import redis
|
|
98
|
+
|
|
90
99
|
self._redis_listener_running = True
|
|
91
100
|
while self._redis_listener_running:
|
|
92
101
|
try:
|
|
@@ -99,6 +108,7 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
99
108
|
for _, stream_messages in messages:
|
|
100
109
|
for message_id, message_data in stream_messages:
|
|
101
110
|
try:
|
|
111
|
+
print(f"deal with message_data {message_data}")
|
|
102
112
|
await handler(message_data)
|
|
103
113
|
last_id = message_id
|
|
104
114
|
except Exception as e:
|
|
@@ -112,17 +122,17 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
112
122
|
logger.error(f"Unexpected error: {e}")
|
|
113
123
|
await asyncio.sleep(1)
|
|
114
124
|
|
|
115
|
-
def
|
|
125
|
+
def redis_start_listening(self, handler: Callable | None = None):
|
|
116
126
|
"""Start the Redis stream listener in a background thread"""
|
|
117
127
|
if self._redis_listener_thread and self._redis_listener_thread.is_alive():
|
|
118
128
|
logger.warning("Listener is already running")
|
|
119
129
|
return
|
|
120
130
|
|
|
121
131
|
if handler is None:
|
|
122
|
-
handler = self.
|
|
132
|
+
handler = self.redis_consume_message_stream
|
|
123
133
|
|
|
124
134
|
self._redis_listener_thread = threading.Thread(
|
|
125
|
-
target=self.
|
|
135
|
+
target=self._redis_run_listener_async,
|
|
126
136
|
args=(handler,),
|
|
127
137
|
daemon=True,
|
|
128
138
|
name="RedisListenerThread",
|
|
@@ -130,13 +140,7 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
130
140
|
self._redis_listener_thread.start()
|
|
131
141
|
logger.info("Started Redis stream listener thread")
|
|
132
142
|
|
|
133
|
-
def
|
|
134
|
-
"""Close Redis connection"""
|
|
135
|
-
if self._redis_conn is not None:
|
|
136
|
-
self._redis_conn.close()
|
|
137
|
-
self._redis_conn = None
|
|
138
|
-
|
|
139
|
-
def stop_listening(self):
|
|
143
|
+
def redis_stop_listening(self):
|
|
140
144
|
"""Stop the listener thread gracefully"""
|
|
141
145
|
self._redis_listener_running = False
|
|
142
146
|
if self._redis_listener_thread and self._redis_listener_thread.is_alive():
|
|
@@ -144,3 +148,9 @@ class RedisSchedulerModule(BaseSchedulerModule):
|
|
|
144
148
|
if self._redis_listener_thread.is_alive():
|
|
145
149
|
logger.warning("Listener thread did not stop gracefully")
|
|
146
150
|
logger.info("Redis stream listener stopped")
|
|
151
|
+
|
|
152
|
+
def redis_close(self):
|
|
153
|
+
"""Close Redis connection"""
|
|
154
|
+
if self._redis_conn is not None:
|
|
155
|
+
self._redis_conn.close()
|
|
156
|
+
self._redis_conn = None
|
|
@@ -1,41 +1,268 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from memos.configs.mem_scheduler import BaseSchedulerConfig
|
|
4
|
+
from memos.dependency import require_python_package
|
|
5
|
+
from memos.llms.base import BaseLLM
|
|
1
6
|
from memos.log import get_logger
|
|
7
|
+
from memos.mem_cube.general import GeneralMemCube
|
|
2
8
|
from memos.mem_scheduler.modules.base import BaseSchedulerModule
|
|
9
|
+
from memos.mem_scheduler.modules.schemas import (
|
|
10
|
+
TreeTextMemory_SEARCH_METHOD,
|
|
11
|
+
)
|
|
12
|
+
from memos.mem_scheduler.utils import (
|
|
13
|
+
extract_json_dict,
|
|
14
|
+
is_all_chinese,
|
|
15
|
+
is_all_english,
|
|
16
|
+
transform_name_to_key,
|
|
17
|
+
)
|
|
18
|
+
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
3
19
|
|
|
4
20
|
|
|
5
21
|
logger = get_logger(__name__)
|
|
6
22
|
|
|
7
23
|
|
|
8
24
|
class SchedulerRetriever(BaseSchedulerModule):
|
|
9
|
-
def __init__(self,
|
|
25
|
+
def __init__(self, process_llm: BaseLLM, config: BaseSchedulerConfig):
|
|
26
|
+
super().__init__()
|
|
27
|
+
|
|
28
|
+
self.config: BaseSchedulerConfig = config
|
|
29
|
+
self.process_llm = process_llm
|
|
30
|
+
|
|
31
|
+
# hyper-parameters
|
|
32
|
+
self.filter_similarity_threshold = 0.75
|
|
33
|
+
self.filter_min_length_threshold = 6
|
|
34
|
+
|
|
35
|
+
# log function callbacks
|
|
36
|
+
self.log_working_memory_replacement = None
|
|
37
|
+
|
|
38
|
+
def search(
|
|
39
|
+
self, query: str, mem_cube: GeneralMemCube, top_k: int, method=TreeTextMemory_SEARCH_METHOD
|
|
40
|
+
):
|
|
41
|
+
"""Search in text memory with the given query.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: The search query string
|
|
45
|
+
top_k: Number of top results to return
|
|
46
|
+
method: Search method to use
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Search results or None if not implemented
|
|
50
|
+
"""
|
|
51
|
+
text_mem_base = mem_cube.text_mem
|
|
52
|
+
try:
|
|
53
|
+
if method == TreeTextMemory_SEARCH_METHOD:
|
|
54
|
+
assert isinstance(text_mem_base, TreeTextMemory)
|
|
55
|
+
results_long_term = text_mem_base.search(
|
|
56
|
+
query=query, top_k=top_k, memory_type="LongTermMemory"
|
|
57
|
+
)
|
|
58
|
+
results_user = text_mem_base.search(
|
|
59
|
+
query=query, top_k=top_k, memory_type="UserMemory"
|
|
60
|
+
)
|
|
61
|
+
results = results_long_term + results_user
|
|
62
|
+
else:
|
|
63
|
+
raise NotImplementedError(str(type(text_mem_base)))
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Fail to search. The exeption is {e}.", exc_info=True)
|
|
66
|
+
results = []
|
|
67
|
+
return results
|
|
68
|
+
|
|
69
|
+
@require_python_package(
|
|
70
|
+
import_name="sklearn",
|
|
71
|
+
install_command="pip install scikit-learn",
|
|
72
|
+
install_link="https://scikit-learn.org/stable/install.html",
|
|
73
|
+
)
|
|
74
|
+
def filter_similar_memories(
|
|
75
|
+
self, text_memories: list[str], similarity_threshold: float = 0.75
|
|
76
|
+
) -> list[str]:
|
|
10
77
|
"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
78
|
+
Filters out low-quality or duplicate memories based on text similarity.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
text_memories: List of text memories to filter
|
|
82
|
+
similarity_threshold: Threshold for considering memories duplicates (0.0-1.0)
|
|
83
|
+
Higher values mean stricter filtering
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of filtered memories with duplicates removed
|
|
14
87
|
"""
|
|
15
|
-
|
|
88
|
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
89
|
+
from sklearn.metrics.pairwise import cosine_similarity
|
|
90
|
+
|
|
91
|
+
if not text_memories:
|
|
92
|
+
logging.warning("Received empty memories list - nothing to filter")
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
for idx in range(len(text_memories)):
|
|
96
|
+
if not isinstance(text_memories[idx], str):
|
|
97
|
+
logger.error(
|
|
98
|
+
f"{text_memories[idx]} in memories is not a string,"
|
|
99
|
+
f" and now has been transformed to be a string."
|
|
100
|
+
)
|
|
101
|
+
text_memories[idx] = str(text_memories[idx])
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# Step 1: Vectorize texts using TF-IDF
|
|
105
|
+
vectorizer = TfidfVectorizer()
|
|
106
|
+
tfidf_matrix = vectorizer.fit_transform(text_memories)
|
|
107
|
+
|
|
108
|
+
# Step 2: Calculate pairwise similarity matrix
|
|
109
|
+
similarity_matrix = cosine_similarity(tfidf_matrix)
|
|
110
|
+
|
|
111
|
+
# Step 3: Identify duplicates
|
|
112
|
+
to_keep = []
|
|
113
|
+
removal_reasons = {}
|
|
16
114
|
|
|
17
|
-
|
|
18
|
-
|
|
115
|
+
for current_idx in range(len(text_memories)):
|
|
116
|
+
is_duplicate = False
|
|
19
117
|
|
|
20
|
-
|
|
21
|
-
|
|
118
|
+
# Compare with already kept memories
|
|
119
|
+
for kept_idx in to_keep:
|
|
120
|
+
similarity_score = similarity_matrix[current_idx, kept_idx]
|
|
22
121
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
122
|
+
if similarity_score > similarity_threshold:
|
|
123
|
+
is_duplicate = True
|
|
124
|
+
# Generate removal reason with sample text
|
|
125
|
+
removal_reasons[current_idx] = (
|
|
126
|
+
f"Memory too similar (score: {similarity_score:.2f}) to kept memory #{kept_idx}. "
|
|
127
|
+
f"Kept: '{text_memories[kept_idx][:100]}...' | "
|
|
128
|
+
f"Removed: '{text_memories[current_idx][:100]}...'"
|
|
129
|
+
)
|
|
130
|
+
logger.info(removal_reasons)
|
|
131
|
+
break
|
|
27
132
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"""The memory cube associated with this MemChat."""
|
|
31
|
-
self._memory_text_list = value
|
|
133
|
+
if not is_duplicate:
|
|
134
|
+
to_keep.append(current_idx)
|
|
32
135
|
|
|
33
|
-
|
|
136
|
+
# Return filtered memories
|
|
137
|
+
return [text_memories[i] for i in sorted(to_keep)]
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logging.error(f"Error filtering memories: {e!s}")
|
|
141
|
+
return text_memories # Return original list if error occurs
|
|
142
|
+
|
|
143
|
+
def filter_too_short_memories(
|
|
144
|
+
self, text_memories: list[str], min_length_threshold: int = 20
|
|
145
|
+
) -> list[str]:
|
|
34
146
|
"""
|
|
35
|
-
|
|
36
|
-
|
|
147
|
+
Filters out text memories that fall below the minimum length requirement.
|
|
148
|
+
Handles both English (word count) and Chinese (character count) differently.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
text_memories: List of text memories to be filtered
|
|
152
|
+
min_length_threshold: Minimum length required to keep a memory.
|
|
153
|
+
For English: word count, for Chinese: character count.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List of filtered memories meeting the length requirement
|
|
37
157
|
"""
|
|
38
|
-
|
|
158
|
+
if not text_memories:
|
|
159
|
+
logging.debug("Empty memories list received in short memory filter")
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
filtered_memories = []
|
|
163
|
+
removed_count = 0
|
|
164
|
+
|
|
165
|
+
for memory in text_memories:
|
|
166
|
+
stripped_memory = memory.strip()
|
|
167
|
+
if not stripped_memory: # Skip empty/whitespace memories
|
|
168
|
+
removed_count += 1
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
# Determine measurement method based on language
|
|
172
|
+
if is_all_english(stripped_memory):
|
|
173
|
+
length = len(stripped_memory.split()) # Word count for English
|
|
174
|
+
elif is_all_chinese(stripped_memory):
|
|
175
|
+
length = len(stripped_memory) # Character count for Chinese
|
|
176
|
+
else:
|
|
177
|
+
logger.debug(
|
|
178
|
+
f"Mixed-language memory, using character count: {stripped_memory[:50]}..."
|
|
179
|
+
)
|
|
180
|
+
length = len(stripped_memory) # Default to character count
|
|
181
|
+
|
|
182
|
+
if length >= min_length_threshold:
|
|
183
|
+
filtered_memories.append(memory)
|
|
184
|
+
else:
|
|
185
|
+
removed_count += 1
|
|
186
|
+
|
|
187
|
+
if removed_count > 0:
|
|
188
|
+
logger.info(
|
|
189
|
+
f"Filtered out {removed_count} short memories "
|
|
190
|
+
f"(below {min_length_threshold} units). "
|
|
191
|
+
f"Total remaining: {len(filtered_memories)}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return filtered_memories
|
|
195
|
+
|
|
196
|
+
def replace_working_memory(
|
|
197
|
+
self,
|
|
198
|
+
queries: list[str],
|
|
199
|
+
user_id: str,
|
|
200
|
+
mem_cube_id: str,
|
|
201
|
+
mem_cube: GeneralMemCube,
|
|
202
|
+
original_memory: list[TextualMemoryItem],
|
|
203
|
+
new_memory: list[TextualMemoryItem],
|
|
204
|
+
top_k: int = 10,
|
|
205
|
+
) -> None | list[TextualMemoryItem]:
|
|
206
|
+
"""Replace working memory with new memories after reranking."""
|
|
207
|
+
memories_with_new_order = None
|
|
208
|
+
text_mem_base = mem_cube.text_mem
|
|
209
|
+
if isinstance(text_mem_base, TreeTextMemory):
|
|
210
|
+
text_mem_base: TreeTextMemory = text_mem_base
|
|
211
|
+
combined_memory = original_memory + new_memory
|
|
212
|
+
memory_map = {
|
|
213
|
+
transform_name_to_key(name=mem_obj.memory): mem_obj for mem_obj in combined_memory
|
|
214
|
+
}
|
|
215
|
+
combined_text_memory = [transform_name_to_key(name=m.memory) for m in combined_memory]
|
|
216
|
+
|
|
217
|
+
# apply filters
|
|
218
|
+
filtered_combined_text_memory = self.filter_similar_memories(
|
|
219
|
+
text_memories=combined_text_memory,
|
|
220
|
+
similarity_threshold=self.filter_similarity_threshold,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
filtered_combined_text_memory = self.filter_too_short_memories(
|
|
224
|
+
text_memories=filtered_combined_text_memory,
|
|
225
|
+
min_length_threshold=self.filter_min_length_threshold,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
unique_memory = list(dict.fromkeys(filtered_combined_text_memory))
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
prompt = self.build_prompt(
|
|
232
|
+
"memory_reranking",
|
|
233
|
+
queries=queries,
|
|
234
|
+
current_order=unique_memory,
|
|
235
|
+
staging_buffer=[],
|
|
236
|
+
)
|
|
237
|
+
response = self.process_llm.generate([{"role": "user", "content": prompt}])
|
|
238
|
+
response = extract_json_dict(response)
|
|
239
|
+
text_memories_with_new_order = response.get("new_order", [])[:top_k]
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Fail to rerank with LLM, Exeption: {e}.", exc_info=True)
|
|
242
|
+
text_memories_with_new_order = unique_memory[:top_k]
|
|
243
|
+
|
|
244
|
+
memories_with_new_order = []
|
|
245
|
+
for text in text_memories_with_new_order:
|
|
246
|
+
normalized_text = transform_name_to_key(name=text)
|
|
247
|
+
if text in memory_map:
|
|
248
|
+
memories_with_new_order.append(memory_map[normalized_text])
|
|
249
|
+
else:
|
|
250
|
+
logger.warning(
|
|
251
|
+
f"Memory text not found in memory map. text: {text}; keys of memory_map: {memory_map.keys()}"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
text_mem_base.replace_working_memory(memories_with_new_order)
|
|
255
|
+
logger.info(
|
|
256
|
+
f"The working memory has been replaced with {len(memories_with_new_order)} new memories."
|
|
257
|
+
)
|
|
258
|
+
self.log_working_memory_replacement(
|
|
259
|
+
original_memory=original_memory,
|
|
260
|
+
new_memory=memories_with_new_order,
|
|
261
|
+
user_id=user_id,
|
|
262
|
+
mem_cube_id=mem_cube_id,
|
|
263
|
+
mem_cube=mem_cube,
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
logger.error("memory_base is not supported")
|
|
39
267
|
|
|
40
|
-
|
|
41
|
-
return None
|
|
268
|
+
return memories_with_new_order
|