MemoryOS 0.2.1__py3-none-any.whl → 0.2.2__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 (74) hide show
  1. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/METADATA +2 -1
  2. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/RECORD +72 -55
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +156 -65
  5. memos/api/context/context.py +147 -0
  6. memos/api/context/dependencies.py +90 -0
  7. memos/api/product_models.py +5 -1
  8. memos/api/routers/product_router.py +54 -26
  9. memos/configs/graph_db.py +49 -1
  10. memos/configs/internet_retriever.py +6 -0
  11. memos/configs/mem_os.py +5 -0
  12. memos/configs/mem_reader.py +9 -0
  13. memos/configs/mem_scheduler.py +18 -4
  14. memos/configs/mem_user.py +58 -0
  15. memos/graph_dbs/base.py +9 -1
  16. memos/graph_dbs/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +1364 -0
  18. memos/graph_dbs/neo4j.py +4 -4
  19. memos/log.py +1 -1
  20. memos/mem_cube/utils.py +13 -6
  21. memos/mem_os/core.py +140 -30
  22. memos/mem_os/main.py +1 -1
  23. memos/mem_os/product.py +266 -152
  24. memos/mem_os/utils/format_utils.py +314 -67
  25. memos/mem_reader/simple_struct.py +13 -5
  26. memos/mem_scheduler/base_scheduler.py +220 -250
  27. memos/mem_scheduler/general_scheduler.py +193 -73
  28. memos/mem_scheduler/modules/base.py +5 -5
  29. memos/mem_scheduler/modules/dispatcher.py +6 -9
  30. memos/mem_scheduler/modules/misc.py +81 -16
  31. memos/mem_scheduler/modules/monitor.py +52 -41
  32. memos/mem_scheduler/modules/rabbitmq_service.py +9 -7
  33. memos/mem_scheduler/modules/retriever.py +108 -191
  34. memos/mem_scheduler/modules/scheduler_logger.py +255 -0
  35. memos/mem_scheduler/mos_for_test_scheduler.py +16 -19
  36. memos/mem_scheduler/schemas/__init__.py +0 -0
  37. memos/mem_scheduler/schemas/general_schemas.py +43 -0
  38. memos/mem_scheduler/schemas/message_schemas.py +148 -0
  39. memos/mem_scheduler/schemas/monitor_schemas.py +329 -0
  40. memos/mem_scheduler/utils/__init__.py +0 -0
  41. memos/mem_scheduler/utils/filter_utils.py +176 -0
  42. memos/mem_scheduler/utils/misc_utils.py +61 -0
  43. memos/mem_user/factory.py +94 -0
  44. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  45. memos/mem_user/mysql_user_manager.py +500 -0
  46. memos/mem_user/persistent_factory.py +96 -0
  47. memos/mem_user/user_manager.py +4 -4
  48. memos/memories/activation/item.py +4 -0
  49. memos/memories/textual/base.py +1 -1
  50. memos/memories/textual/general.py +35 -91
  51. memos/memories/textual/item.py +5 -33
  52. memos/memories/textual/tree.py +13 -7
  53. memos/memories/textual/tree_text_memory/organize/conflict.py +4 -2
  54. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +47 -43
  55. memos/memories/textual/tree_text_memory/organize/reorganizer.py +8 -5
  56. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  57. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  58. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  59. memos/memories/textual/tree_text_memory/retrieve/searcher.py +46 -23
  60. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -15
  61. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  62. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  63. memos/memos_tools/dinding_report_bot.py +422 -0
  64. memos/memos_tools/notification_service.py +44 -0
  65. memos/memos_tools/notification_utils.py +96 -0
  66. memos/settings.py +3 -1
  67. memos/templates/mem_reader_prompts.py +2 -1
  68. memos/templates/mem_scheduler_prompts.py +41 -7
  69. memos/templates/mos_prompts.py +87 -0
  70. memos/mem_scheduler/modules/schemas.py +0 -328
  71. memos/mem_scheduler/utils.py +0 -75
  72. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
  73. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/WHEEL +0 -0
  74. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,255 @@
1
+ from collections.abc import Callable
2
+
3
+ from memos.log import get_logger
4
+ from memos.mem_cube.general import GeneralMemCube
5
+ from memos.mem_scheduler.modules.base import BaseSchedulerModule
6
+ from memos.mem_scheduler.schemas.general_schemas import (
7
+ ACTIVATION_MEMORY_TYPE,
8
+ ADD_LABEL,
9
+ LONG_TERM_MEMORY_TYPE,
10
+ NOT_INITIALIZED,
11
+ PARAMETER_MEMORY_TYPE,
12
+ QUERY_LABEL,
13
+ TEXT_MEMORY_TYPE,
14
+ USER_INPUT_TYPE,
15
+ WORKING_MEMORY_TYPE,
16
+ )
17
+ from memos.mem_scheduler.schemas.message_schemas import (
18
+ ScheduleLogForWebItem,
19
+ ScheduleMessageItem,
20
+ )
21
+ from memos.mem_scheduler.utils.filter_utils import (
22
+ transform_name_to_key,
23
+ )
24
+ from memos.mem_scheduler.utils.misc_utils import log_exceptions
25
+ from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
26
+
27
+
28
+ logger = get_logger(__name__)
29
+
30
+
31
+ class SchedulerLoggerModule(BaseSchedulerModule):
32
+ def __init__(self):
33
+ """
34
+ Initialize RabbitMQ connection settings.
35
+ """
36
+ super().__init__()
37
+
38
+ @log_exceptions(logger=logger)
39
+ def create_autofilled_log_item(
40
+ self,
41
+ log_content: str,
42
+ label: str,
43
+ from_memory_type: str,
44
+ to_memory_type: str,
45
+ user_id: str,
46
+ mem_cube_id: str,
47
+ mem_cube: GeneralMemCube,
48
+ ) -> ScheduleLogForWebItem:
49
+ text_mem_base: TreeTextMemory = mem_cube.text_mem
50
+ current_memory_sizes = text_mem_base.get_current_memory_size()
51
+ current_memory_sizes = {
52
+ "long_term_memory_size": current_memory_sizes.get("LongTermMemory", 0),
53
+ "user_memory_size": current_memory_sizes.get("UserMemory", 0),
54
+ "working_memory_size": current_memory_sizes.get("WorkingMemory", 0),
55
+ "transformed_act_memory_size": NOT_INITIALIZED,
56
+ "parameter_memory_size": NOT_INITIALIZED,
57
+ }
58
+ memory_capacities = {
59
+ "long_term_memory_capacity": text_mem_base.memory_manager.memory_size["LongTermMemory"],
60
+ "user_memory_capacity": text_mem_base.memory_manager.memory_size["UserMemory"],
61
+ "working_memory_capacity": text_mem_base.memory_manager.memory_size["WorkingMemory"],
62
+ "transformed_act_memory_capacity": NOT_INITIALIZED,
63
+ "parameter_memory_capacity": NOT_INITIALIZED,
64
+ }
65
+
66
+ if hasattr(self, "monitor"):
67
+ if (
68
+ user_id in self.monitor.activation_memory_monitors
69
+ and mem_cube_id in self.monitor.activation_memory_monitors[user_id]
70
+ ):
71
+ activation_monitor = self.monitor.activation_memory_monitors[user_id][mem_cube_id]
72
+ transformed_act_memory_size = len(activation_monitor.memories)
73
+ logger.info(
74
+ f'activation_memory_monitors currently has "{transformed_act_memory_size}" transformed memory size'
75
+ )
76
+ else:
77
+ transformed_act_memory_size = 0
78
+ logger.info(
79
+ f'activation_memory_monitors is not initialized for user "{user_id}" and mem_cube "{mem_cube_id}'
80
+ )
81
+ current_memory_sizes["transformed_act_memory_size"] = transformed_act_memory_size
82
+ current_memory_sizes["parameter_memory_size"] = 1
83
+
84
+ memory_capacities["transformed_act_memory_capacity"] = (
85
+ self.monitor.activation_mem_monitor_capacity
86
+ )
87
+ memory_capacities["parameter_memory_capacity"] = 1
88
+
89
+ log_message = ScheduleLogForWebItem(
90
+ user_id=user_id,
91
+ mem_cube_id=mem_cube_id,
92
+ label=label,
93
+ from_memory_type=from_memory_type,
94
+ to_memory_type=to_memory_type,
95
+ log_content=log_content,
96
+ current_memory_sizes=current_memory_sizes,
97
+ memory_capacities=memory_capacities,
98
+ )
99
+ return log_message
100
+
101
+ @log_exceptions(logger=logger)
102
+ def log_working_memory_replacement(
103
+ self,
104
+ original_memory: list[TextualMemoryItem],
105
+ new_memory: list[TextualMemoryItem],
106
+ user_id: str,
107
+ mem_cube_id: str,
108
+ mem_cube: GeneralMemCube,
109
+ log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
110
+ ):
111
+ """Log changes when working memory is replaced."""
112
+ memory_type_map = {
113
+ transform_name_to_key(name=m.memory): m.metadata.memory_type
114
+ for m in original_memory + new_memory
115
+ }
116
+
117
+ original_text_memories = [m.memory for m in original_memory]
118
+ new_text_memories = [m.memory for m in new_memory]
119
+
120
+ # Convert to sets for efficient difference operations
121
+ original_set = set(original_text_memories)
122
+ new_set = set(new_text_memories)
123
+
124
+ # Identify changes
125
+ added_memories = list(new_set - original_set) # Present in new but not original
126
+
127
+ # recording messages
128
+ for memory in added_memories:
129
+ normalized_mem = transform_name_to_key(name=memory)
130
+ if normalized_mem not in memory_type_map:
131
+ logger.error(f"Memory text not found in type mapping: {memory[:50]}...")
132
+ # Get the memory type from the map, default to LONG_TERM_MEMORY_TYPE if not found
133
+ mem_type = memory_type_map.get(normalized_mem, LONG_TERM_MEMORY_TYPE)
134
+
135
+ if mem_type == WORKING_MEMORY_TYPE:
136
+ logger.warning(f"Memory already in working memory: {memory[:50]}...")
137
+ continue
138
+
139
+ log_message = self.create_autofilled_log_item(
140
+ log_content=memory,
141
+ label=QUERY_LABEL,
142
+ from_memory_type=mem_type,
143
+ to_memory_type=WORKING_MEMORY_TYPE,
144
+ user_id=user_id,
145
+ mem_cube_id=mem_cube_id,
146
+ mem_cube=mem_cube,
147
+ )
148
+ log_func_callback([log_message])
149
+ logger.info(
150
+ f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
151
+ f"transformed to {WORKING_MEMORY_TYPE} memories."
152
+ )
153
+
154
+ @log_exceptions(logger=logger)
155
+ def log_activation_memory_update(
156
+ self,
157
+ original_text_memories: list[str],
158
+ new_text_memories: list[str],
159
+ label: str,
160
+ user_id: str,
161
+ mem_cube_id: str,
162
+ mem_cube: GeneralMemCube,
163
+ log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
164
+ ):
165
+ """Log changes when activation memory is updated."""
166
+ original_set = set(original_text_memories)
167
+ new_set = set(new_text_memories)
168
+
169
+ # Identify changes
170
+ added_memories = list(new_set - original_set) # Present in new but not original
171
+
172
+ # recording messages
173
+ for mem in added_memories:
174
+ log_message_a = self.create_autofilled_log_item(
175
+ log_content=mem,
176
+ label=label,
177
+ from_memory_type=TEXT_MEMORY_TYPE,
178
+ to_memory_type=ACTIVATION_MEMORY_TYPE,
179
+ user_id=user_id,
180
+ mem_cube_id=mem_cube_id,
181
+ mem_cube=mem_cube,
182
+ )
183
+ log_message_b = self.create_autofilled_log_item(
184
+ log_content=mem,
185
+ label=label,
186
+ from_memory_type=ACTIVATION_MEMORY_TYPE,
187
+ to_memory_type=PARAMETER_MEMORY_TYPE,
188
+ user_id=user_id,
189
+ mem_cube_id=mem_cube_id,
190
+ mem_cube=mem_cube,
191
+ )
192
+ log_func_callback([log_message_a, log_message_b])
193
+ logger.info(
194
+ f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
195
+ f"transformed to {WORKING_MEMORY_TYPE} memories."
196
+ )
197
+
198
+ @log_exceptions(logger=logger)
199
+ def log_adding_memory(
200
+ self,
201
+ memory: str,
202
+ memory_type: str,
203
+ user_id: str,
204
+ mem_cube_id: str,
205
+ mem_cube: GeneralMemCube,
206
+ log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
207
+ ):
208
+ """Log changes when working memory is replaced."""
209
+ log_message = self.create_autofilled_log_item(
210
+ log_content=memory,
211
+ label=ADD_LABEL,
212
+ from_memory_type=USER_INPUT_TYPE,
213
+ to_memory_type=memory_type,
214
+ user_id=user_id,
215
+ mem_cube_id=mem_cube_id,
216
+ mem_cube=mem_cube,
217
+ )
218
+ log_func_callback([log_message])
219
+ logger.info(
220
+ f"{USER_INPUT_TYPE} memory for user {user_id} "
221
+ f"converted to {memory_type} memory in mem_cube {mem_cube_id}: {memory}"
222
+ )
223
+
224
+ @log_exceptions(logger=logger)
225
+ def validate_schedule_message(self, message: ScheduleMessageItem, label: str):
226
+ """Validate if the message matches the expected label.
227
+
228
+ Args:
229
+ message: Incoming message item to validate.
230
+ label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
231
+
232
+ Returns:
233
+ bool: True if validation passed, False otherwise.
234
+ """
235
+ if message.label != label:
236
+ logger.error(f"Handler validation failed: expected={label}, actual={message.label}")
237
+ return False
238
+ return True
239
+
240
+ @log_exceptions(logger=logger)
241
+ def validate_schedule_messages(self, messages: list[ScheduleMessageItem], label: str):
242
+ """Validate if all messages match the expected label.
243
+
244
+ Args:
245
+ messages: List of message items to validate.
246
+ label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
247
+
248
+ Returns:
249
+ bool: True if all messages passed validation, False if any failed.
250
+ """
251
+ for message in messages:
252
+ if not self.validate_schedule_message(message, label):
253
+ logger.error("Message batch contains invalid labels, aborting processing")
254
+ return False
255
+ return True
@@ -3,12 +3,12 @@ from datetime import datetime
3
3
  from memos.configs.mem_os import MOSConfig
4
4
  from memos.log import get_logger
5
5
  from memos.mem_os.main import MOS
6
- from memos.mem_scheduler.modules.schemas import (
6
+ from memos.mem_scheduler.schemas.general_schemas import (
7
7
  ANSWER_LABEL,
8
8
  MONITOR_WORKING_MEMORY_TYPE,
9
9
  QUERY_LABEL,
10
- ScheduleMessageItem,
11
10
  )
11
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
12
12
 
13
13
 
14
14
  logger = get_logger(__name__)
@@ -54,24 +54,20 @@ class MOSForTestScheduler(MOS):
54
54
  if not mem_cube.text_mem:
55
55
  continue
56
56
 
57
- # submit message to scheduler
58
- if self.enable_mem_scheduler and self.mem_scheduler is not None:
59
- message_item = ScheduleMessageItem(
60
- user_id=target_user_id,
61
- mem_cube_id=mem_cube_id,
62
- mem_cube=mem_cube,
63
- label=QUERY_LABEL,
64
- content=query,
65
- timestamp=datetime.now(),
66
- )
67
- self.mem_scheduler.submit_messages(messages=[message_item])
68
-
69
- self.mem_scheduler.monitor.register_memory_manager_if_not_exists(
70
- user_id=user_id,
57
+ message_item = ScheduleMessageItem(
58
+ user_id=target_user_id,
71
59
  mem_cube_id=mem_cube_id,
72
- memory_monitors=self.mem_scheduler.monitor.working_memory_monitors,
73
- max_capacity=self.mem_scheduler.monitor.working_mem_monitor_capacity,
60
+ mem_cube=mem_cube,
61
+ label=QUERY_LABEL,
62
+ content=query,
63
+ timestamp=datetime.now(),
74
64
  )
65
+ cur_working_memories = [m.memory for m in mem_cube.text_mem.get_working_memory()]
66
+ print(f"Working memories before schedule: {cur_working_memories}")
67
+
68
+ # --- force to run mem_scheduler ---
69
+ self.mem_scheduler.monitor.query_trigger_interval = 0
70
+ self.mem_scheduler._query_message_consumer(messages=[message_item])
75
71
 
76
72
  # from scheduler
77
73
  scheduler_memories = self.mem_scheduler.monitor.get_monitor_memories(
@@ -80,6 +76,7 @@ class MOSForTestScheduler(MOS):
80
76
  memory_type=MONITOR_WORKING_MEMORY_TYPE,
81
77
  top_k=topk_for_scheduler,
82
78
  )
79
+ print(f"Working memories after schedule: {scheduler_memories}")
83
80
  memories_all.extend(scheduler_memories)
84
81
 
85
82
  # from mem_cube
@@ -87,6 +84,7 @@ class MOSForTestScheduler(MOS):
87
84
  query, top_k=self.config.top_k - topk_for_scheduler
88
85
  )
89
86
  text_memories = [m.memory for m in memories]
87
+ print(f"Search results with new working memories: {text_memories}")
90
88
  memories_all.extend(text_memories)
91
89
 
92
90
  memories_all = list(set(memories_all))
@@ -139,5 +137,4 @@ class MOSForTestScheduler(MOS):
139
137
  timestamp=datetime.now(),
140
138
  )
141
139
  self.mem_scheduler.submit_messages(messages=[message_item])
142
-
143
140
  return response
File without changes
@@ -0,0 +1,43 @@
1
+ from pathlib import Path
2
+ from typing import NewType
3
+
4
+
5
+ FILE_PATH = Path(__file__).absolute()
6
+ BASE_DIR = FILE_PATH.parent.parent.parent.parent.parent
7
+
8
+ QUERY_LABEL = "query"
9
+ ANSWER_LABEL = "answer"
10
+ ADD_LABEL = "add"
11
+
12
+ TreeTextMemory_SEARCH_METHOD = "tree_text_memory_search"
13
+ TextMemory_SEARCH_METHOD = "text_memory_search"
14
+ DIRECT_EXCHANGE_TYPE = "direct"
15
+ FANOUT_EXCHANGE_TYPE = "fanout"
16
+ DEFAULT_WORKING_MEM_MONITOR_SIZE_LIMIT = 20
17
+ DEFAULT_ACTIVATION_MEM_MONITOR_SIZE_LIMIT = 5
18
+ DEFAULT_ACT_MEM_DUMP_PATH = f"{BASE_DIR}/outputs/mem_scheduler/mem_cube_scheduler_test.kv_cache"
19
+ DEFAULT_THREAD__POOL_MAX_WORKERS = 5
20
+ DEFAULT_CONSUME_INTERVAL_SECONDS = 3
21
+ NOT_INITIALIZED = -1
22
+
23
+
24
+ # web log
25
+ LONG_TERM_MEMORY_TYPE = "LongTermMemory"
26
+ USER_MEMORY_TYPE = "UserMemory"
27
+ WORKING_MEMORY_TYPE = "WorkingMemory"
28
+ TEXT_MEMORY_TYPE = "TextMemory"
29
+ ACTIVATION_MEMORY_TYPE = "ActivationMemory"
30
+ PARAMETER_MEMORY_TYPE = "ParameterMemory"
31
+ USER_INPUT_TYPE = "UserInput"
32
+ NOT_APPLICABLE_TYPE = "NotApplicable"
33
+
34
+ # monitors
35
+ MONITOR_WORKING_MEMORY_TYPE = "MonitorWorkingMemoryType"
36
+ MONITOR_ACTIVATION_MEMORY_TYPE = "MonitorActivationMemoryType"
37
+ DEFAULT_MAX_QUERY_KEY_WORDS = 1000
38
+ DEFAULT_WEIGHT_VECTOR_FOR_RANKING = [0.9, 0.05, 0.05]
39
+
40
+
41
+ # new types
42
+ UserID = NewType("UserID", str)
43
+ MemCubeID = NewType("CubeID", str)
@@ -0,0 +1,148 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+ from uuid import uuid4
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, field_serializer
6
+ from typing_extensions import TypedDict
7
+
8
+ from memos.log import get_logger
9
+ from memos.mem_cube.general import GeneralMemCube
10
+ from memos.mem_scheduler.modules.misc import DictConversionMixin
11
+
12
+ from .general_schemas import NOT_INITIALIZED
13
+
14
+
15
+ logger = get_logger(__name__)
16
+
17
+ DEFAULT_MEMORY_SIZES = {
18
+ "long_term_memory_size": NOT_INITIALIZED,
19
+ "user_memory_size": NOT_INITIALIZED,
20
+ "working_memory_size": NOT_INITIALIZED,
21
+ "transformed_act_memory_size": NOT_INITIALIZED,
22
+ "parameter_memory_size": NOT_INITIALIZED,
23
+ }
24
+
25
+ DEFAULT_MEMORY_CAPACITIES = {
26
+ "long_term_memory_capacity": 10000,
27
+ "user_memory_capacity": 10000,
28
+ "working_memory_capacity": 20,
29
+ "transformed_act_memory_capacity": NOT_INITIALIZED,
30
+ "parameter_memory_capacity": NOT_INITIALIZED,
31
+ }
32
+
33
+
34
+ class ScheduleMessageItem(BaseModel, DictConversionMixin):
35
+ item_id: str = Field(description="uuid", default_factory=lambda: str(uuid4()))
36
+ user_id: str = Field(..., description="user id")
37
+ mem_cube_id: str = Field(..., description="memcube id")
38
+ label: str = Field(..., description="Label of the schedule message")
39
+ mem_cube: GeneralMemCube | str = Field(..., description="memcube for schedule")
40
+ content: str = Field(..., description="Content of the schedule message")
41
+ timestamp: datetime = Field(
42
+ default_factory=lambda: datetime.utcnow(), description="submit time for schedule_messages"
43
+ )
44
+
45
+ # Pydantic V2 model configuration
46
+ model_config = ConfigDict(
47
+ # Allows arbitrary Python types as model fields without validation
48
+ # Required when using custom types like GeneralMemCube that aren't Pydantic models
49
+ arbitrary_types_allowed=True,
50
+ # Additional metadata for JSON Schema generation
51
+ json_schema_extra={
52
+ # Example payload demonstrating the expected structure and sample values
53
+ # Used for API documentation, testing, and developer reference
54
+ "example": {
55
+ "item_id": "123e4567-e89b-12d3-a456-426614174000", # Sample UUID
56
+ "user_id": "user123", # Example user identifier
57
+ "mem_cube_id": "cube456", # Sample memory cube ID
58
+ "label": "sample_label", # Demonstration label value
59
+ "mem_cube": "obj of GeneralMemCube", # Added mem_cube example
60
+ "content": "sample content", # Example message content
61
+ "timestamp": "2024-07-22T12:00:00Z", # Added timestamp example
62
+ }
63
+ },
64
+ )
65
+
66
+ @field_serializer("mem_cube")
67
+ def serialize_mem_cube(self, cube: GeneralMemCube | str, _info) -> str:
68
+ """Custom serializer for GeneralMemCube objects to string representation"""
69
+ if isinstance(cube, str):
70
+ return cube
71
+ return f"<GeneralMemCube:{id(cube)}>"
72
+
73
+ def to_dict(self) -> dict:
74
+ """Convert model to dictionary suitable for Redis Stream"""
75
+ return {
76
+ "item_id": self.item_id,
77
+ "user_id": self.user_id,
78
+ "cube_id": self.mem_cube_id,
79
+ "label": self.label,
80
+ "cube": "Not Applicable", # Custom cube serialization
81
+ "content": self.content,
82
+ "timestamp": self.timestamp.isoformat(),
83
+ }
84
+
85
+ @classmethod
86
+ def from_dict(cls, data: dict) -> "ScheduleMessageItem":
87
+ """Create model from Redis Stream dictionary"""
88
+ return cls(
89
+ item_id=data.get("item_id", str(uuid4())),
90
+ user_id=data["user_id"],
91
+ cube_id=data["cube_id"],
92
+ label=data["label"],
93
+ cube="Not Applicable", # Custom cube deserialization
94
+ content=data["content"],
95
+ timestamp=datetime.fromisoformat(data["timestamp"]),
96
+ )
97
+
98
+
99
+ class MemorySizes(TypedDict):
100
+ long_term_memory_size: int
101
+ user_memory_size: int
102
+ working_memory_size: int
103
+ transformed_act_memory_size: int
104
+
105
+
106
+ class MemoryCapacities(TypedDict):
107
+ long_term_memory_capacity: int
108
+ user_memory_capacity: int
109
+ working_memory_capacity: int
110
+ transformed_act_memory_capacity: int
111
+
112
+
113
+ class ScheduleLogForWebItem(BaseModel, DictConversionMixin):
114
+ item_id: str = Field(
115
+ description="Unique identifier for the log entry", default_factory=lambda: str(uuid4())
116
+ )
117
+ user_id: str = Field(..., description="Identifier for the user associated with the log")
118
+ mem_cube_id: str = Field(
119
+ ..., description="Identifier for the memcube associated with this log entry"
120
+ )
121
+ label: str = Field(..., description="Label categorizing the type of log")
122
+ from_memory_type: str = Field(..., description="Source memory type")
123
+ to_memory_type: str = Field(..., description="Destination memory type")
124
+ log_content: str = Field(..., description="Detailed content of the log entry")
125
+ current_memory_sizes: MemorySizes = Field(
126
+ default_factory=lambda: dict(DEFAULT_MEMORY_SIZES),
127
+ description="Current utilization of memory partitions",
128
+ )
129
+ memory_capacities: MemoryCapacities = Field(
130
+ default_factory=lambda: dict(DEFAULT_MEMORY_CAPACITIES),
131
+ description="Maximum capacities of memory partitions",
132
+ )
133
+ timestamp: datetime = Field(
134
+ default_factory=lambda: datetime.utcnow(),
135
+ description="Timestamp indicating when the log entry was created",
136
+ )
137
+
138
+ def debug_info(self) -> dict[str, Any]:
139
+ """Return structured debug information for logging purposes."""
140
+ return {
141
+ "log_id": self.item_id,
142
+ "user_id": self.user_id,
143
+ "mem_cube_id": self.mem_cube_id,
144
+ "operation": f"{self.from_memory_type} → {self.to_memory_type}",
145
+ "label": self.label,
146
+ "content_length": len(self.log_content),
147
+ "timestamp": self.timestamp.isoformat(),
148
+ }