MemoryOS 0.0.1__py3-none-any.whl → 0.1.12__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 (119) hide show
  1. memoryos-0.1.12.dist-info/METADATA +257 -0
  2. memoryos-0.1.12.dist-info/RECORD +117 -0
  3. memos/__init__.py +20 -1
  4. memos/api/start_api.py +420 -0
  5. memos/chunkers/__init__.py +4 -0
  6. memos/chunkers/base.py +24 -0
  7. memos/chunkers/factory.py +22 -0
  8. memos/chunkers/sentence_chunker.py +35 -0
  9. memos/configs/__init__.py +0 -0
  10. memos/configs/base.py +82 -0
  11. memos/configs/chunker.py +45 -0
  12. memos/configs/embedder.py +53 -0
  13. memos/configs/graph_db.py +45 -0
  14. memos/configs/llm.py +71 -0
  15. memos/configs/mem_chat.py +81 -0
  16. memos/configs/mem_cube.py +89 -0
  17. memos/configs/mem_os.py +70 -0
  18. memos/configs/mem_reader.py +53 -0
  19. memos/configs/mem_scheduler.py +78 -0
  20. memos/configs/memory.py +190 -0
  21. memos/configs/parser.py +38 -0
  22. memos/configs/utils.py +8 -0
  23. memos/configs/vec_db.py +64 -0
  24. memos/deprecation.py +262 -0
  25. memos/embedders/__init__.py +0 -0
  26. memos/embedders/base.py +15 -0
  27. memos/embedders/factory.py +23 -0
  28. memos/embedders/ollama.py +74 -0
  29. memos/embedders/sentence_transformer.py +40 -0
  30. memos/exceptions.py +30 -0
  31. memos/graph_dbs/__init__.py +0 -0
  32. memos/graph_dbs/base.py +215 -0
  33. memos/graph_dbs/factory.py +21 -0
  34. memos/graph_dbs/neo4j.py +827 -0
  35. memos/hello_world.py +97 -0
  36. memos/llms/__init__.py +0 -0
  37. memos/llms/base.py +16 -0
  38. memos/llms/factory.py +25 -0
  39. memos/llms/hf.py +231 -0
  40. memos/llms/ollama.py +82 -0
  41. memos/llms/openai.py +34 -0
  42. memos/llms/utils.py +14 -0
  43. memos/log.py +78 -0
  44. memos/mem_chat/__init__.py +0 -0
  45. memos/mem_chat/base.py +30 -0
  46. memos/mem_chat/factory.py +21 -0
  47. memos/mem_chat/simple.py +200 -0
  48. memos/mem_cube/__init__.py +0 -0
  49. memos/mem_cube/base.py +29 -0
  50. memos/mem_cube/general.py +146 -0
  51. memos/mem_cube/utils.py +24 -0
  52. memos/mem_os/client.py +5 -0
  53. memos/mem_os/core.py +819 -0
  54. memos/mem_os/main.py +12 -0
  55. memos/mem_os/product.py +89 -0
  56. memos/mem_reader/__init__.py +0 -0
  57. memos/mem_reader/base.py +27 -0
  58. memos/mem_reader/factory.py +21 -0
  59. memos/mem_reader/memory.py +298 -0
  60. memos/mem_reader/simple_struct.py +241 -0
  61. memos/mem_scheduler/__init__.py +0 -0
  62. memos/mem_scheduler/base_scheduler.py +164 -0
  63. memos/mem_scheduler/general_scheduler.py +305 -0
  64. memos/mem_scheduler/modules/__init__.py +0 -0
  65. memos/mem_scheduler/modules/base.py +74 -0
  66. memos/mem_scheduler/modules/dispatcher.py +103 -0
  67. memos/mem_scheduler/modules/monitor.py +82 -0
  68. memos/mem_scheduler/modules/redis_service.py +146 -0
  69. memos/mem_scheduler/modules/retriever.py +41 -0
  70. memos/mem_scheduler/modules/schemas.py +146 -0
  71. memos/mem_scheduler/scheduler_factory.py +21 -0
  72. memos/mem_scheduler/utils.py +26 -0
  73. memos/mem_user/user_manager.py +478 -0
  74. memos/memories/__init__.py +0 -0
  75. memos/memories/activation/__init__.py +0 -0
  76. memos/memories/activation/base.py +42 -0
  77. memos/memories/activation/item.py +25 -0
  78. memos/memories/activation/kv.py +232 -0
  79. memos/memories/base.py +19 -0
  80. memos/memories/factory.py +34 -0
  81. memos/memories/parametric/__init__.py +0 -0
  82. memos/memories/parametric/base.py +19 -0
  83. memos/memories/parametric/item.py +11 -0
  84. memos/memories/parametric/lora.py +41 -0
  85. memos/memories/textual/__init__.py +0 -0
  86. memos/memories/textual/base.py +89 -0
  87. memos/memories/textual/general.py +286 -0
  88. memos/memories/textual/item.py +167 -0
  89. memos/memories/textual/naive.py +185 -0
  90. memos/memories/textual/tree.py +289 -0
  91. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  92. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  93. memos/memories/textual/tree_text_memory/organize/manager.py +305 -0
  94. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  95. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +64 -0
  96. memos/memories/textual/tree_text_memory/retrieve/recall.py +158 -0
  97. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  98. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +13 -0
  99. memos/memories/textual/tree_text_memory/retrieve/searcher.py +166 -0
  100. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +68 -0
  101. memos/memories/textual/tree_text_memory/retrieve/utils.py +48 -0
  102. memos/parsers/__init__.py +0 -0
  103. memos/parsers/base.py +15 -0
  104. memos/parsers/factory.py +19 -0
  105. memos/parsers/markitdown.py +22 -0
  106. memos/settings.py +8 -0
  107. memos/templates/__init__.py +0 -0
  108. memos/templates/mem_reader_prompts.py +98 -0
  109. memos/templates/mem_scheduler_prompts.py +65 -0
  110. memos/types.py +55 -0
  111. memos/vec_dbs/__init__.py +0 -0
  112. memos/vec_dbs/base.py +105 -0
  113. memos/vec_dbs/factory.py +21 -0
  114. memos/vec_dbs/item.py +43 -0
  115. memos/vec_dbs/qdrant.py +292 -0
  116. memoryos-0.0.1.dist-info/METADATA +0 -53
  117. memoryos-0.0.1.dist-info/RECORD +0 -5
  118. {memoryos-0.0.1.dist-info → memoryos-0.1.12.dist-info}/LICENSE +0 -0
  119. {memoryos-0.0.1.dist-info → memoryos-0.1.12.dist-info}/WHEEL +0 -0
memos/mem_os/core.py ADDED
@@ -0,0 +1,819 @@
1
+ import os
2
+
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from threading import Lock
6
+ from typing import Any, Literal
7
+
8
+ from memos.configs.mem_os import MOSConfig
9
+ from memos.llms.factory import LLMFactory
10
+ from memos.log import get_logger
11
+ from memos.mem_cube.general import GeneralMemCube
12
+ from memos.mem_reader.factory import MemReaderFactory
13
+ from memos.mem_scheduler.general_scheduler import GeneralScheduler
14
+ from memos.mem_scheduler.modules.schemas import ANSWER_LABEL, QUERY_LABEL, ScheduleMessageItem
15
+ from memos.mem_scheduler.scheduler_factory import SchedulerFactory
16
+ from memos.mem_user.user_manager import UserManager, UserRole
17
+ from memos.memories.activation.item import ActivationMemoryItem
18
+ from memos.memories.parametric.item import ParametricMemoryItem
19
+ from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
20
+ from memos.types import ChatHistory, MessageList, MOSSearchResult
21
+
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class MOSCore:
27
+ """
28
+ The MOSCore (Memory Operating System Core) class manages multiple MemCube objects and their operations.
29
+ It provides methods for creating, searching, updating, and deleting MemCubes, supporting multi-user scenarios.
30
+ MOSCore acts as an operating system layer for handling and orchestrating MemCube instances.
31
+ """
32
+
33
+ def __init__(self, config: MOSConfig):
34
+ self.config = config
35
+ self.user_id = config.user_id
36
+ self.session_id = config.session_id
37
+ self.mem_cubes: dict[str, GeneralMemCube] = {}
38
+ self.chat_llm = LLMFactory.from_config(config.chat_model)
39
+ self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
40
+ self.chat_history_manager: dict[str, ChatHistory] = {}
41
+ self._register_chat_history()
42
+ self.user_manager = UserManager(user_id=self.user_id if self.user_id else "root")
43
+
44
+ # Validate user exists
45
+ if not self.user_manager.validate_user(self.user_id):
46
+ raise ValueError(
47
+ f"User '{self.user_id}' does not exist or is inactive. Please create user first."
48
+ )
49
+
50
+ # Lazy initialization marker
51
+ self._mem_scheduler_lock = Lock()
52
+ self.enable_mem_scheduler = self.config.get("enable_mem_scheduler", False)
53
+ self._mem_scheduler = None
54
+ logger.info(f"MOS initialized for user: {self.user_id}")
55
+
56
+ @property
57
+ def mem_scheduler(self) -> GeneralScheduler:
58
+ """Lazy-loaded property for memory scheduler."""
59
+ if self.enable_mem_scheduler and self._mem_scheduler is None:
60
+ self._initialize_mem_scheduler()
61
+ return self._mem_scheduler
62
+
63
+ @mem_scheduler.setter
64
+ def mem_scheduler(self, value: GeneralScheduler | None) -> None:
65
+ """Setter for memory scheduler with validation.
66
+
67
+ Args:
68
+ value: GeneralScheduler instance or None to disable
69
+ Raises:
70
+ TypeError: If value is neither GeneralScheduler nor None
71
+ """
72
+ with self._mem_scheduler_lock:
73
+ if value is not None and not isinstance(value, GeneralScheduler):
74
+ raise TypeError(f"Expected GeneralScheduler or None, got {type(value)}")
75
+
76
+ self._mem_scheduler = value
77
+
78
+ if value:
79
+ logger.info("Memory scheduler manually set")
80
+ else:
81
+ logger.debug("Memory scheduler cleared")
82
+
83
+ def _initialize_mem_scheduler(self):
84
+ """Initialize the memory scheduler on first access."""
85
+ if not self.config.enable_mem_scheduler:
86
+ logger.debug("Memory scheduler is disabled in config")
87
+ self._mem_scheduler = None
88
+ elif not hasattr(self.config, "mem_scheduler"):
89
+ logger.error("Config of Memory scheduler is not available")
90
+ self._mem_scheduler = None
91
+ else:
92
+ logger.info("Initializing memory scheduler...")
93
+ scheduler_config = self.config.mem_scheduler
94
+ self._mem_scheduler = SchedulerFactory.from_config(scheduler_config)
95
+ self._mem_scheduler.initialize_modules(chat_llm=self.chat_llm)
96
+ self._mem_scheduler.start()
97
+
98
+ def mem_scheduler_on(self) -> bool:
99
+ if not self.config.enable_mem_scheduler or self._mem_scheduler is None:
100
+ logger.error("Cannot start scheduler: disabled in configuration")
101
+
102
+ try:
103
+ self._mem_scheduler.start()
104
+ logger.info("Memory scheduler service started")
105
+ return True
106
+ except Exception as e:
107
+ logger.error(f"Failed to start scheduler: {e!s}")
108
+ return False
109
+
110
+ def mem_scheduler_off(self) -> bool:
111
+ if not self.config.enable_mem_scheduler:
112
+ logger.error("Cannot stop scheduler: disabled in configuration")
113
+
114
+ if self._mem_scheduler is None:
115
+ logger.warning("No scheduler instance to stop")
116
+ return False
117
+
118
+ try:
119
+ self._mem_scheduler.stop()
120
+ logger.info("Memory scheduler service stopped")
121
+ return True
122
+ except Exception as e:
123
+ logger.error(f"Failed to stop scheduler: {e!s}")
124
+ return False
125
+
126
+ def _register_chat_history(self, user_id: str | None = None) -> None:
127
+ """Initialize chat history with user ID."""
128
+ if user_id is None:
129
+ user_id = self.user_id
130
+ self.chat_history_manager[user_id] = ChatHistory(
131
+ user_id=user_id,
132
+ session_id=self.session_id,
133
+ created_at=datetime.now(),
134
+ total_messages=0,
135
+ chat_history=[],
136
+ )
137
+
138
+ def _validate_user_exists(self, user_id: str) -> None:
139
+ """Validate user exists and is active.
140
+
141
+ Args:
142
+ user_id (str): The user ID to validate.
143
+
144
+ Raises:
145
+ ValueError: If user doesn't exist or is inactive.
146
+ """
147
+ if not self.user_manager.validate_user(user_id):
148
+ raise ValueError(
149
+ f"User '{user_id}' does not exist or is inactive. Please register the user first."
150
+ )
151
+
152
+ def _validate_cube_access(self, user_id: str, cube_id: str) -> None:
153
+ """Validate user has access to the cube.
154
+
155
+ Args:
156
+ user_id (str): The user ID to validate.
157
+ cube_id (str): The cube ID to validate.
158
+
159
+ Raises:
160
+ ValueError: If user doesn't have access to the cube.
161
+ """
162
+ # First validate user exists
163
+ self._validate_user_exists(user_id)
164
+
165
+ # Then validate cube access
166
+ if not self.user_manager.validate_user_cube_access(user_id, cube_id):
167
+ raise ValueError(
168
+ f"User '{user_id}' does not have access to cube '{cube_id}'. Please register the cube first or request access."
169
+ )
170
+
171
+ def _get_all_documents(self, path: str) -> list[str]:
172
+ """Get all documents from path.
173
+
174
+ Args:
175
+ path (str): The path to get documents.
176
+
177
+ Returns:
178
+ list[str]: The list of documents.
179
+ """
180
+ documents = []
181
+
182
+ path_obj = Path(path)
183
+ doc_extensions = {".txt", ".pdf", ".json", ".md", ".ppt", ".pptx"}
184
+ for file_path in path_obj.rglob("*"):
185
+ if file_path.is_file() and (file_path.suffix.lower() in doc_extensions):
186
+ documents.append(str(file_path))
187
+ return documents
188
+
189
+ def chat(self, query: str, user_id: str | None = None) -> str:
190
+ """
191
+ Chat with the MOS.
192
+
193
+ Args:
194
+ query (str): The user's query.
195
+
196
+ Returns:
197
+ str: The response from the MOS.
198
+ """
199
+ target_user_id = user_id if user_id is not None else self.user_id
200
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
201
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
202
+ if target_user_id not in self.chat_history_manager:
203
+ self._register_chat_history(target_user_id)
204
+
205
+ chat_history = self.chat_history_manager[target_user_id]
206
+
207
+ if self.config.enable_textual_memory and self.mem_cubes:
208
+ memories_all = []
209
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
210
+ if mem_cube_id not in user_cube_ids:
211
+ continue
212
+ if not mem_cube.text_mem:
213
+ continue
214
+
215
+ # submit message to scheduler
216
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
217
+ message_item = ScheduleMessageItem(
218
+ user_id=target_user_id,
219
+ mem_cube_id=mem_cube_id,
220
+ mem_cube=mem_cube,
221
+ label=QUERY_LABEL,
222
+ content=query,
223
+ timestamp=datetime.now(),
224
+ )
225
+ self.mem_scheduler.submit_messages(messages=[message_item])
226
+
227
+ memories = mem_cube.text_mem.search(query, top_k=self.config.top_k)
228
+ memories_all.extend(memories)
229
+ logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
230
+ system_prompt = self._build_system_prompt(memories_all)
231
+ else:
232
+ system_prompt = self._build_system_prompt()
233
+ current_messages = [
234
+ {"role": "system", "content": system_prompt},
235
+ *chat_history.chat_history,
236
+ {"role": "user", "content": query},
237
+ ]
238
+ past_key_values = None
239
+
240
+ if self.config.enable_activation_memory:
241
+ assert self.config.chat_model.backend == "huggingface", (
242
+ "Activation memory only used for huggingface backend."
243
+ )
244
+ # TODO this only one cubes
245
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
246
+ if mem_cube_id not in user_cube_ids:
247
+ continue
248
+ if mem_cube.act_mem:
249
+ kv_cache = next(iter(mem_cube.act_mem.get_all()), None)
250
+ past_key_values = (
251
+ kv_cache.memory if (kv_cache and hasattr(kv_cache, "memory")) else None
252
+ )
253
+ break
254
+ # Generate response
255
+ response = self.chat_llm.generate(current_messages, past_key_values=past_key_values)
256
+ else:
257
+ response = self.chat_llm.generate(current_messages)
258
+ logger.info(f"🤖 [Assistant] {response}\n")
259
+ chat_history.chat_history.append({"role": "user", "content": query})
260
+ chat_history.chat_history.append({"role": "assistant", "content": response})
261
+ self.chat_history_manager[user_id] = chat_history
262
+
263
+ # submit message to scheduler
264
+ if len(accessible_cubes) == 1:
265
+ mem_cube_id = accessible_cubes[0].cube_id
266
+ mem_cube = self.mem_cubes[mem_cube_id]
267
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
268
+ message_item = ScheduleMessageItem(
269
+ user_id=target_user_id,
270
+ mem_cube_id=mem_cube_id,
271
+ mem_cube=mem_cube,
272
+ label=ANSWER_LABEL,
273
+ content=response,
274
+ timestamp=datetime.now(),
275
+ )
276
+ self.mem_scheduler.submit_messages(messages=[message_item])
277
+
278
+ return response
279
+
280
+ def _build_system_prompt(self, memories: list | None = None) -> str:
281
+ """Build system prompt with optional memories context."""
282
+ base_prompt = (
283
+ "You are a knowledgeable and helpful AI assistant. "
284
+ "You have access to conversation memories that help you provide more personalized responses. "
285
+ "Use the memories to understand the user's context, preferences, and past interactions. "
286
+ "If memories are provided, reference them naturally when relevant, but don't explicitly mention having memories."
287
+ )
288
+
289
+ if memories:
290
+ memory_context = "\n\n## Memories:\n"
291
+ for i, memory in enumerate(memories, 1):
292
+ memory_context += f"{i}. {memory.memory}\n"
293
+ return base_prompt + memory_context
294
+ return base_prompt
295
+
296
+ def _str_memories(
297
+ self, memories: list[TextualMemoryItem], mode: Literal["concise", "full"] = "full"
298
+ ) -> str:
299
+ """Format memories for display."""
300
+ if not memories:
301
+ return "No memories."
302
+ if mode == "concise":
303
+ return "\n".join(f"{i + 1}. {memory.memory}" for i, memory in enumerate(memories))
304
+ elif mode == "full":
305
+ return "\n".join(f"{i + 1}. {memory}" for i, memory in enumerate(memories))
306
+
307
+ def clear_messages(self, user_id: str | None = None) -> None:
308
+ """Clear chat history."""
309
+ user_id = user_id if user_id is not None else self.user_id
310
+ self._register_chat_history(user_id)
311
+
312
+ def create_user(
313
+ self, user_id: str, role: UserRole = UserRole.USER, user_name: str | None = None
314
+ ) -> str:
315
+ """Create a new user.
316
+
317
+ Args:
318
+ user_name (str): Name of the user.
319
+ role (UserRole): Role of the user.
320
+ user_id (str, optional): Custom user ID.
321
+
322
+ Returns:
323
+ str: The created user ID.
324
+ """
325
+ if not user_name:
326
+ user_name = user_id
327
+ return self.user_manager.create_user(user_name, role, user_id)
328
+
329
+ def list_users(self) -> list:
330
+ """List all active users.
331
+
332
+ Returns:
333
+ list: List of user information dictionaries.
334
+ """
335
+ users = self.user_manager.list_users()
336
+ return [
337
+ {
338
+ "user_id": user.user_id,
339
+ "user_name": user.user_name,
340
+ "role": user.role.value,
341
+ "created_at": user.created_at.isoformat(),
342
+ "is_active": user.is_active,
343
+ }
344
+ for user in users
345
+ ]
346
+
347
+ def create_cube_for_user(
348
+ self,
349
+ cube_name: str,
350
+ owner_id: str,
351
+ cube_path: str | None = None,
352
+ cube_id: str | None = None,
353
+ ) -> str:
354
+ """Create a new cube for the current user.
355
+
356
+ Args:
357
+ cube_name (str): Name of the cube.
358
+ cube_path (str, optional): Path to the cube.
359
+ cube_id (str, optional): Custom cube ID.
360
+
361
+ Returns:
362
+ str: The created cube ID.
363
+ """
364
+ return self.user_manager.create_cube(cube_name, owner_id, cube_path, cube_id)
365
+
366
+ def register_mem_cube(
367
+ self, mem_cube_name_or_path: str, mem_cube_id: str | None = None, user_id: str | None = None
368
+ ) -> None:
369
+ """
370
+ Register a MemCube with the MOS.
371
+
372
+ Args:
373
+ mem_cube_name_or_path (str): The name or path of the MemCube to register.
374
+ mem_cube_id (str, optional): The identifier for the MemCube. If not provided, a default ID is used.
375
+ """
376
+ target_user_id = user_id if user_id is not None else self.user_id
377
+ self._validate_user_exists(target_user_id)
378
+
379
+ if mem_cube_id is None:
380
+ mem_cube_id = mem_cube_name_or_path
381
+
382
+ if mem_cube_id in self.mem_cubes:
383
+ logger.info(f"MemCube with ID {mem_cube_id} already in MOS, skip install.")
384
+ else:
385
+ if os.path.exists(mem_cube_name_or_path):
386
+ self.mem_cubes[mem_cube_id] = GeneralMemCube.init_from_dir(mem_cube_name_or_path)
387
+ else:
388
+ logger.warning(
389
+ f"MemCube {mem_cube_name_or_path} does not exist, try to init from remote repo."
390
+ )
391
+ self.mem_cubes[mem_cube_id] = GeneralMemCube.init_from_remote_repo(
392
+ mem_cube_name_or_path
393
+ )
394
+ # Check if cube already exists in database
395
+ existing_cube = self.user_manager.get_cube(mem_cube_id)
396
+
397
+ if existing_cube:
398
+ # Cube exists, just add user to cube if not already associated
399
+ if not self.user_manager.validate_user_cube_access(target_user_id, mem_cube_id):
400
+ success = self.user_manager.add_user_to_cube(target_user_id, mem_cube_id)
401
+ if success:
402
+ logger.info(f"User {target_user_id} added to existing cube {mem_cube_id}")
403
+ else:
404
+ logger.error(f"Failed to add user {target_user_id} to cube {mem_cube_id}")
405
+ else:
406
+ logger.info(f"User {target_user_id} already has access to cube {mem_cube_id}")
407
+ else:
408
+ # Cube doesn't exist, create it
409
+ self.create_cube_for_user(
410
+ cube_name=mem_cube_name_or_path,
411
+ owner_id=target_user_id,
412
+ cube_id=mem_cube_id,
413
+ cube_path=mem_cube_name_or_path,
414
+ )
415
+ logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
416
+
417
+ def unregister_mem_cube(self, mem_cube_id: str, user_id: str | None = None) -> None:
418
+ """
419
+ Unregister a MemCube by its identifier.
420
+
421
+ Args:
422
+ mem_cube_id (str): The identifier of the MemCube to unregister.
423
+ """
424
+ if mem_cube_id in self.mem_cubes:
425
+ del self.mem_cubes[mem_cube_id]
426
+ else:
427
+ raise ValueError(f"MemCube with ID {mem_cube_id} does not exist.")
428
+
429
+ def search(
430
+ self, query: str, user_id: str | None = None, install_cube_ids: list[str] | None = None
431
+ ) -> MOSSearchResult:
432
+ """
433
+ Search for textual memories across all registered MemCubes.
434
+
435
+ Args:
436
+ query (str): The search query.
437
+ user_id (str, optional): The identifier of the user to search for.
438
+ If None, the default user is used.
439
+ install_cube_ids (list[str], optional): The list of MemCube IDs to install.
440
+ If None, all MemCube for the user is used.
441
+
442
+ Returns:
443
+ MemoryResult: A dictionary containing the search results.
444
+ """
445
+ target_user_id = user_id if user_id is not None else self.user_id
446
+ self._validate_user_exists(target_user_id)
447
+ # Get all cubes accessible by the target user
448
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
449
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
450
+
451
+ logger.info(
452
+ f"User {target_user_id} has access to {len(user_cube_ids)} cubes: {user_cube_ids}"
453
+ )
454
+ result: MOSSearchResult = {
455
+ "text_mem": [],
456
+ "act_mem": [],
457
+ "para_mem": [],
458
+ }
459
+ if install_cube_ids is None:
460
+ install_cube_ids = user_cube_ids
461
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
462
+ if (
463
+ (mem_cube_id in install_cube_ids)
464
+ and (mem_cube.text_mem is not None)
465
+ and self.config.enable_textual_memory
466
+ ):
467
+ memories = mem_cube.text_mem.search(query, top_k=self.config.top_k)
468
+ result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
469
+ logger.info(
470
+ f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
471
+ )
472
+ if (
473
+ (mem_cube_id in install_cube_ids)
474
+ and (mem_cube.act_mem is not None)
475
+ and self.config.enable_activation_memory
476
+ ):
477
+ memories = mem_cube.act_mem.extract(query)
478
+ result["act_mem"].append({"cube_id": mem_cube_id, "memories": [memories]})
479
+ logger.info(
480
+ f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
481
+ )
482
+ return result
483
+
484
+ def add(
485
+ self,
486
+ messages: MessageList | None = None,
487
+ memory_content: str | None = None,
488
+ doc_path: str | None = None,
489
+ mem_cube_id: str | None = None,
490
+ user_id: str | None = None,
491
+ ) -> None:
492
+ """
493
+ Add textual memories to a MemCube.
494
+
495
+ Args:
496
+ messages (Union[MessageList, str]): The path to a document or a list of messages.
497
+ memory_content (str, optional): The content of the memory to add.
498
+ doc_path (str, optional): The path to the document associated with the memory.
499
+ mem_cube_id (str, optional): The identifier of the MemCube to add the memories to.
500
+ If None, the default MemCube for the user is used.
501
+ user_id (str, optional): The identifier of the user to add the memories to.
502
+ If None, the default user is used.
503
+ """
504
+ assert (messages is not None) or (memory_content is not None) or (doc_path is not None), (
505
+ "messages_or_doc_path or memory_content or doc_path must be provided."
506
+ )
507
+ target_user_id = user_id if user_id is not None else self.user_id
508
+ if mem_cube_id is None:
509
+ # Try to find a default cube for the user
510
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
511
+ if not accessible_cubes:
512
+ raise ValueError(
513
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
514
+ )
515
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
516
+ else:
517
+ self._validate_cube_access(target_user_id, mem_cube_id)
518
+
519
+ if mem_cube_id not in self.mem_cubes:
520
+ raise ValueError(f"MemCube '{mem_cube_id}' is not loaded. Please register.")
521
+ if (
522
+ (messages is not None)
523
+ and self.config.enable_textual_memory
524
+ and self.mem_cubes[mem_cube_id].text_mem
525
+ ):
526
+ if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
527
+ add_memory = []
528
+ metadata = TextualMemoryMetadata(
529
+ user_id=self.user_id, session_id=self.session_id, source="conversation"
530
+ )
531
+ for message in messages:
532
+ add_memory.append(
533
+ TextualMemoryItem(memory=message["content"], metadata=metadata)
534
+ )
535
+ self.mem_cubes[mem_cube_id].text_mem.add(add_memory)
536
+ else:
537
+ messages_list = [messages]
538
+ memories = self.mem_reader.get_memory(
539
+ messages_list,
540
+ type="chat",
541
+ info={"user_id": target_user_id, "session_id": self.session_id},
542
+ )
543
+ for mem in memories:
544
+ self.mem_cubes[mem_cube_id].text_mem.add(mem)
545
+ if (
546
+ (memory_content is not None)
547
+ and self.config.enable_textual_memory
548
+ and self.mem_cubes[mem_cube_id].text_mem
549
+ ):
550
+ if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
551
+ metadata = TextualMemoryMetadata(
552
+ user_id=self.user_id, session_id=self.session_id, source="conversation"
553
+ )
554
+ self.mem_cubes[mem_cube_id].text_mem.add(
555
+ [TextualMemoryItem(memory=memory_content, metadata=metadata)]
556
+ )
557
+ else:
558
+ messages_list = [
559
+ [
560
+ {"role": "user", "content": memory_content},
561
+ {
562
+ "role": "assistant",
563
+ "content": "",
564
+ }, # add by str to keep the format,assistant role is empty
565
+ ]
566
+ ]
567
+ memories = self.mem_reader.get_memory(
568
+ messages_list,
569
+ type="chat",
570
+ info={"user_id": target_user_id, "session_id": self.session_id},
571
+ )
572
+ for mem in memories:
573
+ self.mem_cubes[mem_cube_id].text_mem.add(mem)
574
+ if (
575
+ (doc_path is not None)
576
+ and self.config.enable_textual_memory
577
+ and self.mem_cubes[mem_cube_id].text_mem
578
+ ):
579
+ documents = self._get_all_documents(doc_path)
580
+ doc_memory = self.mem_reader.get_memory(
581
+ documents,
582
+ type="doc",
583
+ info={"user_id": target_user_id, "session_id": self.session_id},
584
+ )
585
+ for mem in doc_memory:
586
+ self.mem_cubes[mem_cube_id].text_mem.add(mem)
587
+ logger.info(f"Add memory to {mem_cube_id} successfully")
588
+
589
+ def get(
590
+ self, mem_cube_id: str, memory_id: str, user_id: str | None = None
591
+ ) -> TextualMemoryItem | ActivationMemoryItem | ParametricMemoryItem:
592
+ """
593
+ Get a textual memory from a MemCube.
594
+
595
+ Args:
596
+ mem_cube_id (str): The identifier of the MemCube to get the memory from.
597
+ memory_id (str): The identifier of the memory to get.
598
+ user_id (str, optional): The identifier of the user to get the memory from.
599
+ If None, the default user is used.
600
+
601
+ Returns:
602
+ Union[TextualMemoryItem, ActivationMemoryItem, ParametricMemoryItem]: The requested memory item.
603
+ """
604
+ target_user_id = user_id if user_id is not None else self.user_id
605
+ # Validate user has access to this cube
606
+ self._validate_cube_access(target_user_id, mem_cube_id)
607
+ if mem_cube_id is None:
608
+ # Try to find a default cube for the user
609
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
610
+ if not accessible_cubes:
611
+ raise ValueError(
612
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
613
+ )
614
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
615
+ else:
616
+ self._validate_cube_access(target_user_id, mem_cube_id)
617
+
618
+ assert mem_cube_id in self.mem_cubes, (
619
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
620
+ )
621
+ return self.mem_cubes[mem_cube_id].text_mem.get(memory_id)
622
+
623
+ def get_all(
624
+ self, mem_cube_id: str | None = None, user_id: str | None = None
625
+ ) -> MOSSearchResult:
626
+ """
627
+ Get all textual memories from a MemCube.
628
+
629
+ Args:
630
+ mem_cube_id (str, optional): The identifier of the MemCube to get the memories from.
631
+ If None, all MemCube for the user is used.
632
+ user_id (str, optional): The identifier of the user to get the memories from.
633
+ If None, the default user is used.
634
+
635
+ Returns:
636
+ MemoryResult: A dictionary containing the search results.
637
+ """
638
+ result: MOSSearchResult = {"para_mem": [], "act_mem": [], "text_mem": []}
639
+ target_user_id = user_id if user_id is not None else self.user_id
640
+ # Validate user has access to this cube
641
+ if mem_cube_id is None:
642
+ # Try to find a default cube for the user
643
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
644
+ if not accessible_cubes:
645
+ raise ValueError(
646
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
647
+ )
648
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
649
+ else:
650
+ self._validate_cube_access(target_user_id, mem_cube_id)
651
+ if self.config.enable_textual_memory and self.mem_cubes[mem_cube_id].text_mem:
652
+ result["text_mem"].append(
653
+ {"cube_id": mem_cube_id, "memories": self.mem_cubes[mem_cube_id].text_mem.get_all()}
654
+ )
655
+ if self.config.enable_activation_memory and self.mem_cubes[mem_cube_id].act_mem:
656
+ result["act_mem"].append(
657
+ {"cube_id": mem_cube_id, "memories": self.mem_cubes[mem_cube_id].act_mem.get_all()}
658
+ )
659
+ return result
660
+
661
+ def update(
662
+ self,
663
+ mem_cube_id: str,
664
+ memory_id: str,
665
+ text_memory_item: TextualMemoryItem | dict[str, Any],
666
+ user_id: str | None = None,
667
+ ) -> None:
668
+ """
669
+ Update a textual memory in a MemCube by text_memory_id and text_memory_id.
670
+
671
+ Args:
672
+ mem_cube_id (str): The identifier of the MemCube to update the memory in.
673
+ memory_id (str): The identifier of the textual memory to update.
674
+ text_memory_item (TextualMemoryItem | dict[str, Any]): The updated textual memory item.
675
+ """
676
+ assert mem_cube_id in self.mem_cubes, (
677
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
678
+ )
679
+ target_user_id = user_id if user_id is not None else self.user_id
680
+ # Validate user has access to this cube
681
+ self._validate_cube_access(target_user_id, mem_cube_id)
682
+ if mem_cube_id is None:
683
+ # Try to find a default cube for the user
684
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
685
+ if not accessible_cubes:
686
+ raise ValueError(
687
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
688
+ )
689
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
690
+ else:
691
+ self._validate_cube_access(target_user_id, mem_cube_id)
692
+ if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
693
+ self.mem_cubes[mem_cube_id].text_mem.update(memory_id, memories=text_memory_item)
694
+ logger.info(f"MemCube {mem_cube_id} updated memory {memory_id}")
695
+ else:
696
+ logger.warning(
697
+ f" {self.mem_cubes[mem_cube_id].config.text_mem.backend} does not support update memory"
698
+ )
699
+
700
+ def delete(self, mem_cube_id: str, memory_id: str, user_id: str | None = None) -> None:
701
+ """
702
+ Delete a textual memory from a MemCube by memory_id.
703
+
704
+ Args:
705
+ mem_cube_id (str): The identifier of the MemCube to delete the memory from.
706
+ memory_id (str): The identifier of the memory to delete.
707
+ """
708
+ assert mem_cube_id in self.mem_cubes, (
709
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
710
+ )
711
+ target_user_id = user_id if user_id is not None else self.user_id
712
+ # Validate user has access to this cube
713
+ self._validate_cube_access(target_user_id, mem_cube_id)
714
+ if mem_cube_id is None:
715
+ # Try to find a default cube for the user
716
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
717
+ if not accessible_cubes:
718
+ raise ValueError(
719
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
720
+ )
721
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
722
+ else:
723
+ self._validate_cube_access(target_user_id, mem_cube_id)
724
+ self.mem_cubes[mem_cube_id].text_mem.delete(memory_id)
725
+ logger.info(f"MemCube {mem_cube_id} deleted memory {memory_id}")
726
+
727
+ def delete_all(self, mem_cube_id: str | None = None, user_id: str | None = None) -> None:
728
+ """
729
+ Delete all textual memories from a MemCube for user.
730
+
731
+ Args:
732
+ mem_cube_id (str): The identifier of the MemCube to delete the memories from.
733
+ """
734
+ assert mem_cube_id in self.mem_cubes, (
735
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
736
+ )
737
+ target_user_id = user_id if user_id is not None else self.user_id
738
+ # Validate user has access to this cube
739
+ self._validate_cube_access(target_user_id, mem_cube_id)
740
+ if mem_cube_id is None:
741
+ # Try to find a default cube for the user
742
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
743
+ if not accessible_cubes:
744
+ raise ValueError(
745
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
746
+ )
747
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
748
+ else:
749
+ self._validate_cube_access(target_user_id, mem_cube_id)
750
+ self.mem_cubes[mem_cube_id].text_mem.delete_all()
751
+ logger.info(f"MemCube {mem_cube_id} deleted all memories")
752
+
753
+ def dump(
754
+ self, dump_dir: str, user_id: str | None = None, mem_cube_id: str | None = None
755
+ ) -> None:
756
+ """Dump the MemCube to a dictionary.
757
+ Args:
758
+ dump_dir (str): The directory to dump the MemCube to.
759
+ user_id (str, optional): The identifier of the user to dump the MemCube from.
760
+ If None, the default user is used.
761
+ mem_cube_id (str, optional): The identifier of the MemCube to dump.
762
+ If None, the default MemCube for the user is used.
763
+ """
764
+ target_user_id = user_id if user_id is not None else self.user_id
765
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
766
+ if not mem_cube_id:
767
+ mem_cube_id = accessible_cubes[0].cube_id
768
+ if mem_cube_id not in self.mem_cubes:
769
+ raise ValueError(f"MemCube with ID {mem_cube_id} does not exist. please regiester")
770
+ self.mem_cubes[mem_cube_id].dump(dump_dir)
771
+ logger.info(f"MemCube {mem_cube_id} dumped to {dump_dir}")
772
+
773
+ def get_user_info(self) -> dict[str, Any]:
774
+ """Get current user information including accessible cubes.
775
+
776
+ Returns:
777
+ dict: User information and accessible cubes.
778
+ """
779
+ user = self.user_manager.get_user(self.user_id)
780
+ if not user:
781
+ return {}
782
+
783
+ accessible_cubes = self.user_manager.get_user_cubes(self.user_id)
784
+
785
+ return {
786
+ "user_id": user.user_id,
787
+ "user_name": user.user_name,
788
+ "role": user.role.value,
789
+ "created_at": user.created_at.isoformat(),
790
+ "accessible_cubes": [
791
+ {
792
+ "cube_id": cube.cube_id,
793
+ "cube_name": cube.cube_name,
794
+ "cube_path": cube.cube_path,
795
+ "owner_id": cube.owner_id,
796
+ "is_loaded": cube.cube_id in self.mem_cubes,
797
+ }
798
+ for cube in accessible_cubes
799
+ ],
800
+ }
801
+
802
+ def share_cube_with_user(self, cube_id: str, target_user_id: str) -> bool:
803
+ """Share a cube with another user.
804
+
805
+ Args:
806
+ cube_id (str): The cube ID to share.
807
+ target_user_id (str): The user ID to share with.
808
+
809
+ Returns:
810
+ bool: True if successful, False otherwise.
811
+ """
812
+ # Validate current user has access to this cube
813
+ self._validate_cube_access(cube_id, target_user_id)
814
+
815
+ # Validate target user exists
816
+ if not self.user_manager.validate_user(target_user_id):
817
+ raise ValueError(f"Target user '{target_user_id}' does not exist or is inactive.")
818
+
819
+ return self.user_manager.add_user_to_cube(target_user_id, cube_id)