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.

Files changed (42) hide show
  1. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/METADATA +2 -1
  2. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/RECORD +42 -33
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +25 -0
  5. memos/api/context/context_thread.py +96 -0
  6. memos/api/context/dependencies.py +0 -11
  7. memos/api/middleware/request_context.py +94 -0
  8. memos/api/product_api.py +5 -1
  9. memos/api/product_models.py +16 -0
  10. memos/api/routers/product_router.py +39 -3
  11. memos/api/start_api.py +3 -0
  12. memos/configs/memory.py +13 -0
  13. memos/configs/reranker.py +18 -0
  14. memos/graph_dbs/base.py +4 -2
  15. memos/graph_dbs/nebular.py +215 -68
  16. memos/graph_dbs/neo4j.py +14 -12
  17. memos/graph_dbs/neo4j_community.py +6 -3
  18. memos/llms/vllm.py +2 -0
  19. memos/log.py +120 -8
  20. memos/mem_os/core.py +30 -2
  21. memos/mem_os/product.py +386 -146
  22. memos/mem_os/utils/reference_utils.py +20 -0
  23. memos/mem_reader/simple_struct.py +112 -43
  24. memos/mem_user/mysql_user_manager.py +4 -2
  25. memos/memories/textual/item.py +1 -1
  26. memos/memories/textual/tree.py +31 -1
  27. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +3 -1
  28. memos/memories/textual/tree_text_memory/retrieve/recall.py +53 -3
  29. memos/memories/textual/tree_text_memory/retrieve/searcher.py +74 -14
  30. memos/memories/textual/tree_text_memory/retrieve/utils.py +6 -4
  31. memos/memos_tools/notification_utils.py +46 -0
  32. memos/reranker/__init__.py +4 -0
  33. memos/reranker/base.py +24 -0
  34. memos/reranker/cosine_local.py +95 -0
  35. memos/reranker/factory.py +43 -0
  36. memos/reranker/http_bge.py +99 -0
  37. memos/reranker/noop.py +16 -0
  38. memos/templates/mem_reader_prompts.py +289 -40
  39. memos/templates/mos_prompts.py +133 -60
  40. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/LICENSE +0 -0
  41. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/WHEEL +0 -0
  42. {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": "no_datetime",
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": "standard",
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": logging.DEBUG if settings.DEBUG else logging.INFO,
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=self.user_id, session_id=self.session_id, source="conversation"
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
  {