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.
- memoryos-0.1.12.dist-info/METADATA +257 -0
- memoryos-0.1.12.dist-info/RECORD +117 -0
- memos/__init__.py +20 -1
- memos/api/start_api.py +420 -0
- memos/chunkers/__init__.py +4 -0
- memos/chunkers/base.py +24 -0
- memos/chunkers/factory.py +22 -0
- memos/chunkers/sentence_chunker.py +35 -0
- memos/configs/__init__.py +0 -0
- memos/configs/base.py +82 -0
- memos/configs/chunker.py +45 -0
- memos/configs/embedder.py +53 -0
- memos/configs/graph_db.py +45 -0
- memos/configs/llm.py +71 -0
- memos/configs/mem_chat.py +81 -0
- memos/configs/mem_cube.py +89 -0
- memos/configs/mem_os.py +70 -0
- memos/configs/mem_reader.py +53 -0
- memos/configs/mem_scheduler.py +78 -0
- memos/configs/memory.py +190 -0
- memos/configs/parser.py +38 -0
- memos/configs/utils.py +8 -0
- memos/configs/vec_db.py +64 -0
- memos/deprecation.py +262 -0
- memos/embedders/__init__.py +0 -0
- memos/embedders/base.py +15 -0
- memos/embedders/factory.py +23 -0
- memos/embedders/ollama.py +74 -0
- memos/embedders/sentence_transformer.py +40 -0
- memos/exceptions.py +30 -0
- memos/graph_dbs/__init__.py +0 -0
- memos/graph_dbs/base.py +215 -0
- memos/graph_dbs/factory.py +21 -0
- memos/graph_dbs/neo4j.py +827 -0
- memos/hello_world.py +97 -0
- memos/llms/__init__.py +0 -0
- memos/llms/base.py +16 -0
- memos/llms/factory.py +25 -0
- memos/llms/hf.py +231 -0
- memos/llms/ollama.py +82 -0
- memos/llms/openai.py +34 -0
- memos/llms/utils.py +14 -0
- memos/log.py +78 -0
- memos/mem_chat/__init__.py +0 -0
- memos/mem_chat/base.py +30 -0
- memos/mem_chat/factory.py +21 -0
- memos/mem_chat/simple.py +200 -0
- memos/mem_cube/__init__.py +0 -0
- memos/mem_cube/base.py +29 -0
- memos/mem_cube/general.py +146 -0
- memos/mem_cube/utils.py +24 -0
- memos/mem_os/client.py +5 -0
- memos/mem_os/core.py +819 -0
- memos/mem_os/main.py +12 -0
- memos/mem_os/product.py +89 -0
- memos/mem_reader/__init__.py +0 -0
- memos/mem_reader/base.py +27 -0
- memos/mem_reader/factory.py +21 -0
- memos/mem_reader/memory.py +298 -0
- memos/mem_reader/simple_struct.py +241 -0
- memos/mem_scheduler/__init__.py +0 -0
- memos/mem_scheduler/base_scheduler.py +164 -0
- memos/mem_scheduler/general_scheduler.py +305 -0
- memos/mem_scheduler/modules/__init__.py +0 -0
- memos/mem_scheduler/modules/base.py +74 -0
- memos/mem_scheduler/modules/dispatcher.py +103 -0
- memos/mem_scheduler/modules/monitor.py +82 -0
- memos/mem_scheduler/modules/redis_service.py +146 -0
- memos/mem_scheduler/modules/retriever.py +41 -0
- memos/mem_scheduler/modules/schemas.py +146 -0
- memos/mem_scheduler/scheduler_factory.py +21 -0
- memos/mem_scheduler/utils.py +26 -0
- memos/mem_user/user_manager.py +478 -0
- memos/memories/__init__.py +0 -0
- memos/memories/activation/__init__.py +0 -0
- memos/memories/activation/base.py +42 -0
- memos/memories/activation/item.py +25 -0
- memos/memories/activation/kv.py +232 -0
- memos/memories/base.py +19 -0
- memos/memories/factory.py +34 -0
- memos/memories/parametric/__init__.py +0 -0
- memos/memories/parametric/base.py +19 -0
- memos/memories/parametric/item.py +11 -0
- memos/memories/parametric/lora.py +41 -0
- memos/memories/textual/__init__.py +0 -0
- memos/memories/textual/base.py +89 -0
- memos/memories/textual/general.py +286 -0
- memos/memories/textual/item.py +167 -0
- memos/memories/textual/naive.py +185 -0
- memos/memories/textual/tree.py +289 -0
- memos/memories/textual/tree_text_memory/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +305 -0
- memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/retrieve/reasoner.py +64 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +158 -0
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +13 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +166 -0
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +68 -0
- memos/memories/textual/tree_text_memory/retrieve/utils.py +48 -0
- memos/parsers/__init__.py +0 -0
- memos/parsers/base.py +15 -0
- memos/parsers/factory.py +19 -0
- memos/parsers/markitdown.py +22 -0
- memos/settings.py +8 -0
- memos/templates/__init__.py +0 -0
- memos/templates/mem_reader_prompts.py +98 -0
- memos/templates/mem_scheduler_prompts.py +65 -0
- memos/types.py +55 -0
- memos/vec_dbs/__init__.py +0 -0
- memos/vec_dbs/base.py +105 -0
- memos/vec_dbs/factory.py +21 -0
- memos/vec_dbs/item.py +43 -0
- memos/vec_dbs/qdrant.py +292 -0
- memoryos-0.0.1.dist-info/METADATA +0 -53
- memoryos-0.0.1.dist-info/RECORD +0 -5
- {memoryos-0.0.1.dist-info → memoryos-0.1.12.dist-info}/LICENSE +0 -0
- {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)
|