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.

Files changed (82) hide show
  1. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/METADATA +7 -2
  2. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/RECORD +79 -65
  3. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/WHEEL +1 -1
  4. memos/__init__.py +1 -1
  5. memos/api/client.py +109 -0
  6. memos/api/config.py +11 -9
  7. memos/api/context/dependencies.py +15 -55
  8. memos/api/middleware/request_context.py +9 -40
  9. memos/api/product_api.py +2 -3
  10. memos/api/product_models.py +91 -16
  11. memos/api/routers/product_router.py +23 -16
  12. memos/api/start_api.py +10 -0
  13. memos/configs/graph_db.py +4 -0
  14. memos/configs/mem_scheduler.py +38 -3
  15. memos/context/context.py +255 -0
  16. memos/embedders/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +230 -232
  18. memos/graph_dbs/neo4j.py +35 -1
  19. memos/graph_dbs/neo4j_community.py +7 -0
  20. memos/llms/factory.py +2 -0
  21. memos/llms/openai.py +74 -2
  22. memos/log.py +27 -15
  23. memos/mem_cube/general.py +3 -1
  24. memos/mem_os/core.py +60 -22
  25. memos/mem_os/main.py +3 -6
  26. memos/mem_os/product.py +35 -11
  27. memos/mem_reader/factory.py +2 -0
  28. memos/mem_reader/simple_struct.py +127 -74
  29. memos/mem_scheduler/analyzer/__init__.py +0 -0
  30. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +569 -0
  31. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  32. memos/mem_scheduler/base_scheduler.py +126 -56
  33. memos/mem_scheduler/general_modules/dispatcher.py +2 -2
  34. memos/mem_scheduler/general_modules/misc.py +99 -1
  35. memos/mem_scheduler/general_modules/scheduler_logger.py +17 -11
  36. memos/mem_scheduler/general_scheduler.py +40 -88
  37. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  38. memos/mem_scheduler/memory_manage_modules/memory_filter.py +308 -0
  39. memos/mem_scheduler/{general_modules → memory_manage_modules}/retriever.py +34 -7
  40. memos/mem_scheduler/monitors/dispatcher_monitor.py +9 -8
  41. memos/mem_scheduler/monitors/general_monitor.py +119 -39
  42. memos/mem_scheduler/optimized_scheduler.py +124 -0
  43. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  44. memos/mem_scheduler/orm_modules/base_model.py +635 -0
  45. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  46. memos/mem_scheduler/scheduler_factory.py +2 -0
  47. memos/mem_scheduler/schemas/monitor_schemas.py +96 -29
  48. memos/mem_scheduler/utils/config_utils.py +100 -0
  49. memos/mem_scheduler/utils/db_utils.py +33 -0
  50. memos/mem_scheduler/utils/filter_utils.py +1 -1
  51. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  52. memos/memories/activation/kv.py +2 -1
  53. memos/memories/textual/item.py +95 -16
  54. memos/memories/textual/naive.py +1 -1
  55. memos/memories/textual/tree.py +27 -3
  56. memos/memories/textual/tree_text_memory/organize/handler.py +4 -2
  57. memos/memories/textual/tree_text_memory/organize/manager.py +28 -14
  58. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +1 -2
  59. memos/memories/textual/tree_text_memory/organize/reorganizer.py +75 -23
  60. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +7 -5
  61. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -2
  62. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  63. memos/memories/textual/tree_text_memory/retrieve/recall.py +70 -22
  64. memos/memories/textual/tree_text_memory/retrieve/searcher.py +101 -33
  65. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +5 -4
  66. memos/memos_tools/singleton.py +174 -0
  67. memos/memos_tools/thread_safe_dict.py +22 -0
  68. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  69. memos/parsers/factory.py +2 -0
  70. memos/reranker/concat.py +59 -0
  71. memos/reranker/cosine_local.py +1 -0
  72. memos/reranker/factory.py +5 -0
  73. memos/reranker/http_bge.py +225 -12
  74. memos/templates/mem_scheduler_prompts.py +242 -0
  75. memos/types.py +4 -1
  76. memos/api/context/context.py +0 -147
  77. memos/api/context/context_thread.py +0 -96
  78. memos/mem_scheduler/mos_for_test_scheduler.py +0 -146
  79. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/entry_points.txt +0 -0
  80. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info/licenses}/LICENSE +0 -0
  81. /memos/mem_scheduler/{general_modules → webservice_modules}/rabbitmq_service.py +0 -0
  82. /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
- all_keywords = [kw for item in self.queue for kw in item.keywords]
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, # Greater than or equal to 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
- Returns:
184
- float: The importance_score if it has been initialized (>=0),
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 get_importance_score is None.")
242
+ logger.warning("weight_vector of get_complex_score is None.")
193
243
  weight_vector = DEFAULT_WEIGHT_VECTOR_FOR_RANKING
194
- assert sum(weight_vector) == 1
195
- normalized_keywords_score = min(self.keywords_score * weight_vector[1], 5)
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
- self.sorting_score * weight_vector[0]
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 = 0
380
+ memory.recording_count = 1
316
381
  memory.keywords_score = 0
317
382
 
318
383
  # Step 4: Enforce max_capacity if set
319
- sorted_memories = sorted(
320
- self.memories,
321
- key=lambda item: item.get_importance_score(
322
- weight_vector=DEFAULT_WEIGHT_VECTOR_FOR_RANKING
323
- ),
324
- reverse=True,
325
- )
326
- # Keep only the top max_capacity memories
327
- self.memories = sorted_memories[: self.max_capacity]
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 filter_similar_memories(
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
@@ -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