MemoryOS 1.0.1__py3-none-any.whl → 1.1.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-1.0.1.dist-info → memoryos-1.1.1.dist-info}/METADATA +7 -2
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/RECORD +79 -65
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/WHEEL +1 -1
- memos/__init__.py +1 -1
- memos/api/client.py +109 -0
- memos/api/config.py +11 -9
- memos/api/context/dependencies.py +15 -55
- memos/api/middleware/request_context.py +9 -40
- memos/api/product_api.py +2 -3
- memos/api/product_models.py +91 -16
- memos/api/routers/product_router.py +23 -16
- memos/api/start_api.py +10 -0
- memos/configs/graph_db.py +4 -0
- memos/configs/mem_scheduler.py +38 -3
- memos/context/context.py +255 -0
- memos/embedders/factory.py +2 -0
- memos/graph_dbs/nebular.py +230 -232
- memos/graph_dbs/neo4j.py +35 -1
- memos/graph_dbs/neo4j_community.py +7 -0
- memos/llms/factory.py +2 -0
- memos/llms/openai.py +74 -2
- memos/log.py +27 -15
- memos/mem_cube/general.py +3 -1
- memos/mem_os/core.py +60 -22
- memos/mem_os/main.py +3 -6
- memos/mem_os/product.py +35 -11
- memos/mem_reader/factory.py +2 -0
- memos/mem_reader/simple_struct.py +127 -74
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +569 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +126 -56
- memos/mem_scheduler/general_modules/dispatcher.py +2 -2
- memos/mem_scheduler/general_modules/misc.py +99 -1
- memos/mem_scheduler/general_modules/scheduler_logger.py +17 -11
- memos/mem_scheduler/general_scheduler.py +40 -88
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +308 -0
- memos/mem_scheduler/{general_modules → memory_manage_modules}/retriever.py +34 -7
- memos/mem_scheduler/monitors/dispatcher_monitor.py +9 -8
- memos/mem_scheduler/monitors/general_monitor.py +119 -39
- memos/mem_scheduler/optimized_scheduler.py +124 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/base_model.py +635 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/scheduler_factory.py +2 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +96 -29
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +33 -0
- memos/mem_scheduler/utils/filter_utils.py +1 -1
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/memories/activation/kv.py +2 -1
- memos/memories/textual/item.py +95 -16
- memos/memories/textual/naive.py +1 -1
- memos/memories/textual/tree.py +27 -3
- memos/memories/textual/tree_text_memory/organize/handler.py +4 -2
- memos/memories/textual/tree_text_memory/organize/manager.py +28 -14
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +1 -2
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +75 -23
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +7 -5
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -2
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +70 -22
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +101 -33
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +5 -4
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +22 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/parsers/factory.py +2 -0
- memos/reranker/concat.py +59 -0
- memos/reranker/cosine_local.py +1 -0
- memos/reranker/factory.py +5 -0
- memos/reranker/http_bge.py +225 -12
- memos/templates/mem_scheduler_prompts.py +242 -0
- memos/types.py +4 -1
- memos/api/context/context.py +0 -147
- memos/api/context/context_thread.py +0 -96
- memos/mem_scheduler/mos_for_test_scheduler.py +0 -146
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/entry_points.txt +0 -0
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info/licenses}/LICENSE +0 -0
- /memos/mem_scheduler/{general_modules → webservice_modules}/rabbitmq_service.py +0 -0
- /memos/mem_scheduler/{general_modules → webservice_modules}/redis_service.py +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Index
|
|
4
|
+
from sqlalchemy.engine import Engine
|
|
5
|
+
|
|
6
|
+
from memos.log import get_logger
|
|
7
|
+
from memos.mem_scheduler.schemas.monitor_schemas import (
|
|
8
|
+
MemoryMonitorItem,
|
|
9
|
+
MemoryMonitorManager,
|
|
10
|
+
QueryMonitorItem,
|
|
11
|
+
QueryMonitorQueue,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .base_model import BaseDBManager, LockableORM
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
# Type variables for generic type hints
|
|
20
|
+
T = TypeVar("T") # The model type (MemoryMonitorManager, QueryMonitorManager, etc.)
|
|
21
|
+
ORM = TypeVar("ORM") # The ORM model type
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MemoryMonitorManagerORM(LockableORM):
|
|
25
|
+
"""ORM model for MemoryMonitorManager persistence
|
|
26
|
+
|
|
27
|
+
This table stores serialized MemoryMonitorManager instances with
|
|
28
|
+
proper indexing for efficient user and memory cube lookups.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__tablename__ = "memory_monitor_manager"
|
|
32
|
+
|
|
33
|
+
# Database indexes for performance optimization
|
|
34
|
+
__table_args__ = (Index("idx_memory_monitor_user_memcube", "user_id", "mem_cube_id"),)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class QueryMonitorQueueORM(LockableORM):
|
|
38
|
+
"""ORM model for QueryMonitorQueue persistence
|
|
39
|
+
|
|
40
|
+
This table stores serialized QueryMonitorQueue instances with
|
|
41
|
+
proper indexing for efficient user and memory cube lookups.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
__tablename__ = "query_monitor_queue"
|
|
45
|
+
|
|
46
|
+
# Database indexes for performance optimization
|
|
47
|
+
__table_args__ = (Index("idx_query_monitor_user_memcube", "user_id", "mem_cube_id"),)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DBManagerForMemoryMonitorManager(BaseDBManager):
|
|
51
|
+
"""Database manager for MemoryMonitorManager objects
|
|
52
|
+
|
|
53
|
+
This class handles persistence, synchronization, and locking
|
|
54
|
+
for MemoryMonitorManager instances in the database.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
engine: Engine,
|
|
60
|
+
user_id: str | None = None,
|
|
61
|
+
mem_cube_id: str | None = None,
|
|
62
|
+
obj: MemoryMonitorManager | None = None,
|
|
63
|
+
lock_timeout: int = 10,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the MemoryMonitorManager database manager.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
engine: SQLAlchemy engine instance
|
|
70
|
+
user_id: Unique identifier for the user
|
|
71
|
+
mem_cube_id: Unique identifier for the memory cube
|
|
72
|
+
obj: Optional MemoryMonitorManager instance to manage
|
|
73
|
+
lock_timeout: Timeout in seconds for lock acquisition
|
|
74
|
+
"""
|
|
75
|
+
super().__init__(
|
|
76
|
+
engine=engine, user_id=user_id, mem_cube_id=mem_cube_id, lock_timeout=lock_timeout
|
|
77
|
+
)
|
|
78
|
+
self.obj: MemoryMonitorManager | None = obj
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def orm_class(self) -> type[MemoryMonitorManagerORM]:
|
|
82
|
+
return MemoryMonitorManagerORM
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def obj_class(self) -> type[MemoryMonitorManager]:
|
|
86
|
+
return MemoryMonitorManager
|
|
87
|
+
|
|
88
|
+
def merge_items(
|
|
89
|
+
self,
|
|
90
|
+
orm_instance: MemoryMonitorManagerORM,
|
|
91
|
+
obj_instance: MemoryMonitorManager,
|
|
92
|
+
size_limit: int,
|
|
93
|
+
):
|
|
94
|
+
"""Merge memory monitor items from database with current object
|
|
95
|
+
|
|
96
|
+
This method combines items from the database with items in the current
|
|
97
|
+
object, prioritizing current object items and applying size limits.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
orm_instance: ORM instance containing serialized database data
|
|
101
|
+
obj_instance: Current MemoryMonitorManager instance
|
|
102
|
+
size_limit: Maximum number of items to keep after merge
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Updated obj_instance with merged items
|
|
106
|
+
"""
|
|
107
|
+
logger.debug(f"Starting merge_items for MemoryMonitorManager with size_limit={size_limit}")
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# Deserialize the database instance
|
|
111
|
+
db_instance: MemoryMonitorManager = MemoryMonitorManager.from_json(
|
|
112
|
+
orm_instance.serialized_data
|
|
113
|
+
)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.error(f"Failed to deserialize database instance: {e}", exc_info=True)
|
|
116
|
+
logger.warning("Skipping merge due to deserialization error, using current object only")
|
|
117
|
+
return obj_instance
|
|
118
|
+
|
|
119
|
+
# Merge items - prioritize existing ones in current object
|
|
120
|
+
merged_items: list[MemoryMonitorItem] = []
|
|
121
|
+
seen_ids = set()
|
|
122
|
+
|
|
123
|
+
# First, add all items from current object (higher priority)
|
|
124
|
+
for item in obj_instance.memories:
|
|
125
|
+
if item.item_id not in seen_ids:
|
|
126
|
+
merged_items.append(item)
|
|
127
|
+
seen_ids.add(item.item_id)
|
|
128
|
+
|
|
129
|
+
# Then, add items from database that aren't in current object
|
|
130
|
+
for item in db_instance.memories:
|
|
131
|
+
if item.item_id not in seen_ids:
|
|
132
|
+
merged_items.append(item)
|
|
133
|
+
seen_ids.add(item.item_id)
|
|
134
|
+
|
|
135
|
+
# Apply size limit if specified (keep most recent items)
|
|
136
|
+
if size_limit is not None and size_limit > 0:
|
|
137
|
+
try:
|
|
138
|
+
# Sort by sorting_score descending (highest priority first) and take top N
|
|
139
|
+
# Note: MemoryMonitorItem doesn't have timestamp, so we use sorting_score instead
|
|
140
|
+
merged_items = sorted(merged_items, key=lambda x: x.sorting_score, reverse=True)[
|
|
141
|
+
:size_limit
|
|
142
|
+
]
|
|
143
|
+
logger.debug(f"Applied size limit of {size_limit}, kept {len(merged_items)} items")
|
|
144
|
+
except AttributeError as e:
|
|
145
|
+
logger.error(f"Error sorting MemoryMonitorItem objects: {e}")
|
|
146
|
+
logger.error(
|
|
147
|
+
"Available attributes: "
|
|
148
|
+
+ ", ".join(dir(merged_items[0]) if merged_items else [])
|
|
149
|
+
)
|
|
150
|
+
raise
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error(f"Unexpected error during sorting: {e}")
|
|
153
|
+
raise
|
|
154
|
+
|
|
155
|
+
# Update the object with merged items
|
|
156
|
+
obj_instance.memories = merged_items
|
|
157
|
+
|
|
158
|
+
logger.info(
|
|
159
|
+
f"Merged {len(merged_items)} memory items for {obj_instance} (size_limit: {size_limit})"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return obj_instance
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class DBManagerForQueryMonitorQueue(BaseDBManager):
|
|
166
|
+
"""Database manager for QueryMonitorQueue objects
|
|
167
|
+
|
|
168
|
+
This class handles persistence, synchronization, and locking
|
|
169
|
+
for QueryMonitorQueue instances in the database.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
engine: Engine,
|
|
175
|
+
user_id: str | None = None,
|
|
176
|
+
mem_cube_id: str | None = None,
|
|
177
|
+
obj: QueryMonitorQueue | None = None,
|
|
178
|
+
lock_timeout: int = 10,
|
|
179
|
+
):
|
|
180
|
+
"""
|
|
181
|
+
Initialize the QueryMonitorQueue database manager.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
engine: SQLAlchemy engine instance
|
|
185
|
+
user_id: Unique identifier for the user
|
|
186
|
+
mem_cube_id: Unique identifier for the memory cube
|
|
187
|
+
obj: Optional QueryMonitorQueue instance to manage
|
|
188
|
+
lock_timeout: Timeout in seconds for lock acquisition
|
|
189
|
+
"""
|
|
190
|
+
super().__init__(
|
|
191
|
+
engine=engine, user_id=user_id, mem_cube_id=mem_cube_id, lock_timeout=lock_timeout
|
|
192
|
+
)
|
|
193
|
+
self.obj: QueryMonitorQueue | None = obj
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def orm_class(self) -> type[QueryMonitorQueueORM]:
|
|
197
|
+
return QueryMonitorQueueORM
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def obj_class(self) -> type[QueryMonitorQueue]:
|
|
201
|
+
return QueryMonitorQueue
|
|
202
|
+
|
|
203
|
+
def merge_items(
|
|
204
|
+
self, orm_instance: QueryMonitorQueueORM, obj_instance: QueryMonitorQueue, size_limit: int
|
|
205
|
+
):
|
|
206
|
+
"""Merge query monitor items from database with current queue
|
|
207
|
+
|
|
208
|
+
This method combines items from the database with items in the current
|
|
209
|
+
queue, prioritizing current queue items and applying size limits.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
orm_instance: ORM instance containing serialized database data
|
|
213
|
+
obj_instance: Current QueryMonitorQueue instance
|
|
214
|
+
size_limit: Maximum number of items to keep after merge
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Updated obj_instance with merged items
|
|
218
|
+
"""
|
|
219
|
+
try:
|
|
220
|
+
# Deserialize the database instance
|
|
221
|
+
db_instance: QueryMonitorQueue = QueryMonitorQueue.from_json(
|
|
222
|
+
orm_instance.serialized_data
|
|
223
|
+
)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.error(f"Failed to deserialize database instance: {e}")
|
|
226
|
+
logger.warning("Skipping merge due to deserialization error, using current object only")
|
|
227
|
+
return obj_instance
|
|
228
|
+
|
|
229
|
+
# Merge items - prioritize existing ones in current object
|
|
230
|
+
merged_items: list[QueryMonitorItem] = []
|
|
231
|
+
seen_ids = set()
|
|
232
|
+
|
|
233
|
+
# First, add all items from current queue (higher priority)
|
|
234
|
+
for item in obj_instance.get_queue_content_without_pop():
|
|
235
|
+
if item.item_id not in seen_ids:
|
|
236
|
+
merged_items.append(item)
|
|
237
|
+
seen_ids.add(item.item_id)
|
|
238
|
+
|
|
239
|
+
# Then, add items from database queue that aren't in current queue
|
|
240
|
+
for item in db_instance.get_queue_content_without_pop():
|
|
241
|
+
if item.item_id not in seen_ids:
|
|
242
|
+
merged_items.append(item)
|
|
243
|
+
seen_ids.add(item.item_id)
|
|
244
|
+
|
|
245
|
+
# Apply size limit if specified (keep most recent items)
|
|
246
|
+
if size_limit is not None and size_limit > 0:
|
|
247
|
+
# Sort by timestamp descending (newest first) and take top N
|
|
248
|
+
merged_items = sorted(merged_items, key=lambda x: x.timestamp, reverse=True)[
|
|
249
|
+
:size_limit
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
# Update the queue with merged items
|
|
253
|
+
obj_instance.clear() # Clear existing items
|
|
254
|
+
for item in merged_items:
|
|
255
|
+
obj_instance.put(item) # Add merged items back
|
|
256
|
+
|
|
257
|
+
logger.info(
|
|
258
|
+
f"Merged {len(merged_items)} query items for {obj_instance} (size_limit: {size_limit})"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return obj_instance
|
|
@@ -3,6 +3,7 @@ from typing import Any, ClassVar
|
|
|
3
3
|
from memos.configs.mem_scheduler import SchedulerConfigFactory
|
|
4
4
|
from memos.mem_scheduler.base_scheduler import BaseScheduler
|
|
5
5
|
from memos.mem_scheduler.general_scheduler import GeneralScheduler
|
|
6
|
+
from memos.mem_scheduler.optimized_scheduler import OptimizedScheduler
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class SchedulerFactory(BaseScheduler):
|
|
@@ -10,6 +11,7 @@ class SchedulerFactory(BaseScheduler):
|
|
|
10
11
|
|
|
11
12
|
backend_to_class: ClassVar[dict[str, Any]] = {
|
|
12
13
|
"general_scheduler": GeneralScheduler,
|
|
14
|
+
"optimized_scheduler": OptimizedScheduler,
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
@classmethod
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import threading
|
|
2
3
|
|
|
3
4
|
from collections import Counter
|
|
@@ -30,6 +31,8 @@ class QueryMonitorItem(BaseModel, DictConversionMixin):
|
|
|
30
31
|
item_id: str = Field(
|
|
31
32
|
description="Unique identifier for the query item", default_factory=lambda: str(uuid4())
|
|
32
33
|
)
|
|
34
|
+
user_id: str = Field(..., description="Required user identifier", min_length=1)
|
|
35
|
+
mem_cube_id: str = Field(..., description="Required memory cube identifier", min_length=1)
|
|
33
36
|
query_text: str = Field(
|
|
34
37
|
...,
|
|
35
38
|
description="The actual user query text content",
|
|
@@ -111,7 +114,8 @@ class QueryMonitorQueue(AutoDroppingQueue[QueryMonitorItem]):
|
|
|
111
114
|
"""
|
|
112
115
|
with self.mutex:
|
|
113
116
|
logger.debug(f"Thread {threading.get_ident()} acquired mutex.")
|
|
114
|
-
|
|
117
|
+
# Fix: Handle None keywords safely
|
|
118
|
+
all_keywords = [kw for item in self.queue if item.keywords for kw in item.keywords]
|
|
115
119
|
return Counter(all_keywords)
|
|
116
120
|
|
|
117
121
|
def get_queries_with_timesort(self, reverse: bool = True) -> list[str]:
|
|
@@ -132,9 +136,62 @@ class QueryMonitorQueue(AutoDroppingQueue[QueryMonitorItem]):
|
|
|
132
136
|
for monitor in sorted(self.queue, key=lambda x: x.timestamp, reverse=reverse)
|
|
133
137
|
]
|
|
134
138
|
|
|
139
|
+
def to_json(self) -> str:
|
|
140
|
+
"""Serialize the queue to a JSON string.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
item_serializer: Optional function to serialize individual items.
|
|
144
|
+
If not provided, items must be JSON-serializable.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
A JSON string representing the queue's content and maxsize.
|
|
148
|
+
"""
|
|
149
|
+
with self.mutex:
|
|
150
|
+
serialized_items = [item.to_json() for item in self.queue]
|
|
151
|
+
|
|
152
|
+
data = {"maxsize": self.maxsize, "items": serialized_items}
|
|
153
|
+
return json.dumps(data, ensure_ascii=False, indent=2)
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def from_json(cls, json_str: str) -> "QueryMonitorQueue":
|
|
157
|
+
"""Create a new AutoDroppingQueue from a JSON string.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
json_str: JSON string created by to_json()
|
|
161
|
+
item_deserializer: Optional function to reconstruct items from dicts.
|
|
162
|
+
If not provided, items are used as-is.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
A new AutoDroppingQueue instance with deserialized data.
|
|
166
|
+
"""
|
|
167
|
+
data = json.loads(json_str)
|
|
168
|
+
maxsize = data.get("maxsize", 0)
|
|
169
|
+
item_strs = data.get("items", [])
|
|
170
|
+
|
|
171
|
+
queue = cls(maxsize=maxsize)
|
|
172
|
+
|
|
173
|
+
items = [QueryMonitorItem.from_json(json_str=item_str) for item_str in item_strs]
|
|
174
|
+
|
|
175
|
+
# Fix: Add error handling for put operations
|
|
176
|
+
for item in items:
|
|
177
|
+
try:
|
|
178
|
+
queue.put(item) # Use put() to respect maxsize and auto-drop behavior
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Failed to add item to queue: {e}")
|
|
181
|
+
# Continue with other items instead of failing completely
|
|
182
|
+
|
|
183
|
+
return queue
|
|
184
|
+
|
|
135
185
|
|
|
136
186
|
# ============== Memories ==============
|
|
137
187
|
class MemoryMonitorItem(BaseModel, DictConversionMixin):
|
|
188
|
+
"""
|
|
189
|
+
Represents a memory item in the monitoring system.
|
|
190
|
+
|
|
191
|
+
Note: This class does NOT have a timestamp field, unlike QueryMonitorItem.
|
|
192
|
+
For sorting by recency, use sorting_score or importance_score instead.
|
|
193
|
+
"""
|
|
194
|
+
|
|
138
195
|
item_id: str = Field(
|
|
139
196
|
description="Unique identifier for the memory item", default_factory=lambda: str(uuid4())
|
|
140
197
|
)
|
|
@@ -167,7 +224,7 @@ class MemoryMonitorItem(BaseModel, DictConversionMixin):
|
|
|
167
224
|
recording_count: int = Field(
|
|
168
225
|
default=1,
|
|
169
226
|
description="How many times this memory has been recorded",
|
|
170
|
-
ge=1,
|
|
227
|
+
ge=1,
|
|
171
228
|
)
|
|
172
229
|
|
|
173
230
|
@field_validator("tree_memory_item_mapping_key", mode="before")
|
|
@@ -177,27 +234,28 @@ class MemoryMonitorItem(BaseModel, DictConversionMixin):
|
|
|
177
234
|
return v
|
|
178
235
|
|
|
179
236
|
def get_importance_score(self, weight_vector: list[float] | None = None) -> float:
|
|
180
|
-
|
|
181
|
-
Calculate the effective score for the memory item.
|
|
237
|
+
return self._get_complex_importance_score(weight_vector=weight_vector)
|
|
182
238
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
otherwise the recording_count converted to float.
|
|
186
|
-
|
|
187
|
-
Note:
|
|
188
|
-
This method provides a unified way to retrieve a comparable score
|
|
189
|
-
for memory items, regardless of whether their importance has been explicitly set.
|
|
190
|
-
"""
|
|
239
|
+
def _get_complex_importance_score(self, weight_vector: list[float] | None = None) -> float:
|
|
240
|
+
"""Calculate traditional importance score using existing logic"""
|
|
191
241
|
if weight_vector is None:
|
|
192
|
-
logger.warning("weight_vector of
|
|
242
|
+
logger.warning("weight_vector of get_complex_score is None.")
|
|
193
243
|
weight_vector = DEFAULT_WEIGHT_VECTOR_FOR_RANKING
|
|
194
|
-
|
|
195
|
-
|
|
244
|
+
|
|
245
|
+
# Fix: Add proper validation for weight_vector
|
|
246
|
+
if not weight_vector or len(weight_vector) != 3 or abs(sum(weight_vector) - 1.0) > 1e-6:
|
|
247
|
+
raise ValueError("weight_vector must be provided, have length 3, and sum to 1.0")
|
|
248
|
+
|
|
249
|
+
# Fix: Handle uninitialized scores safely
|
|
250
|
+
sorting_score = self.sorting_score if self.sorting_score != NOT_INITIALIZED else 0.0
|
|
251
|
+
keywords_score = self.keywords_score if self.keywords_score != NOT_INITIALIZED else 0.0
|
|
252
|
+
|
|
253
|
+
normalized_keywords_score = min(keywords_score * weight_vector[1], 5)
|
|
196
254
|
normalized_recording_count_score = min(self.recording_count * weight_vector[2], 2)
|
|
197
255
|
self.importance_score = (
|
|
198
|
-
|
|
199
|
-
+ normalized_keywords_score
|
|
200
|
-
+ normalized_recording_count_score
|
|
256
|
+
sorting_score * weight_vector[0]
|
|
257
|
+
+ normalized_keywords_score * weight_vector[1]
|
|
258
|
+
+ normalized_recording_count_score * weight_vector[2]
|
|
201
259
|
)
|
|
202
260
|
return self.importance_score
|
|
203
261
|
|
|
@@ -258,7 +316,7 @@ class MemoryMonitorManager(BaseModel, DictConversionMixin):
|
|
|
258
316
|
|
|
259
317
|
def update_memories(
|
|
260
318
|
self, new_memory_monitors: list[MemoryMonitorItem], partial_retention_number: int
|
|
261
|
-
) -> MemoryMonitorItem:
|
|
319
|
+
) -> list[MemoryMonitorItem]: # Fix: Correct return type
|
|
262
320
|
"""
|
|
263
321
|
Update memories based on monitor_working_memories.
|
|
264
322
|
"""
|
|
@@ -302,6 +360,13 @@ class MemoryMonitorManager(BaseModel, DictConversionMixin):
|
|
|
302
360
|
reverse=True,
|
|
303
361
|
)
|
|
304
362
|
|
|
363
|
+
# Fix: Add bounds checking to prevent IndexError
|
|
364
|
+
if partial_retention_number > len(sorted_old_mem_monitors):
|
|
365
|
+
partial_retention_number = len(sorted_old_mem_monitors)
|
|
366
|
+
logger.info(
|
|
367
|
+
f"partial_retention_number adjusted to {partial_retention_number} to match available old memories"
|
|
368
|
+
)
|
|
369
|
+
|
|
305
370
|
# Keep the top N old memories
|
|
306
371
|
memories_to_remove = sorted_old_mem_monitors[partial_retention_number:]
|
|
307
372
|
memories_to_change_score = sorted_old_mem_monitors[:partial_retention_number]
|
|
@@ -312,19 +377,21 @@ class MemoryMonitorManager(BaseModel, DictConversionMixin):
|
|
|
312
377
|
|
|
313
378
|
for memory in memories_to_change_score:
|
|
314
379
|
memory.sorting_score = 0
|
|
315
|
-
memory.recording_count =
|
|
380
|
+
memory.recording_count = 1
|
|
316
381
|
memory.keywords_score = 0
|
|
317
382
|
|
|
318
383
|
# Step 4: Enforce max_capacity if set
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
384
|
+
# Fix: Handle max_capacity safely
|
|
385
|
+
if self.max_capacity is not None:
|
|
386
|
+
sorted_memories = sorted(
|
|
387
|
+
self.memories,
|
|
388
|
+
key=lambda item: item.get_importance_score(
|
|
389
|
+
weight_vector=DEFAULT_WEIGHT_VECTOR_FOR_RANKING
|
|
390
|
+
),
|
|
391
|
+
reverse=True,
|
|
392
|
+
)
|
|
393
|
+
# Keep only the top max_capacity memories
|
|
394
|
+
self.memories = sorted_memories[: self.max_capacity]
|
|
328
395
|
|
|
329
396
|
# Log the update result
|
|
330
397
|
logger.info(
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def flatten_dict(
|
|
10
|
+
data: dict[str, Any], parent_keys: list[str] | None = None, prefix: str = ""
|
|
11
|
+
) -> dict[str, str]:
|
|
12
|
+
"""
|
|
13
|
+
Recursively flattens a nested dictionary to generate environment variable keys following the specified format.
|
|
14
|
+
Combines nested keys with underscores, converts to uppercase, and prepends a custom prefix if provided.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
data: Nested dictionary to be flattened (parsed from JSON/YAML)
|
|
18
|
+
parent_keys: List to track nested keys during recursion
|
|
19
|
+
prefix: Custom prefix to be added to all generated keys
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Flattened dictionary with keys in PREFIX_KEY1_KEY2... format and string values
|
|
23
|
+
"""
|
|
24
|
+
parent_keys = parent_keys or []
|
|
25
|
+
flat_data = {}
|
|
26
|
+
|
|
27
|
+
for key, value in data.items():
|
|
28
|
+
# Clean and standardize key: convert to uppercase, replace spaces/hyphens with underscores
|
|
29
|
+
clean_key = key.upper().replace(" ", "_").replace("-", "_")
|
|
30
|
+
current_keys = [*parent_keys, clean_key]
|
|
31
|
+
|
|
32
|
+
if isinstance(value, dict):
|
|
33
|
+
# Recursively process nested dictionaries
|
|
34
|
+
nested_flat = flatten_dict(value, current_keys, prefix)
|
|
35
|
+
flat_data.update(nested_flat)
|
|
36
|
+
else:
|
|
37
|
+
# Construct full key name with prefix (if provided) and nested keys
|
|
38
|
+
if prefix:
|
|
39
|
+
full_key = f"{prefix.upper()}_{'_'.join(current_keys)}"
|
|
40
|
+
else:
|
|
41
|
+
full_key = "_".join(current_keys)
|
|
42
|
+
|
|
43
|
+
# Process value: ensure string type, convert None to empty string
|
|
44
|
+
flat_value = "" if value is None else str(value).strip()
|
|
45
|
+
|
|
46
|
+
flat_data[full_key] = flat_value
|
|
47
|
+
|
|
48
|
+
return flat_data
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def convert_config_to_env(input_file: str, output_file: str = ".env", prefix: str = "") -> None:
|
|
52
|
+
"""
|
|
53
|
+
Converts a JSON or YAML configuration file to a .env file with standardized environment variables.
|
|
54
|
+
Uses the flatten_dict function to generate keys in PREFIX_KEY1_KEY2... format.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
input_file: Path to input configuration file (.json, .yaml, or .yml)
|
|
58
|
+
output_file: Path to output .env file (default: .env)
|
|
59
|
+
prefix: Custom prefix for all environment variable keys
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
FileNotFoundError: If input file does not exist
|
|
63
|
+
ValueError: If file format is unsupported or parsing fails
|
|
64
|
+
"""
|
|
65
|
+
# Check if input file exists
|
|
66
|
+
if not os.path.exists(input_file):
|
|
67
|
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
|
68
|
+
|
|
69
|
+
# Parse input file based on extension
|
|
70
|
+
file_ext = os.path.splitext(input_file)[1].lower()
|
|
71
|
+
config_data: dict[str, Any] = {}
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
with open(input_file, encoding="utf-8") as f:
|
|
75
|
+
if file_ext in (".json",):
|
|
76
|
+
config_data = json.load(f)
|
|
77
|
+
elif file_ext in (".yaml", ".yml"):
|
|
78
|
+
config_data = yaml.safe_load(f)
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Unsupported file format: {file_ext}. Supported formats: .json, .yaml, .yml"
|
|
82
|
+
)
|
|
83
|
+
except (json.JSONDecodeError, yaml.YAMLError) as e:
|
|
84
|
+
raise ValueError(f"Error parsing file: {e!s}") from e
|
|
85
|
+
|
|
86
|
+
# Flatten configuration and generate environment variable key-value pairs
|
|
87
|
+
flat_config = flatten_dict(config_data, prefix=prefix)
|
|
88
|
+
|
|
89
|
+
# Write to .env file
|
|
90
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
91
|
+
for key, value in flat_config.items():
|
|
92
|
+
# Handle values containing double quotes (use no surrounding quotes)
|
|
93
|
+
if '"' in value:
|
|
94
|
+
f.write(f"{key}={value}\n")
|
|
95
|
+
else:
|
|
96
|
+
f.write(f'{key}="{value}"\n') # Enclose regular values in double quotes
|
|
97
|
+
|
|
98
|
+
print(
|
|
99
|
+
f"Conversion complete! Generated {output_file} with {len(flat_config)} environment variables"
|
|
100
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def print_db_tables(db_path: str):
|
|
6
|
+
"""Print all table names and structures in the SQLite database"""
|
|
7
|
+
print(f"\n🔍 Checking database file: {db_path}")
|
|
8
|
+
|
|
9
|
+
if not os.path.exists(db_path):
|
|
10
|
+
print(f"❌ File does not exist! Path: {db_path}")
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
conn = sqlite3.connect(db_path)
|
|
14
|
+
cursor = conn.cursor()
|
|
15
|
+
|
|
16
|
+
# List all tables
|
|
17
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
|
18
|
+
tables = cursor.fetchall()
|
|
19
|
+
if not tables:
|
|
20
|
+
print("❌ Database is empty, no tables created")
|
|
21
|
+
else:
|
|
22
|
+
print(f"✅ Database contains {len(tables)} table(s):")
|
|
23
|
+
for (table_name,) in tables:
|
|
24
|
+
print(f" 📂 Table name: {table_name}")
|
|
25
|
+
|
|
26
|
+
# Print table structure
|
|
27
|
+
cursor.execute(f"PRAGMA table_info({table_name});")
|
|
28
|
+
columns = cursor.fetchall()
|
|
29
|
+
print(" 🧩 Structure:")
|
|
30
|
+
for col in columns:
|
|
31
|
+
print(f" {col[1]} ({col[2]}) {'(PK)' if col[5] else ''}")
|
|
32
|
+
|
|
33
|
+
conn.close()
|
|
@@ -60,7 +60,7 @@ def is_all_chinese(input_string: str) -> bool:
|
|
|
60
60
|
install_command="pip install scikit-learn",
|
|
61
61
|
install_link="https://scikit-learn.org/stable/install.html",
|
|
62
62
|
)
|
|
63
|
-
def
|
|
63
|
+
def filter_vector_based_similar_memories(
|
|
64
64
|
text_memories: list[str], similarity_threshold: float = 0.75
|
|
65
65
|
) -> list[str]:
|
|
66
66
|
"""
|
|
File without changes
|
memos/memories/activation/kv.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import pickle
|
|
3
|
+
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from importlib.metadata import version
|
|
5
|
-
from packaging.version import Version
|
|
6
6
|
|
|
7
|
+
from packaging.version import Version
|
|
7
8
|
from transformers import DynamicCache
|
|
8
9
|
|
|
9
10
|
from memos.configs.memory import KVCacheMemoryConfig
|