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.

Files changed (119) hide show
  1. memoryos-0.1.12.dist-info/METADATA +257 -0
  2. memoryos-0.1.12.dist-info/RECORD +117 -0
  3. memos/__init__.py +20 -1
  4. memos/api/start_api.py +420 -0
  5. memos/chunkers/__init__.py +4 -0
  6. memos/chunkers/base.py +24 -0
  7. memos/chunkers/factory.py +22 -0
  8. memos/chunkers/sentence_chunker.py +35 -0
  9. memos/configs/__init__.py +0 -0
  10. memos/configs/base.py +82 -0
  11. memos/configs/chunker.py +45 -0
  12. memos/configs/embedder.py +53 -0
  13. memos/configs/graph_db.py +45 -0
  14. memos/configs/llm.py +71 -0
  15. memos/configs/mem_chat.py +81 -0
  16. memos/configs/mem_cube.py +89 -0
  17. memos/configs/mem_os.py +70 -0
  18. memos/configs/mem_reader.py +53 -0
  19. memos/configs/mem_scheduler.py +78 -0
  20. memos/configs/memory.py +190 -0
  21. memos/configs/parser.py +38 -0
  22. memos/configs/utils.py +8 -0
  23. memos/configs/vec_db.py +64 -0
  24. memos/deprecation.py +262 -0
  25. memos/embedders/__init__.py +0 -0
  26. memos/embedders/base.py +15 -0
  27. memos/embedders/factory.py +23 -0
  28. memos/embedders/ollama.py +74 -0
  29. memos/embedders/sentence_transformer.py +40 -0
  30. memos/exceptions.py +30 -0
  31. memos/graph_dbs/__init__.py +0 -0
  32. memos/graph_dbs/base.py +215 -0
  33. memos/graph_dbs/factory.py +21 -0
  34. memos/graph_dbs/neo4j.py +827 -0
  35. memos/hello_world.py +97 -0
  36. memos/llms/__init__.py +0 -0
  37. memos/llms/base.py +16 -0
  38. memos/llms/factory.py +25 -0
  39. memos/llms/hf.py +231 -0
  40. memos/llms/ollama.py +82 -0
  41. memos/llms/openai.py +34 -0
  42. memos/llms/utils.py +14 -0
  43. memos/log.py +78 -0
  44. memos/mem_chat/__init__.py +0 -0
  45. memos/mem_chat/base.py +30 -0
  46. memos/mem_chat/factory.py +21 -0
  47. memos/mem_chat/simple.py +200 -0
  48. memos/mem_cube/__init__.py +0 -0
  49. memos/mem_cube/base.py +29 -0
  50. memos/mem_cube/general.py +146 -0
  51. memos/mem_cube/utils.py +24 -0
  52. memos/mem_os/client.py +5 -0
  53. memos/mem_os/core.py +819 -0
  54. memos/mem_os/main.py +12 -0
  55. memos/mem_os/product.py +89 -0
  56. memos/mem_reader/__init__.py +0 -0
  57. memos/mem_reader/base.py +27 -0
  58. memos/mem_reader/factory.py +21 -0
  59. memos/mem_reader/memory.py +298 -0
  60. memos/mem_reader/simple_struct.py +241 -0
  61. memos/mem_scheduler/__init__.py +0 -0
  62. memos/mem_scheduler/base_scheduler.py +164 -0
  63. memos/mem_scheduler/general_scheduler.py +305 -0
  64. memos/mem_scheduler/modules/__init__.py +0 -0
  65. memos/mem_scheduler/modules/base.py +74 -0
  66. memos/mem_scheduler/modules/dispatcher.py +103 -0
  67. memos/mem_scheduler/modules/monitor.py +82 -0
  68. memos/mem_scheduler/modules/redis_service.py +146 -0
  69. memos/mem_scheduler/modules/retriever.py +41 -0
  70. memos/mem_scheduler/modules/schemas.py +146 -0
  71. memos/mem_scheduler/scheduler_factory.py +21 -0
  72. memos/mem_scheduler/utils.py +26 -0
  73. memos/mem_user/user_manager.py +478 -0
  74. memos/memories/__init__.py +0 -0
  75. memos/memories/activation/__init__.py +0 -0
  76. memos/memories/activation/base.py +42 -0
  77. memos/memories/activation/item.py +25 -0
  78. memos/memories/activation/kv.py +232 -0
  79. memos/memories/base.py +19 -0
  80. memos/memories/factory.py +34 -0
  81. memos/memories/parametric/__init__.py +0 -0
  82. memos/memories/parametric/base.py +19 -0
  83. memos/memories/parametric/item.py +11 -0
  84. memos/memories/parametric/lora.py +41 -0
  85. memos/memories/textual/__init__.py +0 -0
  86. memos/memories/textual/base.py +89 -0
  87. memos/memories/textual/general.py +286 -0
  88. memos/memories/textual/item.py +167 -0
  89. memos/memories/textual/naive.py +185 -0
  90. memos/memories/textual/tree.py +289 -0
  91. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  92. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  93. memos/memories/textual/tree_text_memory/organize/manager.py +305 -0
  94. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  95. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +64 -0
  96. memos/memories/textual/tree_text_memory/retrieve/recall.py +158 -0
  97. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  98. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +13 -0
  99. memos/memories/textual/tree_text_memory/retrieve/searcher.py +166 -0
  100. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +68 -0
  101. memos/memories/textual/tree_text_memory/retrieve/utils.py +48 -0
  102. memos/parsers/__init__.py +0 -0
  103. memos/parsers/base.py +15 -0
  104. memos/parsers/factory.py +19 -0
  105. memos/parsers/markitdown.py +22 -0
  106. memos/settings.py +8 -0
  107. memos/templates/__init__.py +0 -0
  108. memos/templates/mem_reader_prompts.py +98 -0
  109. memos/templates/mem_scheduler_prompts.py +65 -0
  110. memos/types.py +55 -0
  111. memos/vec_dbs/__init__.py +0 -0
  112. memos/vec_dbs/base.py +105 -0
  113. memos/vec_dbs/factory.py +21 -0
  114. memos/vec_dbs/item.py +43 -0
  115. memos/vec_dbs/qdrant.py +292 -0
  116. memoryos-0.0.1.dist-info/METADATA +0 -53
  117. memoryos-0.0.1.dist-info/RECORD +0 -5
  118. {memoryos-0.0.1.dist-info → memoryos-0.1.12.dist-info}/LICENSE +0 -0
  119. {memoryos-0.0.1.dist-info → memoryos-0.1.12.dist-info}/WHEEL +0 -0
@@ -0,0 +1,241 @@
1
+ import concurrent.futures
2
+ import copy
3
+ import json
4
+
5
+ from abc import ABC
6
+ from typing import Any
7
+
8
+ from memos import log
9
+ from memos.chunkers import ChunkerFactory
10
+ from memos.configs.mem_reader import SimpleStructMemReaderConfig
11
+ from memos.configs.parser import ParserConfigFactory
12
+ from memos.embedders.factory import EmbedderFactory
13
+ from memos.llms.factory import LLMFactory
14
+ from memos.mem_reader.base import BaseMemReader
15
+ from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata
16
+ from memos.parsers.factory import ParserFactory
17
+ from memos.templates.mem_reader_prompts import (
18
+ SIMPLE_STRUCT_DOC_READER_PROMPT,
19
+ SIMPLE_STRUCT_MEM_READER_PROMPT,
20
+ )
21
+
22
+
23
+ logger = log.get_logger(__name__)
24
+
25
+
26
+ class SimpleStructMemReader(BaseMemReader, ABC):
27
+ """Naive implementation of MemReader."""
28
+
29
+ def __init__(self, config: SimpleStructMemReaderConfig):
30
+ """
31
+ Initialize the NaiveMemReader with configuration.
32
+
33
+ Args:
34
+ config: Configuration object for the reader
35
+ """
36
+ self.config = config
37
+ self.llm = LLMFactory.from_config(config.llm)
38
+ self.embedder = EmbedderFactory.from_config(config.embedder)
39
+ self.chunker = ChunkerFactory.from_config(config.chunker)
40
+
41
+ def _process_chat_data(self, scene_data_info, info):
42
+ prompt = (
43
+ SIMPLE_STRUCT_MEM_READER_PROMPT.replace("${user_a}", "user")
44
+ .replace("${user_b}", "assistant")
45
+ .replace("${conversation}", "\n".join(scene_data_info))
46
+ )
47
+
48
+ messages = [{"role": "user", "content": prompt}]
49
+
50
+ response_text = self.llm.generate(messages)
51
+ response_json = self.parse_json_result(response_text)
52
+
53
+ chat_read_nodes = []
54
+ for memory_i_raw in response_json.get("memory list", []):
55
+ node_i = TextualMemoryItem(
56
+ memory=memory_i_raw.get("value", ""),
57
+ metadata=TreeNodeTextualMemoryMetadata(
58
+ user_id=info.get("user_id"),
59
+ session_id=info.get("session_id"),
60
+ memory_type=memory_i_raw.get("memory_type", ""),
61
+ status="activated",
62
+ tags=memory_i_raw.get("tags", ""),
63
+ key=memory_i_raw.get("key", ""),
64
+ embedding=self.embedder.embed([memory_i_raw.get("value", "")])[0],
65
+ usage=[],
66
+ sources=scene_data_info,
67
+ background=response_json.get("summary", ""),
68
+ confidence=0.99,
69
+ type="fact",
70
+ ),
71
+ )
72
+ chat_read_nodes.append(node_i)
73
+
74
+ return chat_read_nodes
75
+
76
+ def get_memory(
77
+ self, scene_data: list, type: str, info: dict[str, Any]
78
+ ) -> list[list[TextualMemoryItem]]:
79
+ """
80
+ Extract and classify memory content from scene_data.
81
+ For dictionaries: Use LLM to summarize pairs of Q&A
82
+ For file paths: Use chunker to split documents and LLM to summarize each chunk
83
+
84
+ Args:
85
+ scene_data: List of dialogue information or document paths
86
+ type: Type of scene_data: ['doc', 'chat']
87
+ info: Dictionary containing user_id and session_id.
88
+ Must be in format: {"user_id": "1111", "session_id": "2222"}
89
+ Optional parameters:
90
+ - topic_chunk_size: Size for large topic chunks (default: 1024)
91
+ - topic_chunk_overlap: Overlap for large topic chunks (default: 100)
92
+ - chunk_size: Size for small chunks (default: 256)
93
+ - chunk_overlap: Overlap for small chunks (default: 50)
94
+ Returns:
95
+ list[list[TextualMemoryItem]] containing memory content with summaries as keys and original text as values
96
+ Raises:
97
+ ValueError: If scene_data is empty or if info dictionary is missing required fields
98
+ """
99
+ if not scene_data:
100
+ raise ValueError("scene_data is empty")
101
+
102
+ # Validate info dictionary format
103
+ if not isinstance(info, dict):
104
+ raise ValueError("info must be a dictionary")
105
+
106
+ required_fields = {"user_id", "session_id"}
107
+ missing_fields = required_fields - set(info.keys())
108
+ if missing_fields:
109
+ raise ValueError(f"info dictionary is missing required fields: {missing_fields}")
110
+
111
+ if not all(isinstance(info[field], str) for field in required_fields):
112
+ raise ValueError("user_id and session_id must be strings")
113
+
114
+ list_scene_data_info = self.get_scene_data_info(scene_data, type)
115
+
116
+ memory_list = []
117
+
118
+ if type == "chat":
119
+ processing_func = self._process_chat_data
120
+ elif type == "doc":
121
+ processing_func = self._process_doc_data
122
+ else:
123
+ processing_func = self._process_doc_data
124
+
125
+ # Process Q&A pairs concurrently
126
+ with concurrent.futures.ThreadPoolExecutor() as executor:
127
+ futures = [
128
+ executor.submit(processing_func, scene_data_info, info)
129
+ for scene_data_info in list_scene_data_info
130
+ ]
131
+ for future in concurrent.futures.as_completed(futures):
132
+ res_memory = future.result()
133
+ memory_list.append(res_memory)
134
+
135
+ return memory_list
136
+
137
+ def get_scene_data_info(self, scene_data: list, type: str) -> list[str]:
138
+ """
139
+ Get raw information from scene_data.
140
+ If scene_data contains dictionaries, convert them to strings.
141
+ If scene_data contains file paths, parse them using the parser.
142
+
143
+ Args:
144
+ scene_data: List of dialogue information or document paths
145
+ type: Type of scene data: ['doc', 'chat']
146
+ Returns:
147
+ List of strings containing the processed scene data
148
+ """
149
+ results = []
150
+ parser_config = ParserConfigFactory.model_validate(
151
+ {
152
+ "backend": "markitdown",
153
+ "config": {},
154
+ }
155
+ )
156
+ parser = ParserFactory.from_config(parser_config)
157
+
158
+ if type == "chat":
159
+ for items in scene_data:
160
+ result = []
161
+ for item in items:
162
+ # Convert dictionary to string
163
+ if "chat_time" in item:
164
+ mem = item["role"] + ": " + f"[{item['chat_time']}]: " + item["content"]
165
+ result.append(mem)
166
+ else:
167
+ mem = item["role"] + ":" + item["content"]
168
+ result.append(mem)
169
+ if len(result) >= 10:
170
+ results.append(result)
171
+ context = copy.deepcopy(result[-2:])
172
+ result = context
173
+ if result:
174
+ results.append(result)
175
+ elif type == "doc":
176
+ for item in scene_data:
177
+ try:
178
+ parsed_text = parser.parse(item)
179
+ results.append({"file": item, "text": parsed_text})
180
+ except Exception as e:
181
+ print(f"Error parsing file {item}: {e!s}")
182
+
183
+ return results
184
+
185
+ def _process_doc_data(self, scene_data_info, info):
186
+ chunks = self.chunker.chunk(scene_data_info["text"])
187
+ messages = [
188
+ [
189
+ {
190
+ "role": "user",
191
+ "content": SIMPLE_STRUCT_DOC_READER_PROMPT.replace("{chunk_text}", chunk.text),
192
+ }
193
+ ]
194
+ for chunk in chunks
195
+ ]
196
+
197
+ processed_chunks = []
198
+ with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
199
+ futures = [executor.submit(self.llm.generate, message) for message in messages]
200
+ for future in concurrent.futures.as_completed(futures):
201
+ chunk_result = future.result()
202
+ if chunk_result:
203
+ processed_chunks.append(chunk_result)
204
+
205
+ processed_chunks = [self.parse_json_result(r) for r in processed_chunks]
206
+ doc_nodes = []
207
+ for i, chunk_res in enumerate(processed_chunks):
208
+ if chunk_res:
209
+ node_i = TextualMemoryItem(
210
+ memory=chunk_res["summary"],
211
+ metadata=TreeNodeTextualMemoryMetadata(
212
+ user_id=info.get("user_id"),
213
+ session_id=info.get("session_id"),
214
+ memory_type="LongTermMemory",
215
+ status="activated",
216
+ tags=chunk_res["tags"],
217
+ key="",
218
+ embedding=self.embedder.embed([chunk_res["summary"]])[0],
219
+ usage=[],
220
+ sources=[f"{scene_data_info['file']}_{i}"],
221
+ background="",
222
+ confidence=0.99,
223
+ type="fact",
224
+ ),
225
+ )
226
+ doc_nodes.append(node_i)
227
+ return doc_nodes
228
+
229
+ def parse_json_result(self, response_text):
230
+ try:
231
+ response_text = response_text.replace("```", "").replace("json", "")
232
+ response_json = json.loads(response_text)
233
+ return response_json
234
+ except json.JSONDecodeError as e:
235
+ logger.warning(
236
+ f"Failed to parse LLM response as JSON: {e}\nRaw response:\n{response_text}"
237
+ )
238
+ return {}
239
+
240
+ def transform_memreader(self, data: dict) -> list[TextualMemoryItem]:
241
+ pass
File without changes
@@ -0,0 +1,164 @@
1
+ import queue
2
+ import threading
3
+ import time
4
+
5
+ from abc import abstractmethod
6
+ from queue import Queue
7
+
8
+ from memos.configs.mem_scheduler import BaseSchedulerConfig
9
+ from memos.llms.base import BaseLLM
10
+ from memos.log import get_logger
11
+ from memos.mem_scheduler.modules.dispatcher import SchedulerDispatcher
12
+ from memos.mem_scheduler.modules.redis_service import RedisSchedulerModule
13
+ from memos.mem_scheduler.modules.schemas import (
14
+ DEFAULT_CONSUME_INTERVAL_SECONDS,
15
+ DEFAULT_THREAD__POOL_MAX_WORKERS,
16
+ ScheduleLogForWebItem,
17
+ ScheduleMessageItem,
18
+ )
19
+
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class BaseScheduler(RedisSchedulerModule):
25
+ """Base class for all mem_scheduler."""
26
+
27
+ def __init__(self, config: BaseSchedulerConfig):
28
+ """Initialize the scheduler with the given configuration."""
29
+ super().__init__()
30
+ self.config = config
31
+ self.max_workers = self.config.get(
32
+ "thread_pool_max_workers", DEFAULT_THREAD__POOL_MAX_WORKERS
33
+ )
34
+ self.retriever = None
35
+ self.monitor = None
36
+ self.enable_parallel_dispatch = self.config.get("enable_parallel_dispatch", False)
37
+ self.dispatcher = SchedulerDispatcher(
38
+ max_workers=self.max_workers, enable_parallel_dispatch=self.enable_parallel_dispatch
39
+ )
40
+
41
+ # message queue
42
+ self.memos_message_queue: Queue[ScheduleMessageItem] = Queue()
43
+ self._web_log_message_queue: Queue[ScheduleLogForWebItem] = Queue()
44
+ self._consumer_thread = None # Reference to our consumer thread
45
+ self._running = False
46
+ self._consume_interval = self.config.get(
47
+ "consume_interval_seconds", DEFAULT_CONSUME_INTERVAL_SECONDS
48
+ )
49
+
50
+ # others
51
+ self._current_user_id: str | None = None
52
+
53
+ @abstractmethod
54
+ def initialize_modules(self, chat_llm: BaseLLM) -> None:
55
+ """Initialize all necessary modules for the scheduler
56
+
57
+ Args:
58
+ chat_llm: The LLM instance to be used for chat interactions
59
+ """
60
+
61
+ def submit_messages(self, messages: ScheduleMessageItem | list[ScheduleMessageItem]):
62
+ """Submit multiple messages to the message queue."""
63
+ if isinstance(messages, ScheduleMessageItem):
64
+ messages = [messages] # transform single message to list
65
+
66
+ for message in messages:
67
+ self.memos_message_queue.put(message)
68
+ logger.info(f"Submitted message: {message.label} - {message.content}")
69
+
70
+ def _submit_web_logs(self, messages: ScheduleLogForWebItem | list[ScheduleLogForWebItem]):
71
+ if isinstance(messages, ScheduleLogForWebItem):
72
+ messages = [messages] # transform single message to list
73
+
74
+ for message in messages:
75
+ self._web_log_message_queue.put(message)
76
+ logger.info(
77
+ f"Submitted Scheduling log for web: {message.log_title} - {message.log_content}"
78
+ )
79
+ logger.debug(f"{len(messages)} submitted. {self._web_log_message_queue.qsize()} in queue.")
80
+
81
+ def get_web_log_messages(self) -> list[dict]:
82
+ """
83
+ Retrieves all web log messages from the queue and returns them as a list of JSON-serializable dictionaries.
84
+
85
+ Returns:
86
+ List[dict]: A list of dictionaries representing ScheduleLogForWebItem objects,
87
+ ready for JSON serialization. The list is ordered from oldest to newest.
88
+ """
89
+ messages = []
90
+
91
+ # Process all items in the queue
92
+ while not self._web_log_message_queue.empty():
93
+ item = self._web_log_message_queue.get()
94
+ # Convert the ScheduleLogForWebItem to a dictionary and ensure datetime is serialized
95
+ item_dict = item.to_dict()
96
+ messages.append(item_dict)
97
+ return messages
98
+
99
+ def _message_consumer(self) -> None:
100
+ """
101
+ Continuously checks the queue for messages and dispatches them.
102
+
103
+ Runs in a dedicated thread to process messages at regular intervals.
104
+ """
105
+ while self._running: # Use a running flag for graceful shutdown
106
+ try:
107
+ # Check if queue has messages (non-blocking)
108
+ if not self.memos_message_queue.empty():
109
+ # Get all available messages at once
110
+ messages = []
111
+ while not self.memos_message_queue.empty():
112
+ try:
113
+ messages.append(self.memos_message_queue.get_nowait())
114
+ except queue.Empty:
115
+ break
116
+
117
+ if messages:
118
+ try:
119
+ self.dispatcher.dispatch(messages)
120
+ except Exception as e:
121
+ logger.error(f"Error dispatching messages: {e!s}")
122
+ finally:
123
+ # Mark all messages as processed
124
+ for _ in messages:
125
+ self.memos_message_queue.task_done()
126
+
127
+ # Sleep briefly to prevent busy waiting
128
+ time.sleep(self._consume_interval) # Adjust interval as needed
129
+
130
+ except Exception as e:
131
+ logger.error(f"Unexpected error in message consumer: {e!s}")
132
+ time.sleep(self._consume_interval) # Prevent tight error loops
133
+
134
+ def start(self) -> None:
135
+ """
136
+ Start the message consumer thread.
137
+
138
+ Initializes and starts a daemon thread that will periodically
139
+ check for and process messages from the queue.
140
+ """
141
+ if self._consumer_thread is not None and self._consumer_thread.is_alive():
142
+ logger.warning("Consumer thread is already running")
143
+ return
144
+
145
+ self._running = True
146
+ self._consumer_thread = threading.Thread(
147
+ target=self._message_consumer,
148
+ daemon=True, # Allows program to exit even if thread is running
149
+ name="MessageConsumerThread",
150
+ )
151
+ self._consumer_thread.start()
152
+ logger.info("Message consumer thread started")
153
+
154
+ def stop(self) -> None:
155
+ """Stop the consumer thread and clean up resources."""
156
+ if self._consumer_thread is None or not self._running:
157
+ logger.warning("Consumer thread is not running")
158
+ return
159
+ self._running = False
160
+ if self._consumer_thread.is_alive():
161
+ self._consumer_thread.join(timeout=5.0) # Wait up to 5 seconds
162
+ if self._consumer_thread.is_alive():
163
+ logger.warning("Consumer thread did not stop gracefully")
164
+ logger.info("Message consumer thread stopped")