MemoryOS 0.2.1__py3-none-any.whl → 0.2.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 (74) hide show
  1. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/METADATA +2 -1
  2. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/RECORD +72 -55
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +156 -65
  5. memos/api/context/context.py +147 -0
  6. memos/api/context/dependencies.py +90 -0
  7. memos/api/product_models.py +5 -1
  8. memos/api/routers/product_router.py +54 -26
  9. memos/configs/graph_db.py +49 -1
  10. memos/configs/internet_retriever.py +6 -0
  11. memos/configs/mem_os.py +5 -0
  12. memos/configs/mem_reader.py +9 -0
  13. memos/configs/mem_scheduler.py +18 -4
  14. memos/configs/mem_user.py +58 -0
  15. memos/graph_dbs/base.py +9 -1
  16. memos/graph_dbs/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +1364 -0
  18. memos/graph_dbs/neo4j.py +4 -4
  19. memos/log.py +1 -1
  20. memos/mem_cube/utils.py +13 -6
  21. memos/mem_os/core.py +140 -30
  22. memos/mem_os/main.py +1 -1
  23. memos/mem_os/product.py +266 -152
  24. memos/mem_os/utils/format_utils.py +314 -67
  25. memos/mem_reader/simple_struct.py +13 -5
  26. memos/mem_scheduler/base_scheduler.py +220 -250
  27. memos/mem_scheduler/general_scheduler.py +193 -73
  28. memos/mem_scheduler/modules/base.py +5 -5
  29. memos/mem_scheduler/modules/dispatcher.py +6 -9
  30. memos/mem_scheduler/modules/misc.py +81 -16
  31. memos/mem_scheduler/modules/monitor.py +52 -41
  32. memos/mem_scheduler/modules/rabbitmq_service.py +9 -7
  33. memos/mem_scheduler/modules/retriever.py +108 -191
  34. memos/mem_scheduler/modules/scheduler_logger.py +255 -0
  35. memos/mem_scheduler/mos_for_test_scheduler.py +16 -19
  36. memos/mem_scheduler/schemas/__init__.py +0 -0
  37. memos/mem_scheduler/schemas/general_schemas.py +43 -0
  38. memos/mem_scheduler/schemas/message_schemas.py +148 -0
  39. memos/mem_scheduler/schemas/monitor_schemas.py +329 -0
  40. memos/mem_scheduler/utils/__init__.py +0 -0
  41. memos/mem_scheduler/utils/filter_utils.py +176 -0
  42. memos/mem_scheduler/utils/misc_utils.py +61 -0
  43. memos/mem_user/factory.py +94 -0
  44. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  45. memos/mem_user/mysql_user_manager.py +500 -0
  46. memos/mem_user/persistent_factory.py +96 -0
  47. memos/mem_user/user_manager.py +4 -4
  48. memos/memories/activation/item.py +4 -0
  49. memos/memories/textual/base.py +1 -1
  50. memos/memories/textual/general.py +35 -91
  51. memos/memories/textual/item.py +5 -33
  52. memos/memories/textual/tree.py +13 -7
  53. memos/memories/textual/tree_text_memory/organize/conflict.py +4 -2
  54. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +47 -43
  55. memos/memories/textual/tree_text_memory/organize/reorganizer.py +8 -5
  56. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  57. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  58. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  59. memos/memories/textual/tree_text_memory/retrieve/searcher.py +46 -23
  60. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -15
  61. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  62. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  63. memos/memos_tools/dinding_report_bot.py +422 -0
  64. memos/memos_tools/notification_service.py +44 -0
  65. memos/memos_tools/notification_utils.py +96 -0
  66. memos/settings.py +3 -1
  67. memos/templates/mem_reader_prompts.py +2 -1
  68. memos/templates/mem_scheduler_prompts.py +41 -7
  69. memos/templates/mos_prompts.py +87 -0
  70. memos/mem_scheduler/modules/schemas.py +0 -328
  71. memos/mem_scheduler/utils.py +0 -75
  72. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
  73. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/WHEEL +0 -0
  74. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/entry_points.txt +0 -0
memos/graph_dbs/neo4j.py CHANGED
@@ -114,14 +114,14 @@ class Neo4jGraphDB(BaseGraphDB):
114
114
  )
115
115
  return result.single()["count"]
116
116
 
117
- def count_nodes(self, scope: str) -> int:
117
+ def node_not_exist(self, scope: str) -> int:
118
118
  query = """
119
119
  MATCH (n:Memory)
120
120
  WHERE n.memory_type = $scope
121
121
  """
122
122
  if not self.config.use_multi_db and self.config.user_name:
123
123
  query += "\nAND n.user_name = $user_name"
124
- query += "\nRETURN count(n) AS count"
124
+ query += "\nRETURN n LIMIT 1"
125
125
 
126
126
  with self.driver.session(database=self.db_name) as session:
127
127
  result = session.run(
@@ -131,7 +131,7 @@ class Neo4jGraphDB(BaseGraphDB):
131
131
  "user_name": self.config.user_name if self.config.user_name else None,
132
132
  },
133
133
  )
134
- return result.single()["count"]
134
+ return result.single() is None
135
135
 
136
136
  def remove_oldest_memory(self, memory_type: str, keep_latest: int) -> None:
137
137
  """
@@ -920,7 +920,7 @@ class Neo4jGraphDB(BaseGraphDB):
920
920
  Returns:
921
921
  list[dict]: Full list of memory items under this scope.
922
922
  """
923
- if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory"}:
923
+ if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}:
924
924
  raise ValueError(f"Unsupported memory type scope: {scope}")
925
925
 
926
926
  where_clause = "WHERE n.memory_type = $scope"
memos/log.py CHANGED
@@ -48,7 +48,7 @@ LOGGING_CONFIG = {
48
48
  "class": "logging.handlers.RotatingFileHandler",
49
49
  "filename": _setup_logfile(),
50
50
  "maxBytes": 1024**2 * 10,
51
- "backupCount": 3,
51
+ "backupCount": 10,
52
52
  "formatter": "standard",
53
53
  },
54
54
  },
memos/mem_cube/utils.py CHANGED
@@ -71,10 +71,6 @@ def merge_config_with_default(
71
71
 
72
72
  # Define graph_db fields to preserve (user-specific)
73
73
  preserve_graph_fields = {
74
- "uri",
75
- "user",
76
- "password",
77
- "db_name",
78
74
  "auto_create",
79
75
  "user_name",
80
76
  "use_multi_db",
@@ -96,9 +92,20 @@ def merge_config_with_default(
96
92
  merged_graph_config["db_name"] = default_graph_config.get("db_name")
97
93
  else:
98
94
  logger.info("use_multi_db is already False, no need to change")
99
-
95
+ if "neo4j" not in default_text_config["graph_db"]["backend"]:
96
+ if "db_name" in merged_graph_config:
97
+ merged_graph_config.pop("db_name")
98
+ logger.info("neo4j is not supported, remove db_name")
99
+ else:
100
+ logger.info("db_name is not in merged_graph_config, no need to remove")
101
+ else:
102
+ if "space" in merged_graph_config:
103
+ merged_graph_config.pop("space")
104
+ logger.info("neo4j is not supported, remove db_name")
105
+ else:
106
+ logger.info("space is not in merged_graph_config, no need to remove")
100
107
  preserved_graph_db = {
101
- "backend": existing_text_config["graph_db"]["backend"],
108
+ "backend": default_text_config["graph_db"]["backend"],
102
109
  "config": merged_graph_config,
103
110
  }
104
111
 
memos/mem_os/core.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import os
3
- import uuid
3
+ import time
4
4
 
5
5
  from datetime import datetime
6
6
  from pathlib import Path
@@ -13,16 +13,18 @@ from memos.log import get_logger
13
13
  from memos.mem_cube.general import GeneralMemCube
14
14
  from memos.mem_reader.factory import MemReaderFactory
15
15
  from memos.mem_scheduler.general_scheduler import GeneralScheduler
16
- from memos.mem_scheduler.modules.schemas import (
16
+ from memos.mem_scheduler.scheduler_factory import SchedulerFactory
17
+ from memos.mem_scheduler.schemas.general_schemas import (
17
18
  ADD_LABEL,
18
19
  ANSWER_LABEL,
19
- ScheduleMessageItem,
20
+ QUERY_LABEL,
20
21
  )
21
- from memos.mem_scheduler.scheduler_factory import SchedulerFactory
22
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
22
23
  from memos.mem_user.user_manager import UserManager, UserRole
23
24
  from memos.memories.activation.item import ActivationMemoryItem
24
25
  from memos.memories.parametric.item import ParametricMemoryItem
25
26
  from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
27
+ from memos.templates.mos_prompts import QUERY_REWRITING_PROMPT
26
28
  from memos.types import ChatHistory, MessageList, MOSSearchResult
27
29
 
28
30
 
@@ -58,10 +60,15 @@ class MOSCore:
58
60
  f"User '{self.user_id}' does not exist or is inactive. Please create user first."
59
61
  )
60
62
 
61
- # Lazy initialization marker
63
+ # Initialize mem_scheduler
62
64
  self._mem_scheduler_lock = Lock()
63
65
  self.enable_mem_scheduler = self.config.get("enable_mem_scheduler", False)
64
- self._mem_scheduler: GeneralScheduler = None
66
+ if self.enable_mem_scheduler:
67
+ self._mem_scheduler = self._initialize_mem_scheduler()
68
+ self._mem_scheduler.mem_cubes = self.mem_cubes
69
+ else:
70
+ self._mem_scheduler: GeneralScheduler = None
71
+
65
72
  logger.info(f"MOS initialized for user: {self.user_id}")
66
73
 
67
74
  @property
@@ -93,14 +100,16 @@ class MOSCore:
93
100
  else:
94
101
  logger.debug("Memory scheduler cleared")
95
102
 
96
- def _initialize_mem_scheduler(self):
103
+ def _initialize_mem_scheduler(self) -> GeneralScheduler:
97
104
  """Initialize the memory scheduler on first access."""
98
105
  if not self.config.enable_mem_scheduler:
99
106
  logger.debug("Memory scheduler is disabled in config")
100
107
  self._mem_scheduler = None
108
+ return self._mem_scheduler
101
109
  elif not hasattr(self.config, "mem_scheduler"):
102
110
  logger.error("Config of Memory scheduler is not available")
103
111
  self._mem_scheduler = None
112
+ return self._mem_scheduler
104
113
  else:
105
114
  logger.info("Initializing memory scheduler...")
106
115
  scheduler_config = self.config.mem_scheduler
@@ -111,13 +120,16 @@ class MOSCore:
111
120
  f"Memory reader of type {type(self.mem_reader).__name__} "
112
121
  "missing required 'llm' attribute"
113
122
  )
114
- self._mem_scheduler.initialize_modules(chat_llm=self.chat_llm)
123
+ self._mem_scheduler.initialize_modules(
124
+ chat_llm=self.chat_llm, process_llm=self.chat_llm
125
+ )
115
126
  else:
116
127
  # Configure scheduler modules
117
128
  self._mem_scheduler.initialize_modules(
118
129
  chat_llm=self.chat_llm, process_llm=self.mem_reader.llm
119
130
  )
120
131
  self._mem_scheduler.start()
132
+ return self._mem_scheduler
121
133
 
122
134
  def mem_scheduler_on(self) -> bool:
123
135
  if not self.config.enable_mem_scheduler or self._mem_scheduler is None:
@@ -157,6 +169,14 @@ class MOSCore:
157
169
  if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
158
170
  logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
159
171
  mem_cube.text_mem.memory_manager.close()
172
+ mem_cube.text_mem.memory_manager.wait_reorganizer()
173
+
174
+ def mem_reorganizer_wait(self) -> bool:
175
+ for mem_cube in self.mem_cubes.values():
176
+ logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
177
+ if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
178
+ logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
179
+ mem_cube.text_mem.memory_manager.wait_reorganizer()
160
180
 
161
181
  def _register_chat_history(self, user_id: str | None = None) -> None:
162
182
  """Initialize chat history with user ID."""
@@ -257,13 +277,21 @@ class MOSCore:
257
277
  user_id=target_user_id,
258
278
  mem_cube_id=mem_cube_id,
259
279
  mem_cube=mem_cube,
260
- label=ADD_LABEL,
280
+ label=QUERY_LABEL,
261
281
  content=query,
262
282
  timestamp=datetime.now(),
263
283
  )
264
284
  self.mem_scheduler.submit_messages(messages=[message_item])
265
285
 
266
- memories = mem_cube.text_mem.search(query, top_k=self.config.top_k)
286
+ memories = mem_cube.text_mem.search(
287
+ query,
288
+ top_k=self.config.top_k,
289
+ info={
290
+ "user_id": target_user_id,
291
+ "session_id": self.session_id,
292
+ "chat_history": chat_history.chat_history,
293
+ },
294
+ )
267
295
  memories_all.extend(memories)
268
296
  logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
269
297
  system_prompt = self._build_system_prompt(memories_all, base_prompt=base_prompt)
@@ -511,6 +539,8 @@ class MOSCore:
511
539
  user_id: str | None = None,
512
540
  install_cube_ids: list[str] | None = None,
513
541
  top_k: int | None = None,
542
+ mode: Literal["fast", "fine"] = "fast",
543
+ internet_search: bool = False,
514
544
  ) -> MOSSearchResult:
515
545
  """
516
546
  Search for textual memories across all registered MemCubes.
@@ -534,6 +564,10 @@ class MOSCore:
534
564
  logger.info(
535
565
  f"User {target_user_id} has access to {len(user_cube_ids)} cubes: {user_cube_ids}"
536
566
  )
567
+ if target_user_id not in self.chat_history_manager:
568
+ self._register_chat_history(target_user_id)
569
+ chat_history = self.chat_history_manager[target_user_id]
570
+
537
571
  result: MOSSearchResult = {
538
572
  "text_mem": [],
539
573
  "act_mem": [],
@@ -547,13 +581,26 @@ class MOSCore:
547
581
  and (mem_cube.text_mem is not None)
548
582
  and self.config.enable_textual_memory
549
583
  ):
584
+ time_start = time.time()
550
585
  memories = mem_cube.text_mem.search(
551
- query, top_k=top_k if top_k else self.config.top_k
586
+ query,
587
+ top_k=top_k if top_k else self.config.top_k,
588
+ mode=mode,
589
+ manual_close_internet=not internet_search,
590
+ info={
591
+ "user_id": target_user_id,
592
+ "session_id": self.session_id,
593
+ "chat_history": chat_history.chat_history,
594
+ },
552
595
  )
553
596
  result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
554
597
  logger.info(
555
598
  f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
556
599
  )
600
+ search_time_end = time.time()
601
+ logger.info(
602
+ f"time search graph: search graph time user_id: {target_user_id} time is: {search_time_end - time_start}"
603
+ )
557
604
  return result
558
605
 
559
606
  def add(
@@ -576,6 +623,7 @@ class MOSCore:
576
623
  user_id (str, optional): The identifier of the user to add the memories to.
577
624
  If None, the default user is used.
578
625
  """
626
+ # user input messages
579
627
  assert (messages is not None) or (memory_content is not None) or (doc_path is not None), (
580
628
  "messages_or_doc_path or memory_content or doc_path must be provided."
581
629
  )
@@ -613,25 +661,31 @@ class MOSCore:
613
661
  memories = self.mem_reader.get_memory(
614
662
  messages_list,
615
663
  type="chat",
616
- info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
664
+ info={"user_id": target_user_id, "session_id": self.session_id},
617
665
  )
666
+
667
+ mem_ids = []
618
668
  for mem in memories:
619
- self.mem_cubes[mem_cube_id].text_mem.add(mem)
669
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
670
+ mem_ids.extend(mem_id_list)
671
+ logger.info(
672
+ f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
673
+ )
620
674
 
621
675
  # submit messages for scheduler
622
- mem_cube = self.mem_cubes[mem_cube_id]
623
676
  if self.enable_mem_scheduler and self.mem_scheduler is not None:
624
- text_messages = [message["content"] for message in messages]
677
+ mem_cube = self.mem_cubes[mem_cube_id]
625
678
  message_item = ScheduleMessageItem(
626
679
  user_id=target_user_id,
627
680
  mem_cube_id=mem_cube_id,
628
681
  mem_cube=mem_cube,
629
682
  label=ADD_LABEL,
630
- content=json.dumps(text_messages),
683
+ content=json.dumps(mem_ids),
631
684
  timestamp=datetime.now(),
632
685
  )
633
686
  self.mem_scheduler.submit_messages(messages=[message_item])
634
687
 
688
+ # user profile
635
689
  if (
636
690
  (memory_content is not None)
637
691
  and self.config.enable_textual_memory
@@ -646,34 +700,66 @@ class MOSCore:
646
700
  )
647
701
  else:
648
702
  messages_list = [
649
- [
650
- {"role": "user", "content": memory_content},
651
- {
652
- "role": "assistant",
653
- "content": "",
654
- }, # add by str to keep the format,assistant role is empty
655
- ]
656
- ]
703
+ [{"role": "user", "content": memory_content}]
704
+ ] # for only user-str input and convert message
657
705
  memories = self.mem_reader.get_memory(
658
706
  messages_list,
659
707
  type="chat",
660
- info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
708
+ info={"user_id": target_user_id, "session_id": self.session_id},
661
709
  )
710
+
711
+ mem_ids = []
662
712
  for mem in memories:
663
- self.mem_cubes[mem_cube_id].text_mem.add(mem)
713
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
714
+ logger.info(
715
+ f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
716
+ )
717
+ mem_ids.extend(mem_id_list)
718
+
719
+ # submit messages for scheduler
720
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
721
+ mem_cube = self.mem_cubes[mem_cube_id]
722
+ message_item = ScheduleMessageItem(
723
+ user_id=target_user_id,
724
+ mem_cube_id=mem_cube_id,
725
+ mem_cube=mem_cube,
726
+ label=ADD_LABEL,
727
+ content=json.dumps(mem_ids),
728
+ timestamp=datetime.now(),
729
+ )
730
+ self.mem_scheduler.submit_messages(messages=[message_item])
731
+
732
+ # user doc input
664
733
  if (
665
734
  (doc_path is not None)
666
735
  and self.config.enable_textual_memory
667
736
  and self.mem_cubes[mem_cube_id].text_mem
668
737
  ):
669
738
  documents = self._get_all_documents(doc_path)
670
- doc_memory = self.mem_reader.get_memory(
739
+ doc_memories = self.mem_reader.get_memory(
671
740
  documents,
672
741
  type="doc",
673
- info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
742
+ info={"user_id": target_user_id, "session_id": self.session_id},
674
743
  )
675
- for mem in doc_memory:
676
- self.mem_cubes[mem_cube_id].text_mem.add(mem)
744
+
745
+ mem_ids = []
746
+ for mem in doc_memories:
747
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
748
+ mem_ids.extend(mem_id_list)
749
+
750
+ # submit messages for scheduler
751
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
752
+ mem_cube = self.mem_cubes[mem_cube_id]
753
+ message_item = ScheduleMessageItem(
754
+ user_id=target_user_id,
755
+ mem_cube_id=mem_cube_id,
756
+ mem_cube=mem_cube,
757
+ label=ADD_LABEL,
758
+ content=json.dumps(mem_ids),
759
+ timestamp=datetime.now(),
760
+ )
761
+ self.mem_scheduler.submit_messages(messages=[message_item])
762
+
677
763
  logger.info(f"Add memory to {mem_cube_id} successfully")
678
764
 
679
765
  def get(
@@ -907,3 +993,27 @@ class MOSCore:
907
993
  raise ValueError(f"Target user '{target_user_id}' does not exist or is inactive.")
908
994
 
909
995
  return self.user_manager.add_user_to_cube(target_user_id, cube_id)
996
+
997
+ def get_query_rewrite(self, query: str, user_id: str | None = None):
998
+ """
999
+ Rewrite user's query according the context.
1000
+ Args:
1001
+ query (str): The search query that needs rewriting.
1002
+ user_id(str, optional): The identifier of the user that the query belongs to.
1003
+ If None, the default user is used.
1004
+
1005
+ Returns:
1006
+ str: query after rewriting process.
1007
+ """
1008
+ target_user_id = user_id if user_id is not None else self.user_id
1009
+ chat_history = self.chat_history_manager[target_user_id]
1010
+
1011
+ dialogue = "————{}".format("\n————".join(chat_history.chat_history))
1012
+ user_prompt = QUERY_REWRITING_PROMPT.format(dialogue=dialogue, query=query)
1013
+ messages = {"role": "user", "content": user_prompt}
1014
+ rewritten_result = self.chat_llm.generate(messages=messages)
1015
+ rewritten_result = json.loads(rewritten_result)
1016
+ if rewritten_result.get("former_dialogue_related", False):
1017
+ rewritten_query = rewritten_result.get("rewritten_question")
1018
+ return rewritten_query if len(rewritten_query) > 0 else query
1019
+ return query
memos/mem_os/main.py CHANGED
@@ -208,7 +208,7 @@ class MOS(MOSCore):
208
208
  if self.enable_mem_scheduler and self.mem_scheduler is not None:
209
209
  from datetime import datetime
210
210
 
211
- from memos.mem_scheduler.modules.schemas import (
211
+ from memos.mem_scheduler.schemas import (
212
212
  ANSWER_LABEL,
213
213
  ScheduleMessageItem,
214
214
  )