MemoryOS 0.1.13__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/METADATA +78 -49
- memoryos-0.2.1.dist-info/RECORD +152 -0
- memoryos-0.2.1.dist-info/entry_points.txt +3 -0
- memos/__init__.py +1 -1
- memos/api/config.py +471 -0
- memos/api/exceptions.py +28 -0
- memos/api/mcp_serve.py +502 -0
- memos/api/product_api.py +35 -0
- memos/api/product_models.py +159 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +358 -0
- memos/chunkers/sentence_chunker.py +8 -2
- memos/cli.py +113 -0
- memos/configs/embedder.py +27 -0
- memos/configs/graph_db.py +83 -2
- memos/configs/llm.py +48 -0
- memos/configs/mem_cube.py +1 -1
- memos/configs/mem_reader.py +4 -0
- memos/configs/mem_scheduler.py +91 -5
- memos/configs/memory.py +10 -4
- memos/dependency.py +52 -0
- memos/embedders/ark.py +92 -0
- memos/embedders/factory.py +4 -0
- memos/embedders/sentence_transformer.py +8 -2
- memos/embedders/universal_api.py +32 -0
- memos/graph_dbs/base.py +2 -2
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/neo4j.py +377 -101
- memos/graph_dbs/neo4j_community.py +300 -0
- memos/llms/base.py +9 -0
- memos/llms/deepseek.py +54 -0
- memos/llms/factory.py +10 -1
- memos/llms/hf.py +170 -13
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +4 -0
- memos/llms/openai.py +68 -1
- memos/llms/qwen.py +63 -0
- memos/llms/vllm.py +153 -0
- memos/mem_cube/general.py +77 -16
- memos/mem_cube/utils.py +102 -0
- memos/mem_os/core.py +131 -41
- memos/mem_os/main.py +93 -11
- memos/mem_os/product.py +1098 -35
- memos/mem_os/utils/default_config.py +352 -0
- memos/mem_os/utils/format_utils.py +1154 -0
- memos/mem_reader/simple_struct.py +13 -8
- memos/mem_scheduler/base_scheduler.py +467 -36
- memos/mem_scheduler/general_scheduler.py +125 -244
- memos/mem_scheduler/modules/base.py +9 -0
- memos/mem_scheduler/modules/dispatcher.py +68 -2
- memos/mem_scheduler/modules/misc.py +39 -0
- memos/mem_scheduler/modules/monitor.py +228 -49
- memos/mem_scheduler/modules/rabbitmq_service.py +317 -0
- memos/mem_scheduler/modules/redis_service.py +32 -22
- memos/mem_scheduler/modules/retriever.py +250 -23
- memos/mem_scheduler/modules/schemas.py +189 -7
- memos/mem_scheduler/mos_for_test_scheduler.py +143 -0
- memos/mem_scheduler/utils.py +51 -2
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/memories/activation/item.py +25 -0
- memos/memories/activation/kv.py +10 -3
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/factory.py +2 -0
- memos/memories/textual/general.py +7 -5
- memos/memories/textual/item.py +3 -1
- memos/memories/textual/tree.py +14 -6
- memos/memories/textual/tree_text_memory/organize/conflict.py +198 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +72 -23
- memos/memories/textual/tree_text_memory/organize/redundancy.py +193 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +233 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +606 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +6 -5
- memos/parsers/markitdown.py +8 -2
- memos/templates/mem_reader_prompts.py +105 -36
- memos/templates/mem_scheduler_prompts.py +96 -47
- memos/templates/tree_reorganize_prompts.py +223 -0
- memos/vec_dbs/base.py +12 -0
- memos/vec_dbs/qdrant.py +46 -20
- memoryos-0.1.13.dist-info/RECORD +0 -122
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/LICENSE +0 -0
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Persistent user management system for MemOS with configuration storage.
|
|
2
|
+
|
|
3
|
+
This module extends the base UserManager to provide persistent storage
|
|
4
|
+
for user configurations and MOS instances.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from sqlalchemy import Column, String, Text
|
|
13
|
+
|
|
14
|
+
from memos.configs.mem_os import MOSConfig
|
|
15
|
+
from memos.log import get_logger
|
|
16
|
+
from memos.mem_user.user_manager import Base, UserManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UserConfig(Base):
|
|
23
|
+
"""User configuration model for the database."""
|
|
24
|
+
|
|
25
|
+
__tablename__ = "user_configs"
|
|
26
|
+
|
|
27
|
+
user_id = Column(String, primary_key=True)
|
|
28
|
+
config_data = Column(Text, nullable=False) # JSON string of MOSConfig
|
|
29
|
+
created_at = Column(String, nullable=False) # ISO format timestamp
|
|
30
|
+
updated_at = Column(String, nullable=False) # ISO format timestamp
|
|
31
|
+
|
|
32
|
+
def __repr__(self):
|
|
33
|
+
return f"<UserConfig(user_id='{self.user_id}')>"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PersistentUserManager(UserManager):
|
|
37
|
+
"""Extended UserManager with configuration persistence."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, db_path: str | None = None, user_id: str = "root"):
|
|
40
|
+
"""Initialize the persistent user manager.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
db_path (str, optional): Path to the SQLite database file.
|
|
44
|
+
If None, uses default path in MEMOS_DIR.
|
|
45
|
+
user_id (str, optional): User ID. If None, uses default user ID.
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(db_path, user_id)
|
|
48
|
+
|
|
49
|
+
# Create user_configs table
|
|
50
|
+
Base.metadata.create_all(bind=self.engine)
|
|
51
|
+
logger.info("PersistentUserManager initialized with configuration storage")
|
|
52
|
+
|
|
53
|
+
def _convert_datetime_strings(self, obj: Any) -> Any:
|
|
54
|
+
"""Recursively convert datetime strings back to datetime objects in config dict.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
obj: The object to process (dict, list, or primitive type)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The object with datetime strings converted to datetime objects
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(obj, dict):
|
|
63
|
+
result = {}
|
|
64
|
+
for key, value in obj.items():
|
|
65
|
+
if key == "created_at" and isinstance(value, str):
|
|
66
|
+
try:
|
|
67
|
+
result[key] = datetime.fromisoformat(value)
|
|
68
|
+
except ValueError:
|
|
69
|
+
# If parsing fails, keep the original string
|
|
70
|
+
result[key] = value
|
|
71
|
+
else:
|
|
72
|
+
result[key] = self._convert_datetime_strings(value)
|
|
73
|
+
return result
|
|
74
|
+
elif isinstance(obj, list):
|
|
75
|
+
return [self._convert_datetime_strings(item) for item in obj]
|
|
76
|
+
else:
|
|
77
|
+
return obj
|
|
78
|
+
|
|
79
|
+
def save_user_config(self, user_id: str, config: MOSConfig) -> bool:
|
|
80
|
+
"""Save user configuration to database.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
user_id (str): The user ID.
|
|
84
|
+
config (MOSConfig): The user's MOS configuration.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
bool: True if successful, False otherwise.
|
|
88
|
+
"""
|
|
89
|
+
session = self._get_session()
|
|
90
|
+
try:
|
|
91
|
+
# Convert config to JSON string with proper datetime handling
|
|
92
|
+
config_dict = config.model_dump(mode="json")
|
|
93
|
+
config_json = json.dumps(config_dict, indent=2)
|
|
94
|
+
|
|
95
|
+
from datetime import datetime
|
|
96
|
+
|
|
97
|
+
now = datetime.now().isoformat()
|
|
98
|
+
|
|
99
|
+
# Check if config already exists
|
|
100
|
+
existing_config = (
|
|
101
|
+
session.query(UserConfig).filter(UserConfig.user_id == user_id).first()
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if existing_config:
|
|
105
|
+
# Update existing config
|
|
106
|
+
existing_config.config_data = config_json
|
|
107
|
+
existing_config.updated_at = now
|
|
108
|
+
logger.info(f"Updated configuration for user {user_id}")
|
|
109
|
+
else:
|
|
110
|
+
# Create new config
|
|
111
|
+
user_config = UserConfig(
|
|
112
|
+
user_id=user_id, config_data=config_json, created_at=now, updated_at=now
|
|
113
|
+
)
|
|
114
|
+
session.add(user_config)
|
|
115
|
+
logger.info(f"Saved new configuration for user {user_id}")
|
|
116
|
+
|
|
117
|
+
session.commit()
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
session.rollback()
|
|
122
|
+
logger.error(f"Error saving user config for {user_id}: {e}")
|
|
123
|
+
return False
|
|
124
|
+
finally:
|
|
125
|
+
session.close()
|
|
126
|
+
|
|
127
|
+
def get_user_config(self, user_id: str) -> MOSConfig | None:
|
|
128
|
+
"""Get user configuration from database.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
user_id (str): The user ID.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
MOSConfig | None: The user's configuration or None if not found.
|
|
135
|
+
"""
|
|
136
|
+
session = self._get_session()
|
|
137
|
+
try:
|
|
138
|
+
user_config = session.query(UserConfig).filter(UserConfig.user_id == user_id).first()
|
|
139
|
+
|
|
140
|
+
if user_config:
|
|
141
|
+
config_dict = json.loads(user_config.config_data)
|
|
142
|
+
# Convert datetime strings back to datetime objects
|
|
143
|
+
config_dict = self._convert_datetime_strings(config_dict)
|
|
144
|
+
return MOSConfig(**config_dict)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Error loading user config for {user_id}: {e}")
|
|
149
|
+
return None
|
|
150
|
+
finally:
|
|
151
|
+
session.close()
|
|
152
|
+
|
|
153
|
+
def delete_user_config(self, user_id: str) -> bool:
|
|
154
|
+
"""Delete user configuration from database.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
user_id (str): The user ID.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
bool: True if successful, False otherwise.
|
|
161
|
+
"""
|
|
162
|
+
session = self._get_session()
|
|
163
|
+
try:
|
|
164
|
+
user_config = session.query(UserConfig).filter(UserConfig.user_id == user_id).first()
|
|
165
|
+
|
|
166
|
+
if user_config:
|
|
167
|
+
session.delete(user_config)
|
|
168
|
+
session.commit()
|
|
169
|
+
logger.info(f"Deleted configuration for user {user_id}")
|
|
170
|
+
return True
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
session.rollback()
|
|
175
|
+
logger.error(f"Error deleting user config for {user_id}: {e}")
|
|
176
|
+
return False
|
|
177
|
+
finally:
|
|
178
|
+
session.close()
|
|
179
|
+
|
|
180
|
+
def list_user_configs(self) -> dict[str, MOSConfig]:
|
|
181
|
+
"""List all user configurations.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Dict[str, MOSConfig]: Dictionary mapping user_id to MOSConfig.
|
|
185
|
+
"""
|
|
186
|
+
session = self._get_session()
|
|
187
|
+
try:
|
|
188
|
+
user_configs = session.query(UserConfig).all()
|
|
189
|
+
result = {}
|
|
190
|
+
|
|
191
|
+
for user_config in user_configs:
|
|
192
|
+
try:
|
|
193
|
+
config_dict = json.loads(user_config.config_data)
|
|
194
|
+
# Convert datetime strings back to datetime objects
|
|
195
|
+
config_dict = self._convert_datetime_strings(config_dict)
|
|
196
|
+
result[user_config.user_id] = MOSConfig(**config_dict)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Error parsing config for user {user_config.user_id}: {e}")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Error listing user configs: {e}")
|
|
205
|
+
return {}
|
|
206
|
+
finally:
|
|
207
|
+
session.close()
|
|
208
|
+
|
|
209
|
+
def create_user_with_config(
|
|
210
|
+
self, user_name: str, config: MOSConfig, role=None, user_id: str | None = None
|
|
211
|
+
) -> str:
|
|
212
|
+
"""Create a new user with configuration.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
user_name (str): Name of the user.
|
|
216
|
+
config (MOSConfig): The user's configuration.
|
|
217
|
+
role: User role (optional, uses default from UserManager).
|
|
218
|
+
user_id (str, optional): Custom user ID.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
str: The created user ID.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
ValueError: If user_name already exists.
|
|
225
|
+
"""
|
|
226
|
+
# Create user using parent method
|
|
227
|
+
created_user_id = self.create_user(user_name, role, user_id)
|
|
228
|
+
|
|
229
|
+
# Save configuration
|
|
230
|
+
if not self.save_user_config(created_user_id, config):
|
|
231
|
+
logger.error(f"Failed to save configuration for user {created_user_id}")
|
|
232
|
+
|
|
233
|
+
return created_user_id
|
|
234
|
+
|
|
235
|
+
def delete_user(self, user_id: str) -> bool:
|
|
236
|
+
"""Delete a user and their configuration.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
user_id (str): The user ID.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
bool: True if successful, False otherwise.
|
|
243
|
+
"""
|
|
244
|
+
# Delete configuration first
|
|
245
|
+
self.delete_user_config(user_id)
|
|
246
|
+
|
|
247
|
+
# Delete user using parent method
|
|
248
|
+
return super().delete_user(user_id)
|
|
249
|
+
|
|
250
|
+
def get_user_cube_access(self, user_id: str) -> list[str]:
|
|
251
|
+
"""Get list of cube IDs that a user has access to.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
user_id (str): The user ID.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
list[str]: List of cube IDs the user can access.
|
|
258
|
+
"""
|
|
259
|
+
cubes = self.get_user_cubes(user_id)
|
|
260
|
+
return [cube.cube_id for cube in cubes]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from pydantic import BaseModel, ConfigDict, Field
|
|
@@ -12,6 +13,16 @@ class ActivationMemoryItem(BaseModel):
|
|
|
12
13
|
metadata: dict = {}
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
class KVCacheRecords(BaseModel):
|
|
17
|
+
text_memories: list[str] = Field(
|
|
18
|
+
default=[],
|
|
19
|
+
description="The list of text memories transformed to the activation memory.",
|
|
20
|
+
)
|
|
21
|
+
timestamp: datetime = Field(
|
|
22
|
+
default_factory=datetime.now, description="submit time for schedule_messages"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
15
26
|
class KVCacheItem(ActivationMemoryItem):
|
|
16
27
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
17
28
|
memory: DynamicCache = Field(
|
|
@@ -23,3 +34,17 @@ class KVCacheItem(ActivationMemoryItem):
|
|
|
23
34
|
)
|
|
24
35
|
|
|
25
36
|
model_config = ConfigDict(arbitrary_types_allowed=True) # To allow DynamicCache as a field type
|
|
37
|
+
records: KVCacheRecords = KVCacheRecords()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class VLLMKVCacheItem(KVCacheItem):
|
|
41
|
+
"""
|
|
42
|
+
VLLM KV Cache Item that stores prompt strings instead of DynamicCache objects.
|
|
43
|
+
This is because vLLM handles KV cache on the server side via preloading.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Override memory field to store prompt string instead of DynamicCache
|
|
47
|
+
memory: str = Field(
|
|
48
|
+
default="",
|
|
49
|
+
description="Prompt string used to preload KV cache in vLLM server",
|
|
50
|
+
)
|
memos/memories/activation/kv.py
CHANGED
|
@@ -3,11 +3,10 @@ import pickle
|
|
|
3
3
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
|
-
import torch
|
|
7
|
-
|
|
8
6
|
from transformers import DynamicCache
|
|
9
7
|
|
|
10
8
|
from memos.configs.memory import KVCacheMemoryConfig
|
|
9
|
+
from memos.dependency import require_python_package
|
|
11
10
|
from memos.llms.factory import LLMFactory
|
|
12
11
|
from memos.memories.activation.base import BaseActMemory
|
|
13
12
|
from memos.memories.activation.item import KVCacheItem
|
|
@@ -20,6 +19,10 @@ class KVCacheMemory(BaseActMemory):
|
|
|
20
19
|
This memory type is designed to store and retrieve key-value caches.
|
|
21
20
|
"""
|
|
22
21
|
|
|
22
|
+
@require_python_package(
|
|
23
|
+
import_name="torch",
|
|
24
|
+
install_link="https://pytorch.org/get-started/locally/",
|
|
25
|
+
)
|
|
23
26
|
def __init__(self, config: KVCacheMemoryConfig) -> None:
|
|
24
27
|
"""Initialize the KV Cache Memory with a configuration."""
|
|
25
28
|
self.config = config
|
|
@@ -139,6 +142,8 @@ class KVCacheMemory(BaseActMemory):
|
|
|
139
142
|
Args:
|
|
140
143
|
dir (str): The directory containing the memory files.
|
|
141
144
|
"""
|
|
145
|
+
import torch
|
|
146
|
+
|
|
142
147
|
file_path = os.path.join(dir, self.config.memory_filename)
|
|
143
148
|
|
|
144
149
|
if not os.path.exists(file_path):
|
|
@@ -197,6 +202,8 @@ class KVCacheMemory(BaseActMemory):
|
|
|
197
202
|
Faster concat merge: for each layer, gather all caches' tensors
|
|
198
203
|
and do a single torch.cat per layer.
|
|
199
204
|
"""
|
|
205
|
+
import torch
|
|
206
|
+
|
|
200
207
|
assert caches, "Need at least one cache"
|
|
201
208
|
if len(caches) == 1:
|
|
202
209
|
return caches[0]
|
|
@@ -215,7 +222,7 @@ class KVCacheMemory(BaseActMemory):
|
|
|
215
222
|
return merged
|
|
216
223
|
|
|
217
224
|
|
|
218
|
-
def move_dynamic_cache_htod(dynamic_cache: DynamicCache, device:
|
|
225
|
+
def move_dynamic_cache_htod(dynamic_cache: DynamicCache, device: str) -> DynamicCache:
|
|
219
226
|
"""
|
|
220
227
|
In SimpleMemChat.run(), if self.config.enable_activation_memory is enabled,
|
|
221
228
|
we load serialized kv cache from a [class KVCacheMemory] object, which has a kv_cache_memories on CPU.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pickle
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from memos.configs.memory import KVCacheMemoryConfig
|
|
7
|
+
from memos.dependency import require_python_package
|
|
8
|
+
from memos.llms.factory import LLMFactory
|
|
9
|
+
from memos.memories.activation.base import BaseActMemory
|
|
10
|
+
from memos.memories.activation.item import VLLMKVCacheItem
|
|
11
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VLLMKVCacheMemory(BaseActMemory):
|
|
15
|
+
"""
|
|
16
|
+
VLLM Key-Value Cache Memory for activation memories.
|
|
17
|
+
This memory type is designed to store and retrieve prompt strings for vLLM KV cache preloading.
|
|
18
|
+
Unlike traditional KV cache that stores DynamicCache objects, vLLM handles cache on server side.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@require_python_package(
|
|
22
|
+
import_name="torch",
|
|
23
|
+
install_link="https://pytorch.org/get-started/locally/",
|
|
24
|
+
)
|
|
25
|
+
def __init__(self, config: KVCacheMemoryConfig) -> None:
|
|
26
|
+
"""Initialize the VLLM KV Cache Memory with a configuration."""
|
|
27
|
+
self.config = config
|
|
28
|
+
self.llm = LLMFactory.from_config(config.extractor_llm)
|
|
29
|
+
self.kv_cache_memories: dict[str, VLLMKVCacheItem] = {}
|
|
30
|
+
|
|
31
|
+
def extract(self, text: str) -> VLLMKVCacheItem:
|
|
32
|
+
"""Extract memory based on the text.
|
|
33
|
+
|
|
34
|
+
Uses the LLM to build vLLM KV cache from the provided text.
|
|
35
|
+
For vLLM, this means preloading the KV cache on the server side.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
text: Input text to extract memory from
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Extracted VLLM KV cache item with prompt string
|
|
42
|
+
"""
|
|
43
|
+
# Build vLLM KV cache from the text using the LLM
|
|
44
|
+
# This preloads the cache on the vLLM server and returns the prompt
|
|
45
|
+
prompt = self.llm.build_vllm_kv_cache(text)
|
|
46
|
+
|
|
47
|
+
# Create a VLLMKVCacheItem with the extracted prompt
|
|
48
|
+
cache_item = VLLMKVCacheItem(
|
|
49
|
+
memory=prompt,
|
|
50
|
+
metadata={"source_text": text, "extracted_at": datetime.now().isoformat()},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return cache_item
|
|
54
|
+
|
|
55
|
+
def add(self, memories: list[VLLMKVCacheItem]) -> None:
|
|
56
|
+
"""Add memories to the VLLM KV cache memory.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
memories: List of VLLMKVCacheItem to add
|
|
60
|
+
"""
|
|
61
|
+
for memory in memories:
|
|
62
|
+
self.kv_cache_memories[memory.id] = memory
|
|
63
|
+
|
|
64
|
+
def get_cache(self, cache_ids: list[str]) -> str | None:
|
|
65
|
+
"""Get the prompt string for the most recent cache.
|
|
66
|
+
|
|
67
|
+
Since vLLM handles KV cache on server side, we return the prompt string
|
|
68
|
+
that can be used for generation. For multiple caches, we return the most recent one.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
cache_ids: List of cache IDs to consider
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Prompt string for the most recent cache or None if no caches found
|
|
75
|
+
"""
|
|
76
|
+
if not cache_ids:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# For vLLM, we typically want the most recent cache
|
|
80
|
+
# Return the prompt from the last cache ID in the list
|
|
81
|
+
latest_cache_id = cache_ids[-1]
|
|
82
|
+
cache_item = self.kv_cache_memories.get(latest_cache_id)
|
|
83
|
+
|
|
84
|
+
if cache_item and cache_item.memory:
|
|
85
|
+
return cache_item.memory
|
|
86
|
+
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def get(self, memory_id: str) -> VLLMKVCacheItem | None:
|
|
90
|
+
"""Get a memory by its ID.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
memory_id: ID of the memory to retrieve
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
VLLMKVCacheItem or None if not found
|
|
97
|
+
"""
|
|
98
|
+
return self.kv_cache_memories.get(memory_id)
|
|
99
|
+
|
|
100
|
+
def get_by_ids(self, memory_ids: list[str]) -> list[VLLMKVCacheItem | None]:
|
|
101
|
+
"""Get memories by their IDs.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
memory_ids: List of memory IDs to retrieve
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of VLLMKVCacheItem or None for missing ones
|
|
108
|
+
"""
|
|
109
|
+
results = []
|
|
110
|
+
for memory_id in memory_ids:
|
|
111
|
+
memory = self.get(memory_id)
|
|
112
|
+
results.append(memory)
|
|
113
|
+
return results
|
|
114
|
+
|
|
115
|
+
def get_all(self) -> list[VLLMKVCacheItem]:
|
|
116
|
+
"""Get all memories.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of all VLLMKVCacheItems in the memory
|
|
120
|
+
"""
|
|
121
|
+
return list(self.kv_cache_memories.values())
|
|
122
|
+
|
|
123
|
+
def delete(self, memory_ids: list[str]) -> None:
|
|
124
|
+
"""Delete memories by their IDs.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
memory_ids: List of memory IDs to delete
|
|
128
|
+
"""
|
|
129
|
+
for memory_id in memory_ids:
|
|
130
|
+
self.kv_cache_memories.pop(memory_id, None)
|
|
131
|
+
|
|
132
|
+
def delete_all(self) -> None:
|
|
133
|
+
"""Delete all memories."""
|
|
134
|
+
self.kv_cache_memories.clear()
|
|
135
|
+
|
|
136
|
+
def from_textual_memory(self, mem: TextualMemoryItem) -> VLLMKVCacheItem:
|
|
137
|
+
"""
|
|
138
|
+
Convert a TextualMemoryItem to a VLLMKVCacheItem.
|
|
139
|
+
This method extracts the prompt string from the textual memory.
|
|
140
|
+
"""
|
|
141
|
+
# Build vLLM KV cache from the textual memory content
|
|
142
|
+
prompt = self.llm.build_vllm_kv_cache(mem.memory)
|
|
143
|
+
return VLLMKVCacheItem(memory=prompt, metadata=mem.metadata.model_dump())
|
|
144
|
+
|
|
145
|
+
def load(self, dir: str) -> None:
|
|
146
|
+
"""Load memories from os.path.join(dir, self.config.memory_filename)
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
dir (str): The directory containing the memory files.
|
|
150
|
+
"""
|
|
151
|
+
file_path = os.path.join(dir, self.config.memory_filename)
|
|
152
|
+
|
|
153
|
+
if not os.path.exists(file_path):
|
|
154
|
+
# If file doesn't exist, start with empty memories
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
# Allow loading VLLMKVCacheItem types
|
|
159
|
+
import torch
|
|
160
|
+
|
|
161
|
+
torch.serialization.add_safe_globals([VLLMKVCacheItem])
|
|
162
|
+
|
|
163
|
+
with open(file_path, "rb") as f:
|
|
164
|
+
data = pickle.load(f)
|
|
165
|
+
|
|
166
|
+
if isinstance(data, dict):
|
|
167
|
+
# Load memories, handle both old and new formats
|
|
168
|
+
if "kv_cache_memories" in data:
|
|
169
|
+
memories = data["kv_cache_memories"]
|
|
170
|
+
if isinstance(memories, list):
|
|
171
|
+
# Convert list to dict format
|
|
172
|
+
self.kv_cache_memories = {item.id: item for item in memories}
|
|
173
|
+
else:
|
|
174
|
+
self.kv_cache_memories = memories
|
|
175
|
+
else:
|
|
176
|
+
# Reset to empty if no memories in data
|
|
177
|
+
self.kv_cache_memories = {}
|
|
178
|
+
elif isinstance(data, list):
|
|
179
|
+
# Backward compatibility: convert list to dict
|
|
180
|
+
self.kv_cache_memories = {item.id: item for item in data}
|
|
181
|
+
else:
|
|
182
|
+
# Reset to empty if data format is unexpected
|
|
183
|
+
self.kv_cache_memories = {}
|
|
184
|
+
|
|
185
|
+
except (EOFError, pickle.UnpicklingError, Exception):
|
|
186
|
+
# If loading fails, start with empty memories
|
|
187
|
+
self.kv_cache_memories = {}
|
|
188
|
+
|
|
189
|
+
def dump(self, dir: str) -> None:
|
|
190
|
+
"""Dump memories to os.path.join(dir, self.config.memory_filename)
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
dir (str): The directory where the memory files will be saved.
|
|
194
|
+
"""
|
|
195
|
+
file_path = os.path.join(dir, self.config.memory_filename)
|
|
196
|
+
|
|
197
|
+
# Create directory if it doesn't exist
|
|
198
|
+
os.makedirs(dir, exist_ok=True)
|
|
199
|
+
|
|
200
|
+
# Prepare data to save (only memories)
|
|
201
|
+
data = {"kv_cache_memories": self.kv_cache_memories}
|
|
202
|
+
|
|
203
|
+
with open(file_path, "wb") as f:
|
|
204
|
+
pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)
|
|
205
|
+
|
|
206
|
+
def preload_kv_cache(self, cache_ids: list[str]) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Preload KV cache on vLLM server for the given cache IDs.
|
|
209
|
+
This method calls build_vllm_kv_cache for each cache to ensure
|
|
210
|
+
the KV cache is loaded on the server side.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
cache_ids: List of cache IDs to preload
|
|
214
|
+
"""
|
|
215
|
+
for cache_id in cache_ids:
|
|
216
|
+
cache_item = self.kv_cache_memories.get(cache_id)
|
|
217
|
+
if cache_item and cache_item.memory:
|
|
218
|
+
# Re-preload the KV cache on the server
|
|
219
|
+
self.llm.build_vllm_kv_cache(cache_item.memory)
|
memos/memories/factory.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Any, ClassVar
|
|
|
3
3
|
from memos.configs.memory import MemoryConfigFactory
|
|
4
4
|
from memos.memories.activation.base import BaseActMemory
|
|
5
5
|
from memos.memories.activation.kv import KVCacheMemory
|
|
6
|
+
from memos.memories.activation.vllmkv import VLLMKVCacheMemory
|
|
6
7
|
from memos.memories.base import BaseMemory
|
|
7
8
|
from memos.memories.parametric.base import BaseParaMemory
|
|
8
9
|
from memos.memories.parametric.lora import LoRAMemory
|
|
@@ -20,6 +21,7 @@ class MemoryFactory(BaseMemory):
|
|
|
20
21
|
"general_text": GeneralTextMemory,
|
|
21
22
|
"tree_text": TreeTextMemory,
|
|
22
23
|
"kv_cache": KVCacheMemory,
|
|
24
|
+
"vllm_kv_cache": VLLMKVCacheMemory,
|
|
23
25
|
"lora": LoRAMemory,
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -7,8 +7,8 @@ from typing import Any
|
|
|
7
7
|
from tenacity import retry, retry_if_exception_type, stop_after_attempt
|
|
8
8
|
|
|
9
9
|
from memos.configs.memory import GeneralTextMemoryConfig
|
|
10
|
-
from memos.embedders.factory import EmbedderFactory, OllamaEmbedder
|
|
11
|
-
from memos.llms.factory import LLMFactory, OllamaLLM, OpenAILLM
|
|
10
|
+
from memos.embedders.factory import ArkEmbedder, EmbedderFactory, OllamaEmbedder
|
|
11
|
+
from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM
|
|
12
12
|
from memos.log import get_logger
|
|
13
13
|
from memos.memories.textual.base import BaseTextMemory
|
|
14
14
|
from memos.memories.textual.item import TextualMemoryItem
|
|
@@ -26,9 +26,11 @@ class GeneralTextMemory(BaseTextMemory):
|
|
|
26
26
|
def __init__(self, config: GeneralTextMemoryConfig):
|
|
27
27
|
"""Initialize memory with the given configuration."""
|
|
28
28
|
self.config: GeneralTextMemoryConfig = config
|
|
29
|
-
self.extractor_llm: OpenAILLM | OllamaLLM = LLMFactory.from_config(
|
|
29
|
+
self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
|
|
30
|
+
config.extractor_llm
|
|
31
|
+
)
|
|
30
32
|
self.vector_db: QdrantVecDB = VecDBFactory.from_config(config.vector_db)
|
|
31
|
-
self.embedder: OllamaEmbedder = EmbedderFactory.from_config(config.embedder)
|
|
33
|
+
self.embedder: OllamaEmbedder | ArkEmbedder = EmbedderFactory.from_config(config.embedder)
|
|
32
34
|
|
|
33
35
|
@retry(
|
|
34
36
|
stop=stop_after_attempt(3),
|
|
@@ -202,7 +204,7 @@ class GeneralTextMemory(BaseTextMemory):
|
|
|
202
204
|
|
|
203
205
|
def _embed_one_sentence(self, sentence: str) -> list[float]:
|
|
204
206
|
"""Embed a single sentence."""
|
|
205
|
-
return self.embedder.embed(sentence)[0]
|
|
207
|
+
return self.embedder.embed([sentence])[0]
|
|
206
208
|
|
|
207
209
|
|
|
208
210
|
EXTRACTION_PROMPT_PART_1 = f"""You are a memory extractor. Your task is to extract memories from the given messages.
|
memos/memories/textual/item.py
CHANGED
|
@@ -27,7 +27,9 @@ class TextualMemoryMetadata(BaseModel):
|
|
|
27
27
|
default="activated",
|
|
28
28
|
description="The status of the memory, e.g., 'activated', 'archived', 'deleted'.",
|
|
29
29
|
)
|
|
30
|
-
type: Literal["procedure", "fact", "event", "opinion", "topic"] | None = Field(
|
|
30
|
+
type: Literal["procedure", "fact", "event", "opinion", "topic", "reasoning"] | None = Field(
|
|
31
|
+
default=None
|
|
32
|
+
)
|
|
31
33
|
memory_time: str | None = Field(
|
|
32
34
|
default=None,
|
|
33
35
|
description='The time the memory occurred or refers to. Must be in standard `YYYY-MM-DD` format. Relative expressions such as "yesterday" or "tomorrow" are not allowed.',
|
memos/memories/textual/tree.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
|
10
10
|
from memos.configs.memory import TreeTextMemoryConfig
|
|
11
11
|
from memos.embedders.factory import EmbedderFactory, OllamaEmbedder
|
|
12
12
|
from memos.graph_dbs.factory import GraphStoreFactory, Neo4jGraphDB
|
|
13
|
-
from memos.llms.factory import LLMFactory, OllamaLLM, OpenAILLM
|
|
13
|
+
from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM
|
|
14
14
|
from memos.log import get_logger
|
|
15
15
|
from memos.memories.textual.base import BaseTextMemory
|
|
16
16
|
from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata
|
|
@@ -31,11 +31,19 @@ class TreeTextMemory(BaseTextMemory):
|
|
|
31
31
|
def __init__(self, config: TreeTextMemoryConfig):
|
|
32
32
|
"""Initialize memory with the given configuration."""
|
|
33
33
|
self.config: TreeTextMemoryConfig = config
|
|
34
|
-
self.extractor_llm: OpenAILLM | OllamaLLM = LLMFactory.from_config(
|
|
35
|
-
|
|
34
|
+
self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
|
|
35
|
+
config.extractor_llm
|
|
36
|
+
)
|
|
37
|
+
self.dispatcher_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
|
|
38
|
+
config.dispatcher_llm
|
|
39
|
+
)
|
|
36
40
|
self.embedder: OllamaEmbedder = EmbedderFactory.from_config(config.embedder)
|
|
37
41
|
self.graph_store: Neo4jGraphDB = GraphStoreFactory.from_config(config.graph_db)
|
|
38
|
-
self.
|
|
42
|
+
self.is_reorganize = config.reorganize
|
|
43
|
+
|
|
44
|
+
self.memory_manager: MemoryManager = MemoryManager(
|
|
45
|
+
self.graph_store, self.embedder, self.extractor_llm, is_reorganize=self.is_reorganize
|
|
46
|
+
)
|
|
39
47
|
|
|
40
48
|
# Create internet retriever if configured
|
|
41
49
|
self.internet_retriever = None
|
|
@@ -49,7 +57,7 @@ class TreeTextMemory(BaseTextMemory):
|
|
|
49
57
|
else:
|
|
50
58
|
logger.info("No internet retriever configured")
|
|
51
59
|
|
|
52
|
-
def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) ->
|
|
60
|
+
def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> list[str]:
|
|
53
61
|
"""Add memories.
|
|
54
62
|
Args:
|
|
55
63
|
memories: List of TextualMemoryItem objects or dictionaries to add.
|
|
@@ -59,7 +67,7 @@ class TreeTextMemory(BaseTextMemory):
|
|
|
59
67
|
plan = plan_memory_operations(memory_items, metadata, self.graph_store)
|
|
60
68
|
execute_plan(memory_items, metadata, plan, self.graph_store)
|
|
61
69
|
"""
|
|
62
|
-
self.memory_manager.add(memories)
|
|
70
|
+
return self.memory_manager.add(memories)
|
|
63
71
|
|
|
64
72
|
def replace_working_memory(self, memories: list[TextualMemoryItem]) -> None:
|
|
65
73
|
self.memory_manager.replace_working_memory(memories)
|