MemoryOS 1.0.1__py3-none-any.whl → 1.1.2__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 (82) hide show
  1. {memoryos-1.0.1.dist-info → memoryos-1.1.2.dist-info}/METADATA +7 -2
  2. {memoryos-1.0.1.dist-info → memoryos-1.1.2.dist-info}/RECORD +79 -65
  3. {memoryos-1.0.1.dist-info → memoryos-1.1.2.dist-info}/WHEEL +1 -1
  4. memos/__init__.py +1 -1
  5. memos/api/client.py +109 -0
  6. memos/api/config.py +11 -9
  7. memos/api/context/dependencies.py +15 -55
  8. memos/api/middleware/request_context.py +9 -40
  9. memos/api/product_api.py +2 -3
  10. memos/api/product_models.py +91 -16
  11. memos/api/routers/product_router.py +23 -16
  12. memos/api/start_api.py +10 -0
  13. memos/configs/graph_db.py +4 -0
  14. memos/configs/mem_scheduler.py +38 -3
  15. memos/context/context.py +255 -0
  16. memos/embedders/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +230 -232
  18. memos/graph_dbs/neo4j.py +35 -1
  19. memos/graph_dbs/neo4j_community.py +7 -0
  20. memos/llms/factory.py +2 -0
  21. memos/llms/openai.py +74 -2
  22. memos/log.py +27 -15
  23. memos/mem_cube/general.py +3 -1
  24. memos/mem_os/core.py +60 -22
  25. memos/mem_os/main.py +3 -6
  26. memos/mem_os/product.py +35 -11
  27. memos/mem_reader/factory.py +2 -0
  28. memos/mem_reader/simple_struct.py +127 -74
  29. memos/mem_scheduler/analyzer/__init__.py +0 -0
  30. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +569 -0
  31. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  32. memos/mem_scheduler/base_scheduler.py +126 -56
  33. memos/mem_scheduler/general_modules/dispatcher.py +2 -2
  34. memos/mem_scheduler/general_modules/misc.py +99 -1
  35. memos/mem_scheduler/general_modules/scheduler_logger.py +17 -11
  36. memos/mem_scheduler/general_scheduler.py +40 -88
  37. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  38. memos/mem_scheduler/memory_manage_modules/memory_filter.py +308 -0
  39. memos/mem_scheduler/{general_modules → memory_manage_modules}/retriever.py +34 -7
  40. memos/mem_scheduler/monitors/dispatcher_monitor.py +9 -8
  41. memos/mem_scheduler/monitors/general_monitor.py +119 -39
  42. memos/mem_scheduler/optimized_scheduler.py +124 -0
  43. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  44. memos/mem_scheduler/orm_modules/base_model.py +635 -0
  45. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  46. memos/mem_scheduler/scheduler_factory.py +2 -0
  47. memos/mem_scheduler/schemas/monitor_schemas.py +96 -29
  48. memos/mem_scheduler/utils/config_utils.py +100 -0
  49. memos/mem_scheduler/utils/db_utils.py +33 -0
  50. memos/mem_scheduler/utils/filter_utils.py +1 -1
  51. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  52. memos/memories/activation/kv.py +2 -1
  53. memos/memories/textual/item.py +95 -16
  54. memos/memories/textual/naive.py +1 -1
  55. memos/memories/textual/tree.py +27 -3
  56. memos/memories/textual/tree_text_memory/organize/handler.py +4 -2
  57. memos/memories/textual/tree_text_memory/organize/manager.py +28 -14
  58. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +1 -2
  59. memos/memories/textual/tree_text_memory/organize/reorganizer.py +75 -23
  60. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +7 -5
  61. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -2
  62. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  63. memos/memories/textual/tree_text_memory/retrieve/recall.py +70 -22
  64. memos/memories/textual/tree_text_memory/retrieve/searcher.py +101 -33
  65. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +5 -4
  66. memos/memos_tools/singleton.py +174 -0
  67. memos/memos_tools/thread_safe_dict.py +22 -0
  68. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  69. memos/parsers/factory.py +2 -0
  70. memos/reranker/concat.py +59 -0
  71. memos/reranker/cosine_local.py +1 -0
  72. memos/reranker/factory.py +5 -0
  73. memos/reranker/http_bge.py +225 -12
  74. memos/templates/mem_scheduler_prompts.py +242 -0
  75. memos/types.py +4 -1
  76. memos/api/context/context.py +0 -147
  77. memos/api/context/context_thread.py +0 -96
  78. memos/mem_scheduler/mos_for_test_scheduler.py +0 -146
  79. {memoryos-1.0.1.dist-info → memoryos-1.1.2.dist-info}/entry_points.txt +0 -0
  80. {memoryos-1.0.1.dist-info → memoryos-1.1.2.dist-info/licenses}/LICENSE +0 -0
  81. /memos/mem_scheduler/{general_modules → webservice_modules}/rabbitmq_service.py +0 -0
  82. /memos/mem_scheduler/{general_modules → webservice_modules}/redis_service.py +0 -0
memos/types.py CHANGED
@@ -22,11 +22,14 @@ MessageRole: TypeAlias = Literal["user", "assistant", "system"]
22
22
 
23
23
 
24
24
  # Message structure
25
- class MessageDict(TypedDict):
25
+ class MessageDict(TypedDict, total=False):
26
26
  """Typed dictionary for chat message dictionaries."""
27
27
 
28
28
  role: MessageRole
29
29
  content: str
30
+ chat_time: str | None # Optional timestamp for the message, format is not
31
+ # restricted, it can be any vague or precise time string.
32
+ message_id: str | None # Optional unique identifier for the message
30
33
 
31
34
 
32
35
  # Message collections
@@ -1,147 +0,0 @@
1
- """
2
- Global request context management for trace_id and request-scoped data.
3
-
4
- This module provides optional trace_id functionality that can be enabled
5
- when using the API components. It uses ContextVar to ensure thread safety
6
- and request isolation.
7
- """
8
-
9
- import uuid
10
-
11
- from collections.abc import Callable
12
- from contextvars import ContextVar
13
- from typing import Any
14
-
15
-
16
- # Global context variable for request-scoped data
17
- _request_context: ContextVar[dict[str, Any] | None] = ContextVar("request_context", default=None)
18
-
19
-
20
- class RequestContext:
21
- """
22
- Request-scoped context object that holds trace_id and other request data.
23
-
24
- This provides a Flask g-like object for FastAPI applications.
25
- """
26
-
27
- def __init__(self, trace_id: str | None = None):
28
- self.trace_id = trace_id or str(uuid.uuid4())
29
- self._data: dict[str, Any] = {}
30
-
31
- def set(self, key: str, value: Any) -> None:
32
- """Set a value in the context."""
33
- self._data[key] = value
34
-
35
- def get(self, key: str, default: Any | None = None) -> Any:
36
- """Get a value from the context."""
37
- return self._data.get(key, default)
38
-
39
- def __setattr__(self, name: str, value: Any) -> None:
40
- if name.startswith("_") or name == "trace_id":
41
- super().__setattr__(name, value)
42
- else:
43
- if not hasattr(self, "_data"):
44
- super().__setattr__(name, value)
45
- else:
46
- self._data[name] = value
47
-
48
- def __getattr__(self, name: str) -> Any:
49
- if hasattr(self, "_data") and name in self._data:
50
- return self._data[name]
51
- raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
52
-
53
- def to_dict(self) -> dict[str, Any]:
54
- """Convert context to dictionary."""
55
- return {"trace_id": self.trace_id, "data": self._data.copy()}
56
-
57
-
58
- def set_request_context(context: RequestContext) -> None:
59
- """
60
- Set the current request context.
61
-
62
- This is typically called by the API dependency injection system.
63
- """
64
- _request_context.set(context.to_dict())
65
-
66
-
67
- def get_current_trace_id() -> str | None:
68
- """
69
- Get the current request's trace_id.
70
-
71
- Returns:
72
- The trace_id if available, None otherwise.
73
- """
74
- context = _request_context.get()
75
- if context:
76
- return context.get("trace_id")
77
- return None
78
-
79
-
80
- def get_current_context() -> RequestContext | None:
81
- """
82
- Get the current request context.
83
-
84
- Returns:
85
- The current RequestContext if available, None otherwise.
86
- """
87
- context_dict = _request_context.get()
88
- if context_dict:
89
- ctx = RequestContext(trace_id=context_dict.get("trace_id"))
90
- ctx._data = context_dict.get("data", {}).copy()
91
- return ctx
92
- return None
93
-
94
-
95
- def require_context() -> RequestContext:
96
- """
97
- Get the current request context, raising an error if not available.
98
-
99
- Returns:
100
- The current RequestContext.
101
-
102
- Raises:
103
- RuntimeError: If called outside of a request context.
104
- """
105
- context = get_current_context()
106
- if context is None:
107
- raise RuntimeError(
108
- "No request context available. This function must be called within a request handler."
109
- )
110
- return context
111
-
112
-
113
- # Type for trace_id getter function
114
- TraceIdGetter = Callable[[], str | None]
115
-
116
- # Global variable to hold the trace_id getter function
117
- _trace_id_getter: TraceIdGetter | None = None
118
-
119
-
120
- def set_trace_id_getter(getter: TraceIdGetter) -> None:
121
- """
122
- Set a custom trace_id getter function.
123
-
124
- This allows the logging system to retrieve trace_id without importing
125
- API-specific general_modules.
126
- """
127
- global _trace_id_getter
128
- _trace_id_getter = getter
129
-
130
-
131
- def get_trace_id_for_logging() -> str | None:
132
- """
133
- Get trace_id for logging purposes.
134
-
135
- This function is used by the logging system and will use either
136
- the custom getter function or fall back to the default context.
137
- """
138
- if _trace_id_getter:
139
- try:
140
- return _trace_id_getter()
141
- except Exception:
142
- pass
143
- return get_current_trace_id()
144
-
145
-
146
- # Initialize the default trace_id getter
147
- set_trace_id_getter(get_current_trace_id)
@@ -1,96 +0,0 @@
1
- import functools
2
- import threading
3
-
4
- from collections.abc import Callable
5
- from concurrent.futures import ThreadPoolExecutor
6
- from typing import Any, TypeVar
7
-
8
- from memos.api.context.context import (
9
- RequestContext,
10
- get_current_context,
11
- get_current_trace_id,
12
- set_request_context,
13
- )
14
-
15
-
16
- T = TypeVar("T")
17
-
18
-
19
- class ContextThread(threading.Thread):
20
- """
21
- Thread class that automatically propagates the main thread's trace_id to child threads.
22
- """
23
-
24
- def __init__(self, target, args=(), kwargs=None, **thread_kwargs):
25
- super().__init__(**thread_kwargs)
26
- self.target = target
27
- self.args = args
28
- self.kwargs = kwargs or {}
29
-
30
- self.main_trace_id = get_current_trace_id()
31
- self.main_context = get_current_context()
32
-
33
- def run(self):
34
- # Create a new RequestContext with the main thread's trace_id
35
- if self.main_context:
36
- # Copy the context data
37
- child_context = RequestContext(trace_id=self.main_trace_id)
38
- child_context._data = self.main_context._data.copy()
39
-
40
- # Set the context in the child thread
41
- set_request_context(child_context)
42
-
43
- # Run the target function
44
- self.target(*self.args, **self.kwargs)
45
-
46
-
47
- class ContextThreadPoolExecutor(ThreadPoolExecutor):
48
- """
49
- ThreadPoolExecutor that automatically propagates the main thread's trace_id to worker threads.
50
- """
51
-
52
- def submit(self, fn: Callable[..., T], *args: Any, **kwargs: Any) -> Any:
53
- """
54
- Submit a callable to be executed with the given arguments.
55
- Automatically propagates the current thread's context to the worker thread.
56
- """
57
- main_trace_id = get_current_trace_id()
58
- main_context = get_current_context()
59
-
60
- @functools.wraps(fn)
61
- def wrapper(*args: Any, **kwargs: Any) -> Any:
62
- if main_context:
63
- # Create and set new context in worker thread
64
- child_context = RequestContext(trace_id=main_trace_id)
65
- child_context._data = main_context._data.copy()
66
- set_request_context(child_context)
67
-
68
- return fn(*args, **kwargs)
69
-
70
- return super().submit(wrapper, *args, **kwargs)
71
-
72
- def map(
73
- self,
74
- fn: Callable[..., T],
75
- *iterables: Any,
76
- timeout: float | None = None,
77
- chunksize: int = 1,
78
- ) -> Any:
79
- """
80
- Returns an iterator equivalent to map(fn, iter).
81
- Automatically propagates the current thread's context to worker threads.
82
- """
83
- main_trace_id = get_current_trace_id()
84
- main_context = get_current_context()
85
-
86
- @functools.wraps(fn)
87
- def wrapper(*args: Any, **kwargs: Any) -> Any:
88
- if main_context:
89
- # Create and set new context in worker thread
90
- child_context = RequestContext(trace_id=main_trace_id)
91
- child_context._data = main_context._data.copy()
92
- set_request_context(child_context)
93
-
94
- return fn(*args, **kwargs)
95
-
96
- return super().map(wrapper, *iterables, timeout=timeout, chunksize=chunksize)
@@ -1,146 +0,0 @@
1
- from datetime import datetime
2
-
3
- from memos.configs.mem_os import MOSConfig
4
- from memos.log import get_logger
5
- from memos.mem_os.main import MOS
6
- from memos.mem_scheduler.schemas.general_schemas import (
7
- ANSWER_LABEL,
8
- MONITOR_WORKING_MEMORY_TYPE,
9
- QUERY_LABEL,
10
- )
11
- from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
12
-
13
-
14
- logger = get_logger(__name__)
15
-
16
-
17
- class MOSForTestScheduler(MOS):
18
- """This class is only to test abilities of mem scheduler"""
19
-
20
- def __init__(self, config: MOSConfig):
21
- super().__init__(config)
22
-
23
- def _str_memories(self, memories: list[str]) -> str:
24
- """Format memories for display."""
25
- if not memories:
26
- return "No memories."
27
- return "\n".join(f"{i + 1}. {memory}" for i, memory in enumerate(memories))
28
-
29
- def chat(self, query: str, user_id: str | None = None) -> str:
30
- """
31
- Chat with the MOS.
32
-
33
- Args:
34
- query (str): The user's query.
35
-
36
- Returns:
37
- str: The response from the MOS.
38
- """
39
- target_user_id = user_id if user_id is not None else self.user_id
40
- accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
41
- user_cube_ids = [cube.cube_id for cube in accessible_cubes]
42
- if target_user_id not in self.chat_history_manager:
43
- self._register_chat_history(target_user_id)
44
-
45
- chat_history = self.chat_history_manager[target_user_id]
46
-
47
- topk_for_scheduler = 2
48
-
49
- if self.config.enable_textual_memory and self.mem_cubes:
50
- memories_all = []
51
- for mem_cube_id, mem_cube in self.mem_cubes.items():
52
- if mem_cube_id not in user_cube_ids:
53
- continue
54
- if not mem_cube.text_mem:
55
- continue
56
-
57
- message_item = ScheduleMessageItem(
58
- user_id=target_user_id,
59
- mem_cube_id=mem_cube_id,
60
- mem_cube=mem_cube,
61
- label=QUERY_LABEL,
62
- content=query,
63
- timestamp=datetime.now(),
64
- )
65
- cur_working_memories = [m.memory for m in mem_cube.text_mem.get_working_memory()]
66
- print(f"Working memories before schedule: {cur_working_memories}")
67
-
68
- # --- force to run mem_scheduler ---
69
- self.mem_scheduler.monitor.query_trigger_interval = 0
70
- self.mem_scheduler._query_message_consumer(messages=[message_item])
71
-
72
- # from scheduler
73
- scheduler_memories = self.mem_scheduler.monitor.get_monitor_memories(
74
- user_id=target_user_id,
75
- mem_cube_id=mem_cube_id,
76
- memory_type=MONITOR_WORKING_MEMORY_TYPE,
77
- top_k=topk_for_scheduler,
78
- )
79
- print(f"Working memories after schedule: {scheduler_memories}")
80
- memories_all.extend(scheduler_memories)
81
-
82
- # from mem_cube
83
- memories = mem_cube.text_mem.search(
84
- query,
85
- top_k=self.config.top_k - topk_for_scheduler,
86
- info={
87
- "user_id": target_user_id,
88
- "session_id": self.session_id,
89
- "chat_history": chat_history.chat_history,
90
- },
91
- )
92
- text_memories = [m.memory for m in memories]
93
- print(f"Search results with new working memories: {text_memories}")
94
- memories_all.extend(text_memories)
95
-
96
- memories_all = list(set(memories_all))
97
-
98
- logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
99
- system_prompt = self._build_system_prompt(memories_all)
100
- else:
101
- system_prompt = self._build_system_prompt()
102
- current_messages = [
103
- {"role": "system", "content": system_prompt},
104
- *chat_history.chat_history,
105
- {"role": "user", "content": query},
106
- ]
107
- past_key_values = None
108
-
109
- if self.config.enable_activation_memory:
110
- assert self.config.chat_model.backend == "huggingface", (
111
- "Activation memory only used for huggingface backend."
112
- )
113
- # TODO this only one cubes
114
- for mem_cube_id, mem_cube in self.mem_cubes.items():
115
- if mem_cube_id not in user_cube_ids:
116
- continue
117
- if mem_cube.act_mem:
118
- kv_cache = next(iter(mem_cube.act_mem.get_all()), None)
119
- past_key_values = (
120
- kv_cache.memory if (kv_cache and hasattr(kv_cache, "memory")) else None
121
- )
122
- break
123
- # Generate response
124
- response = self.chat_llm.generate(current_messages, past_key_values=past_key_values)
125
- else:
126
- response = self.chat_llm.generate(current_messages)
127
- logger.info(f"🤖 [Assistant] {response}\n")
128
- chat_history.chat_history.append({"role": "user", "content": query})
129
- chat_history.chat_history.append({"role": "assistant", "content": response})
130
- self.chat_history_manager[user_id] = chat_history
131
-
132
- # submit message to scheduler
133
- for accessible_mem_cube in accessible_cubes:
134
- mem_cube_id = accessible_mem_cube.cube_id
135
- mem_cube = self.mem_cubes[mem_cube_id]
136
- if self.enable_mem_scheduler and self.mem_scheduler is not None:
137
- message_item = ScheduleMessageItem(
138
- user_id=target_user_id,
139
- mem_cube_id=mem_cube_id,
140
- mem_cube=mem_cube,
141
- label=ANSWER_LABEL,
142
- content=response,
143
- timestamp=datetime.now(),
144
- )
145
- self.mem_scheduler.submit_messages(messages=[message_item])
146
- return response