MemoryOS 1.0.0__py3-none-any.whl → 1.0.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-1.0.0.dist-info → memoryos-1.0.1.dist-info}/METADATA +2 -1
- {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/RECORD +42 -33
- memos/__init__.py +1 -1
- memos/api/config.py +25 -0
- memos/api/context/context_thread.py +96 -0
- memos/api/context/dependencies.py +0 -11
- memos/api/middleware/request_context.py +94 -0
- memos/api/product_api.py +5 -1
- memos/api/product_models.py +16 -0
- memos/api/routers/product_router.py +39 -3
- memos/api/start_api.py +3 -0
- memos/configs/memory.py +13 -0
- memos/configs/reranker.py +18 -0
- memos/graph_dbs/base.py +4 -2
- memos/graph_dbs/nebular.py +215 -68
- memos/graph_dbs/neo4j.py +14 -12
- memos/graph_dbs/neo4j_community.py +6 -3
- memos/llms/vllm.py +2 -0
- memos/log.py +120 -8
- memos/mem_os/core.py +30 -2
- memos/mem_os/product.py +386 -146
- memos/mem_os/utils/reference_utils.py +20 -0
- memos/mem_reader/simple_struct.py +112 -43
- memos/mem_user/mysql_user_manager.py +4 -2
- memos/memories/textual/item.py +1 -1
- memos/memories/textual/tree.py +31 -1
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +3 -1
- memos/memories/textual/tree_text_memory/retrieve/recall.py +53 -3
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +74 -14
- memos/memories/textual/tree_text_memory/retrieve/utils.py +6 -4
- memos/memos_tools/notification_utils.py +46 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +24 -0
- memos/reranker/cosine_local.py +95 -0
- memos/reranker/factory.py +43 -0
- memos/reranker/http_bge.py +99 -0
- memos/reranker/noop.py +16 -0
- memos/templates/mem_reader_prompts.py +289 -40
- memos/templates/mos_prompts.py +133 -60
- {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/LICENSE +0 -0
- {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/WHEEL +0 -0
- {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/entry_points.txt +0 -0
memos/log.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
2
5
|
|
|
3
6
|
from logging.config import dictConfig
|
|
4
7
|
from pathlib import Path
|
|
5
8
|
from sys import stdout
|
|
6
9
|
|
|
10
|
+
import requests
|
|
11
|
+
|
|
7
12
|
from dotenv import load_dotenv
|
|
8
13
|
|
|
9
14
|
from memos import settings
|
|
15
|
+
from memos.api.context.context import get_current_trace_id
|
|
16
|
+
from memos.api.context.context_thread import ContextThreadPoolExecutor
|
|
10
17
|
|
|
11
18
|
|
|
12
19
|
# Load environment variables
|
|
@@ -26,27 +33,126 @@ def _setup_logfile() -> Path:
|
|
|
26
33
|
return logfile
|
|
27
34
|
|
|
28
35
|
|
|
36
|
+
class TraceIDFilter(logging.Filter):
|
|
37
|
+
"""add trace_id to the log record"""
|
|
38
|
+
|
|
39
|
+
def filter(self, record):
|
|
40
|
+
try:
|
|
41
|
+
trace_id = get_current_trace_id()
|
|
42
|
+
record.trace_id = trace_id if trace_id else "no-trace-id"
|
|
43
|
+
except Exception:
|
|
44
|
+
record.trace_id = "no-trace-id"
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CustomLoggerRequestHandler(logging.Handler):
|
|
49
|
+
_instance = None
|
|
50
|
+
_lock = threading.Lock()
|
|
51
|
+
|
|
52
|
+
def __new__(cls):
|
|
53
|
+
if cls._instance is None:
|
|
54
|
+
with cls._lock:
|
|
55
|
+
if cls._instance is None:
|
|
56
|
+
cls._instance = super().__new__(cls)
|
|
57
|
+
cls._instance._initialized = False
|
|
58
|
+
cls._instance._executor = None
|
|
59
|
+
cls._instance._session = None
|
|
60
|
+
cls._instance._is_shutting_down = None
|
|
61
|
+
return cls._instance
|
|
62
|
+
|
|
63
|
+
def __init__(self):
|
|
64
|
+
"""Initialize handler with minimal setup"""
|
|
65
|
+
if not self._initialized:
|
|
66
|
+
super().__init__()
|
|
67
|
+
workers = int(os.getenv("CUSTOM_LOGGER_WORKERS", "2"))
|
|
68
|
+
self._executor = ContextThreadPoolExecutor(
|
|
69
|
+
max_workers=workers, thread_name_prefix="log_sender"
|
|
70
|
+
)
|
|
71
|
+
self._is_shutting_down = threading.Event()
|
|
72
|
+
self._session = requests.Session()
|
|
73
|
+
self._initialized = True
|
|
74
|
+
atexit.register(self._cleanup)
|
|
75
|
+
|
|
76
|
+
def emit(self, record):
|
|
77
|
+
"""Process log records of INFO or ERROR level (non-blocking)"""
|
|
78
|
+
if os.getenv("CUSTOM_LOGGER_URL") is None or self._is_shutting_down.is_set():
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
trace_id = get_current_trace_id() or "no-trace-id"
|
|
83
|
+
self._executor.submit(self._send_log_sync, record.getMessage(), trace_id)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
if not self._is_shutting_down.is_set():
|
|
86
|
+
print(f"Error sending log: {e}")
|
|
87
|
+
|
|
88
|
+
def _send_log_sync(self, message, trace_id):
|
|
89
|
+
"""Send log message synchronously in a separate thread"""
|
|
90
|
+
try:
|
|
91
|
+
logger_url = os.getenv("CUSTOM_LOGGER_URL")
|
|
92
|
+
token = os.getenv("CUSTOM_LOGGER_TOKEN")
|
|
93
|
+
|
|
94
|
+
headers = {"Content-Type": "application/json"}
|
|
95
|
+
post_content = {"message": message, "trace_id": trace_id}
|
|
96
|
+
|
|
97
|
+
# Add auth token if exists
|
|
98
|
+
if token:
|
|
99
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
100
|
+
|
|
101
|
+
# Add traceId to headers for consistency
|
|
102
|
+
headers["traceId"] = trace_id
|
|
103
|
+
|
|
104
|
+
# Add custom attributes from env
|
|
105
|
+
for key, value in os.environ.items():
|
|
106
|
+
if key.startswith("CUSTOM_LOGGER_ATTRIBUTE_"):
|
|
107
|
+
attribute_key = key[len("CUSTOM_LOGGER_ATTRIBUTE_") :].lower()
|
|
108
|
+
post_content[attribute_key] = value
|
|
109
|
+
|
|
110
|
+
self._session.post(logger_url, headers=headers, json=post_content, timeout=5)
|
|
111
|
+
except Exception:
|
|
112
|
+
# Silently ignore errors to avoid affecting main application
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def _cleanup(self):
|
|
116
|
+
"""Clean up resources during program exit"""
|
|
117
|
+
if not self._initialized:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
self._is_shutting_down.set()
|
|
121
|
+
try:
|
|
122
|
+
self._executor.shutdown(wait=False)
|
|
123
|
+
self._session.close()
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"Error during cleanup: {e}")
|
|
126
|
+
|
|
127
|
+
def close(self):
|
|
128
|
+
"""Override close to prevent premature shutdown"""
|
|
129
|
+
|
|
130
|
+
|
|
29
131
|
LOGGING_CONFIG = {
|
|
30
132
|
"version": 1,
|
|
31
133
|
"disable_existing_loggers": False,
|
|
32
134
|
"formatters": {
|
|
33
135
|
"standard": {
|
|
34
|
-
"format": "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
136
|
+
"format": "%(asctime)s [%(trace_id)s] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
35
137
|
},
|
|
36
138
|
"no_datetime": {
|
|
37
|
-
"format": "%(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
139
|
+
"format": "[%(trace_id)s] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
140
|
+
},
|
|
141
|
+
"simplified": {
|
|
142
|
+
"format": "%(asctime)s | %(trace_id)s | %(levelname)s | %(filename)s | %(message)s"
|
|
38
143
|
},
|
|
39
144
|
},
|
|
40
145
|
"filters": {
|
|
41
|
-
"package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX}
|
|
146
|
+
"package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX},
|
|
147
|
+
"trace_id_filter": {"()": "memos.log.TraceIDFilter"},
|
|
42
148
|
},
|
|
43
149
|
"handlers": {
|
|
44
150
|
"console": {
|
|
45
151
|
"level": selected_log_level,
|
|
46
152
|
"class": "logging.StreamHandler",
|
|
47
153
|
"stream": stdout,
|
|
48
|
-
"formatter": "
|
|
49
|
-
"filters": ["package_tree_filter"],
|
|
154
|
+
"formatter": "simplified",
|
|
155
|
+
"filters": ["package_tree_filter", "trace_id_filter"],
|
|
50
156
|
},
|
|
51
157
|
"file": {
|
|
52
158
|
"level": "DEBUG",
|
|
@@ -54,12 +160,18 @@ LOGGING_CONFIG = {
|
|
|
54
160
|
"filename": _setup_logfile(),
|
|
55
161
|
"maxBytes": 1024**2 * 10,
|
|
56
162
|
"backupCount": 10,
|
|
57
|
-
"formatter": "
|
|
163
|
+
"formatter": "simplified",
|
|
164
|
+
"filters": ["trace_id_filter"],
|
|
165
|
+
},
|
|
166
|
+
"custom_logger": {
|
|
167
|
+
"level": selected_log_level,
|
|
168
|
+
"class": "memos.log.CustomLoggerRequestHandler",
|
|
169
|
+
"formatter": "simplified",
|
|
58
170
|
},
|
|
59
171
|
},
|
|
60
172
|
"root": { # Root logger handles all logs
|
|
61
|
-
"level":
|
|
62
|
-
"handlers": ["console", "file"],
|
|
173
|
+
"level": selected_log_level,
|
|
174
|
+
"handlers": ["console", "file", "custom_logger"],
|
|
63
175
|
},
|
|
64
176
|
"loggers": {
|
|
65
177
|
"memos": {
|
memos/mem_os/core.py
CHANGED
|
@@ -352,6 +352,7 @@ class MOSCore:
|
|
|
352
352
|
self,
|
|
353
353
|
memories: list[TextualMemoryItem] | list[str] | None = None,
|
|
354
354
|
base_prompt: str | None = None,
|
|
355
|
+
**kwargs,
|
|
355
356
|
) -> str:
|
|
356
357
|
"""Build system prompt with optional memories context."""
|
|
357
358
|
if base_prompt is None:
|
|
@@ -545,6 +546,8 @@ class MOSCore:
|
|
|
545
546
|
top_k: int | None = None,
|
|
546
547
|
mode: Literal["fast", "fine"] = "fast",
|
|
547
548
|
internet_search: bool = False,
|
|
549
|
+
moscube: bool = False,
|
|
550
|
+
**kwargs,
|
|
548
551
|
) -> MOSSearchResult:
|
|
549
552
|
"""
|
|
550
553
|
Search for textual memories across all registered MemCubes.
|
|
@@ -602,6 +605,7 @@ class MOSCore:
|
|
|
602
605
|
"session_id": self.session_id,
|
|
603
606
|
"chat_history": chat_history.chat_history,
|
|
604
607
|
},
|
|
608
|
+
moscube=moscube,
|
|
605
609
|
)
|
|
606
610
|
result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
|
|
607
611
|
logger.info(
|
|
@@ -659,7 +663,7 @@ class MOSCore:
|
|
|
659
663
|
if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
|
|
660
664
|
add_memory = []
|
|
661
665
|
metadata = TextualMemoryMetadata(
|
|
662
|
-
user_id=
|
|
666
|
+
user_id=target_user_id, session_id=self.session_id, source="conversation"
|
|
663
667
|
)
|
|
664
668
|
for message in messages:
|
|
665
669
|
add_memory.append(
|
|
@@ -956,6 +960,30 @@ class MOSCore:
|
|
|
956
960
|
self.mem_cubes[mem_cube_id].dump(dump_dir)
|
|
957
961
|
logger.info(f"MemCube {mem_cube_id} dumped to {dump_dir}")
|
|
958
962
|
|
|
963
|
+
def load(
|
|
964
|
+
self,
|
|
965
|
+
load_dir: str,
|
|
966
|
+
user_id: str | None = None,
|
|
967
|
+
mem_cube_id: str | None = None,
|
|
968
|
+
memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None,
|
|
969
|
+
) -> None:
|
|
970
|
+
"""Dump the MemCube to a dictionary.
|
|
971
|
+
Args:
|
|
972
|
+
load_dir (str): The directory to load the MemCube from.
|
|
973
|
+
user_id (str, optional): The identifier of the user to load the MemCube from.
|
|
974
|
+
If None, the default user is used.
|
|
975
|
+
mem_cube_id (str, optional): The identifier of the MemCube to load.
|
|
976
|
+
If None, the default MemCube for the user is used.
|
|
977
|
+
"""
|
|
978
|
+
target_user_id = user_id if user_id is not None else self.user_id
|
|
979
|
+
accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
|
|
980
|
+
if not mem_cube_id:
|
|
981
|
+
mem_cube_id = accessible_cubes[0].cube_id
|
|
982
|
+
if mem_cube_id not in self.mem_cubes:
|
|
983
|
+
raise ValueError(f"MemCube with ID {mem_cube_id} does not exist. please regiester")
|
|
984
|
+
self.mem_cubes[mem_cube_id].load(load_dir, memory_types=memory_types)
|
|
985
|
+
logger.info(f"MemCube {mem_cube_id} loaded from {load_dir}")
|
|
986
|
+
|
|
959
987
|
def get_user_info(self) -> dict[str, Any]:
|
|
960
988
|
"""Get current user information including accessible cubes.
|
|
961
989
|
|
|
@@ -971,7 +999,7 @@ class MOSCore:
|
|
|
971
999
|
return {
|
|
972
1000
|
"user_id": user.user_id,
|
|
973
1001
|
"user_name": user.user_name,
|
|
974
|
-
"role": user.role.value,
|
|
1002
|
+
"role": user.role.value if hasattr(user.role, "value") else user.role,
|
|
975
1003
|
"created_at": user.created_at.isoformat(),
|
|
976
1004
|
"accessible_cubes": [
|
|
977
1005
|
{
|