MemoryOS 1.0.1__py3-none-any.whl → 1.1.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 (82) hide show
  1. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/METADATA +7 -2
  2. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/RECORD +79 -65
  3. {memoryos-1.0.1.dist-info → memoryos-1.1.1.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.1.dist-info}/entry_points.txt +0 -0
  80. {memoryos-1.0.1.dist-info → memoryos-1.1.1.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/mem_os/product.py CHANGED
@@ -2,7 +2,6 @@ import asyncio
2
2
  import json
3
3
  import os
4
4
  import random
5
- import threading
6
5
  import time
7
6
 
8
7
  from collections.abc import Generator
@@ -14,6 +13,7 @@ from transformers import AutoTokenizer
14
13
 
15
14
  from memos.configs.mem_cube import GeneralMemCubeConfig
16
15
  from memos.configs.mem_os import MOSConfig
16
+ from memos.context.context import ContextThread
17
17
  from memos.log import get_logger
18
18
  from memos.mem_cube.general import GeneralMemCube
19
19
  from memos.mem_os.core import MOSCore
@@ -46,6 +46,7 @@ from memos.templates.mos_prompts import (
46
46
  get_memos_prompt,
47
47
  )
48
48
  from memos.types import MessageList
49
+ from memos.utils import timed
49
50
 
50
51
 
51
52
  logger = get_logger(__name__)
@@ -257,6 +258,7 @@ class MOSProduct(MOSCore):
257
258
  except Exception as e:
258
259
  logger.error(f"Error pre-loading cubes for user {user_id}: {e}", exc_info=True)
259
260
 
261
+ @timed
260
262
  def _load_user_cubes(
261
263
  self, user_id: str, default_cube_config: GeneralMemCubeConfig | None = None
262
264
  ) -> None:
@@ -288,6 +290,7 @@ class MOSProduct(MOSCore):
288
290
  )
289
291
  except Exception as e:
290
292
  logger.error(f"Failed to load cube {cube.cube_id} for user {user_id}: {e}")
293
+ logger.info(f"load user {user_id} cubes successfully")
291
294
 
292
295
  def _ensure_user_instance(self, user_id: str, max_instances: int | None = None) -> None:
293
296
  """
@@ -694,8 +697,8 @@ class MOSProduct(MOSCore):
694
697
  else None
695
698
  )
696
699
  except RuntimeError:
697
- # No event loop, run in a new thread
698
- thread = threading.Thread(
700
+ # No event loop, run in a new thread with context propagation
701
+ thread = ContextThread(
699
702
  target=run_async_in_thread,
700
703
  name=f"PostChatProcessing-{user_id}",
701
704
  # Set as a daemon thread to avoid blocking program exit
@@ -775,10 +778,14 @@ class MOSProduct(MOSCore):
775
778
  return
776
779
 
777
780
  # Create MemCube from path
781
+ time_start = time.time()
778
782
  if os.path.exists(mem_cube_name_or_path):
779
783
  mem_cube = GeneralMemCube.init_from_dir(
780
784
  mem_cube_name_or_path, memory_types, default_config
781
785
  )
786
+ logger.info(
787
+ f"time register_mem_cube: init_from_dir time is: {time.time() - time_start}"
788
+ )
782
789
  else:
783
790
  logger.warning(
784
791
  f"MemCube {mem_cube_name_or_path} does not exist, try to init from remote repo."
@@ -791,7 +798,10 @@ class MOSProduct(MOSCore):
791
798
  logger.info(
792
799
  f"Registering MemCube {mem_cube_id} with cube config {mem_cube.config.model_dump(mode='json')}"
793
800
  )
801
+ time_start = time.time()
794
802
  self.mem_cubes[mem_cube_id] = mem_cube
803
+ time_end = time.time()
804
+ logger.info(f"time register_mem_cube: add mem_cube time is: {time_end - time_start}")
795
805
 
796
806
  def user_register(
797
807
  self,
@@ -801,6 +811,7 @@ class MOSProduct(MOSCore):
801
811
  interests: str | None = None,
802
812
  default_mem_cube: GeneralMemCube | None = None,
803
813
  default_cube_config: GeneralMemCubeConfig | None = None,
814
+ mem_cube_id: str | None = None,
804
815
  ) -> dict[str, str]:
805
816
  """Register a new user with configuration and default cube.
806
817
 
@@ -836,15 +847,19 @@ class MOSProduct(MOSCore):
836
847
  default_cube_name = f"{user_name}_{user_id}_default_cube"
837
848
  mem_cube_name_or_path = os.path.join(CUBE_PATH, default_cube_name)
838
849
  default_cube_id = self.create_cube_for_user(
839
- cube_name=default_cube_name, owner_id=user_id, cube_path=mem_cube_name_or_path
850
+ cube_name=default_cube_name,
851
+ owner_id=user_id,
852
+ cube_path=mem_cube_name_or_path,
853
+ cube_id=mem_cube_id,
840
854
  )
841
-
855
+ time_start = time.time()
842
856
  if default_mem_cube:
843
857
  try:
844
- default_mem_cube.dump(mem_cube_name_or_path)
858
+ default_mem_cube.dump(mem_cube_name_or_path, memory_types=[])
845
859
  except Exception as e:
846
860
  logger.error(f"Failed to dump default cube: {e}")
847
-
861
+ time_end = time.time()
862
+ logger.info(f"time user_register: dump default cube time is: {time_end - time_start}")
848
863
  # Register the default cube with MOS
849
864
  self.register_mem_cube(
850
865
  mem_cube_name_or_path_or_object=default_mem_cube,
@@ -924,6 +939,7 @@ class MOSProduct(MOSCore):
924
939
  moscube: bool = False,
925
940
  top_k: int = 10,
926
941
  threshold: float = 0.5,
942
+ session_id: str | None = None,
927
943
  ) -> str:
928
944
  """
929
945
  Chat with LLM with memory references and complete response.
@@ -938,6 +954,7 @@ class MOSProduct(MOSCore):
938
954
  mode="fine",
939
955
  internet_search=internet_search,
940
956
  moscube=moscube,
957
+ session_id=session_id,
941
958
  )["text_mem"]
942
959
 
943
960
  memories_list = []
@@ -982,6 +999,7 @@ class MOSProduct(MOSCore):
982
999
  top_k: int = 20,
983
1000
  internet_search: bool = False,
984
1001
  moscube: bool = False,
1002
+ session_id: str | None = None,
985
1003
  ) -> Generator[str, None, None]:
986
1004
  """
987
1005
  Chat with LLM with memory references and streaming output.
@@ -1008,6 +1026,7 @@ class MOSProduct(MOSCore):
1008
1026
  mode="fine",
1009
1027
  internet_search=internet_search,
1010
1028
  moscube=moscube,
1029
+ session_id=session_id,
1011
1030
  )["text_mem"]
1012
1031
 
1013
1032
  yield f"data: {json.dumps({'type': 'status', 'data': '1'})}\n\n"
@@ -1028,7 +1047,7 @@ class MOSProduct(MOSCore):
1028
1047
  system_prompt = self._build_enhance_system_prompt(user_id, memories_list)
1029
1048
  # Get chat history
1030
1049
  if user_id not in self.chat_history_manager:
1031
- self._register_chat_history(user_id)
1050
+ self._register_chat_history(user_id, session_id)
1032
1051
 
1033
1052
  chat_history = self.chat_history_manager[user_id]
1034
1053
  if history:
@@ -1296,6 +1315,7 @@ class MOSProduct(MOSCore):
1296
1315
  install_cube_ids: list[str] | None = None,
1297
1316
  top_k: int = 10,
1298
1317
  mode: Literal["fast", "fine"] = "fast",
1318
+ session_id: str | None = None,
1299
1319
  ):
1300
1320
  """Search memories for a specific user."""
1301
1321
 
@@ -1306,7 +1326,9 @@ class MOSProduct(MOSCore):
1306
1326
  logger.info(
1307
1327
  f"time search: load_user_cubes time user_id: {user_id} time is: {load_user_cubes_time_end - time_start}"
1308
1328
  )
1309
- search_result = super().search(query, user_id, install_cube_ids, top_k, mode=mode)
1329
+ search_result = super().search(
1330
+ query, user_id, install_cube_ids, top_k, mode=mode, session_id=session_id
1331
+ )
1310
1332
  search_time_end = time.time()
1311
1333
  logger.info(
1312
1334
  f"time search: search text_mem time user_id: {user_id} time is: {search_time_end - load_user_cubes_time_end}"
@@ -1342,13 +1364,15 @@ class MOSProduct(MOSCore):
1342
1364
  mem_cube_id: str | None = None,
1343
1365
  source: str | None = None,
1344
1366
  user_profile: bool = False,
1367
+ session_id: str | None = None,
1345
1368
  ):
1346
1369
  """Add memory for a specific user."""
1347
1370
 
1348
1371
  # Load user cubes if not already loaded
1349
1372
  self._load_user_cubes(user_id, self.default_cube_config)
1350
-
1351
- result = super().add(messages, memory_content, doc_path, mem_cube_id, user_id)
1373
+ result = super().add(
1374
+ messages, memory_content, doc_path, mem_cube_id, user_id, session_id=session_id
1375
+ )
1352
1376
  if user_profile:
1353
1377
  try:
1354
1378
  user_interests = memory_content.split("'userInterests': '")[1].split("', '")[0]
@@ -3,6 +3,7 @@ from typing import Any, ClassVar
3
3
  from memos.configs.mem_reader import MemReaderConfigFactory
4
4
  from memos.mem_reader.base import BaseMemReader
5
5
  from memos.mem_reader.simple_struct import SimpleStructMemReader
6
+ from memos.memos_tools.singleton import singleton_factory
6
7
 
7
8
 
8
9
  class MemReaderFactory(BaseMemReader):
@@ -13,6 +14,7 @@ class MemReaderFactory(BaseMemReader):
13
14
  }
14
15
 
15
16
  @classmethod
17
+ @singleton_factory()
16
18
  def from_config(cls, config_factory: MemReaderConfigFactory) -> BaseMemReader:
17
19
  backend = config_factory.backend
18
20
  if backend not in cls.backend_to_class:
@@ -13,6 +13,7 @@ from memos import log
13
13
  from memos.chunkers import ChunkerFactory
14
14
  from memos.configs.mem_reader import SimpleStructMemReaderConfig
15
15
  from memos.configs.parser import ParserConfigFactory
16
+ from memos.context.context import ContextThreadPoolExecutor
16
17
  from memos.embedders.factory import EmbedderFactory
17
18
  from memos.llms.factory import LLMFactory
18
19
  from memos.mem_reader.base import BaseMemReader
@@ -26,6 +27,7 @@ from memos.templates.mem_reader_prompts import (
26
27
  SIMPLE_STRUCT_MEM_READER_PROMPT,
27
28
  SIMPLE_STRUCT_MEM_READER_PROMPT_ZH,
28
29
  )
30
+ from memos.utils import timed
29
31
 
30
32
 
31
33
  logger = log.get_logger(__name__)
@@ -55,45 +57,60 @@ def detect_lang(text):
55
57
 
56
58
  def _build_node(idx, message, info, scene_file, llm, parse_json_result, embedder):
57
59
  # generate
58
- raw = llm.generate(message)
59
- if not raw:
60
+ try:
61
+ raw = llm.generate(message)
62
+ if not raw:
63
+ logger.warning(f"[LLM] Empty generation for input: {message}")
64
+ return None
65
+ except Exception as e:
66
+ logger.error(f"[LLM] Exception during generation: {e}")
60
67
  return None
61
68
 
62
69
  # parse_json_result
63
- chunk_res = parse_json_result(raw)
64
- if not chunk_res:
70
+ try:
71
+ chunk_res = parse_json_result(raw)
72
+ if not chunk_res:
73
+ logger.warning(f"[Parse] Failed to parse result: {raw}")
74
+ return None
75
+ except Exception as e:
76
+ logger.error(f"[Parse] Exception during JSON parsing: {e}")
65
77
  return None
66
78
 
67
- value = chunk_res.get("value")
68
- if not value:
79
+ try:
80
+ value = chunk_res.get("value", "").strip()
81
+ if not value:
82
+ logger.warning("[BuildNode] value is empty")
83
+ return None
84
+
85
+ tags = chunk_res.get("tags", [])
86
+ if not isinstance(tags, list):
87
+ tags = []
88
+
89
+ key = chunk_res.get("key", None)
90
+
91
+ embedding = embedder.embed([value])[0]
92
+
93
+ return TextualMemoryItem(
94
+ memory=value,
95
+ metadata=TreeNodeTextualMemoryMetadata(
96
+ user_id=info.get("user_id", ""),
97
+ session_id=info.get("session_id", ""),
98
+ memory_type="LongTermMemory",
99
+ status="activated",
100
+ tags=tags,
101
+ key=key,
102
+ embedding=embedding,
103
+ usage=[],
104
+ sources=[{"type": "doc", "doc_path": f"{scene_file}_{idx}"}],
105
+ background="",
106
+ confidence=0.99,
107
+ type="fact",
108
+ ),
109
+ )
110
+ except Exception as e:
111
+ logger.error(f"[BuildNode] Error building node: {e}")
69
112
  return None
70
113
 
71
- # embed
72
- embedding = embedder.embed([value])[0]
73
-
74
- # TextualMemoryItem
75
- tags = chunk_res["tags"] if isinstance(chunk_res.get("tags"), list) else []
76
- key = chunk_res.get("key", None)
77
-
78
- node_i = TextualMemoryItem(
79
- memory=value,
80
- metadata=TreeNodeTextualMemoryMetadata(
81
- user_id=info.get("user_id"),
82
- session_id=info.get("session_id"),
83
- memory_type="LongTermMemory",
84
- status="activated",
85
- tags=tags,
86
- key=key,
87
- embedding=embedding,
88
- usage=[],
89
- sources=[f"{scene_file}_{idx}"],
90
- background="",
91
- confidence=0.99,
92
- type="fact",
93
- ),
94
- )
95
- return node_i
96
-
97
114
 
98
115
  class SimpleStructMemReader(BaseMemReader, ABC):
99
116
  """Naive implementation of MemReader."""
@@ -110,44 +127,77 @@ class SimpleStructMemReader(BaseMemReader, ABC):
110
127
  self.embedder = EmbedderFactory.from_config(config.embedder)
111
128
  self.chunker = ChunkerFactory.from_config(config.chunker)
112
129
 
130
+ @timed
113
131
  def _process_chat_data(self, scene_data_info, info):
114
- lang = detect_lang("\n".join(scene_data_info))
132
+ mem_list = []
133
+ for item in scene_data_info:
134
+ if "chat_time" in item:
135
+ mem = item["role"] + ": " + f"[{item['chat_time']}]: " + item["content"]
136
+ mem_list.append(mem)
137
+ else:
138
+ mem = item["role"] + ":" + item["content"]
139
+ mem_list.append(mem)
140
+ lang = detect_lang("\n".join(mem_list))
115
141
  template = PROMPT_DICT["chat"][lang]
116
142
  examples = PROMPT_DICT["chat"][f"{lang}_example"]
117
143
 
118
- prompt = template.replace("${conversation}", "\n".join(scene_data_info))
144
+ prompt = template.replace("${conversation}", "\n".join(mem_list))
119
145
  if self.config.remove_prompt_example:
120
146
  prompt = prompt.replace(examples, "")
121
147
 
122
148
  messages = [{"role": "user", "content": prompt}]
123
149
 
124
- response_text = self.llm.generate(messages)
125
- response_json = self.parse_json_result(response_text)
150
+ try:
151
+ response_text = self.llm.generate(messages)
152
+ response_json = self.parse_json_result(response_text)
153
+ except Exception as e:
154
+ logger.error(f"[LLM] Exception during chat generation: {e}")
155
+ response_json = {
156
+ "memory list": [
157
+ {
158
+ "key": "\n".join(mem_list)[:10],
159
+ "memory_type": "UserMemory",
160
+ "value": "\n".join(mem_list),
161
+ "tags": [],
162
+ }
163
+ ],
164
+ "summary": "\n".join(mem_list),
165
+ }
126
166
 
127
167
  chat_read_nodes = []
128
168
  for memory_i_raw in response_json.get("memory list", []):
129
- node_i = TextualMemoryItem(
130
- memory=memory_i_raw.get("value", ""),
131
- metadata=TreeNodeTextualMemoryMetadata(
132
- user_id=info.get("user_id"),
133
- session_id=info.get("session_id"),
134
- memory_type=memory_i_raw.get("memory_type", "")
169
+ try:
170
+ memory_type = (
171
+ memory_i_raw.get("memory_type", "LongTermMemory")
135
172
  .replace("长期记忆", "LongTermMemory")
136
- .replace("用户记忆", "UserMemory"),
137
- status="activated",
138
- tags=memory_i_raw.get("tags", [])
139
- if type(memory_i_raw.get("tags", [])) is list
140
- else [],
141
- key=memory_i_raw.get("key", ""),
142
- embedding=self.embedder.embed([memory_i_raw.get("value", "")])[0],
143
- usage=[],
144
- sources=scene_data_info,
145
- background=response_json.get("summary", ""),
146
- confidence=0.99,
147
- type="fact",
148
- ),
149
- )
150
- chat_read_nodes.append(node_i)
173
+ .replace("用户记忆", "UserMemory")
174
+ )
175
+
176
+ if memory_type not in ["LongTermMemory", "UserMemory"]:
177
+ memory_type = "LongTermMemory"
178
+
179
+ node_i = TextualMemoryItem(
180
+ memory=memory_i_raw.get("value", ""),
181
+ metadata=TreeNodeTextualMemoryMetadata(
182
+ user_id=info.get("user_id"),
183
+ session_id=info.get("session_id"),
184
+ memory_type=memory_type,
185
+ status="activated",
186
+ tags=memory_i_raw.get("tags", [])
187
+ if type(memory_i_raw.get("tags", [])) is list
188
+ else [],
189
+ key=memory_i_raw.get("key", ""),
190
+ embedding=self.embedder.embed([memory_i_raw.get("value", "")])[0],
191
+ usage=[],
192
+ sources=scene_data_info,
193
+ background=response_json.get("summary", ""),
194
+ confidence=0.99,
195
+ type="fact",
196
+ ),
197
+ )
198
+ chat_read_nodes.append(node_i)
199
+ except Exception as e:
200
+ logger.error(f"[ChatReader] Error parsing memory item: {e}")
151
201
 
152
202
  return chat_read_nodes
153
203
 
@@ -200,8 +250,8 @@ class SimpleStructMemReader(BaseMemReader, ABC):
200
250
  else:
201
251
  processing_func = self._process_doc_data
202
252
 
203
- # Process Q&A pairs concurrently
204
- with concurrent.futures.ThreadPoolExecutor() as executor:
253
+ # Process Q&A pairs concurrently with context propagation
254
+ with ContextThreadPoolExecutor() as executor:
205
255
  futures = [
206
256
  executor.submit(processing_func, scene_data_info, info)
207
257
  for scene_data_info in list_scene_data_info
@@ -239,11 +289,9 @@ class SimpleStructMemReader(BaseMemReader, ABC):
239
289
  for item in items:
240
290
  # Convert dictionary to string
241
291
  if "chat_time" in item:
242
- mem = item["role"] + ": " + f"[{item['chat_time']}]: " + item["content"]
243
- result.append(mem)
292
+ result.append(item)
244
293
  else:
245
- mem = item["role"] + ":" + item["content"]
246
- result.append(mem)
294
+ result.append(item)
247
295
  if len(result) >= 10:
248
296
  results.append(result)
249
297
  context = copy.deepcopy(result[-2:])
@@ -254,17 +302,21 @@ class SimpleStructMemReader(BaseMemReader, ABC):
254
302
  for item in scene_data:
255
303
  try:
256
304
  if os.path.exists(item):
257
- parsed_text = parser.parse(item)
258
- results.append({"file": "pure_text", "text": parsed_text})
305
+ try:
306
+ parsed_text = parser.parse(item)
307
+ results.append({"file": item, "text": parsed_text})
308
+ except Exception as e:
309
+ logger.error(f"[SceneParser] Error parsing {item}: {e}")
310
+ continue
259
311
  else:
260
312
  parsed_text = item
261
- results.append({"file": item, "text": parsed_text})
313
+ results.append({"file": "pure_text", "text": parsed_text})
262
314
  except Exception as e:
263
315
  print(f"Error parsing file {item}: {e!s}")
264
316
 
265
317
  return results
266
318
 
267
- def _process_doc_data(self, scene_data_info, info):
319
+ def _process_doc_data(self, scene_data_info, info, **kwargs):
268
320
  chunks = self.chunker.chunk(scene_data_info["text"])
269
321
  messages = []
270
322
  for chunk in chunks:
@@ -277,7 +329,7 @@ class SimpleStructMemReader(BaseMemReader, ABC):
277
329
  doc_nodes = []
278
330
  scene_file = scene_data_info["file"]
279
331
 
280
- with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
332
+ with ContextThreadPoolExecutor(max_workers=50) as executor:
281
333
  futures = {
282
334
  executor.submit(
283
335
  _build_node,
@@ -302,6 +354,7 @@ class SimpleStructMemReader(BaseMemReader, ABC):
302
354
  doc_nodes.append(node)
303
355
  except Exception as e:
304
356
  tqdm.write(f"[ERROR] {e}")
357
+ logger.error(f"[DocReader] Future task failed: {e}")
305
358
  return doc_nodes
306
359
 
307
360
  def parse_json_result(self, response_text):
@@ -309,14 +362,14 @@ class SimpleStructMemReader(BaseMemReader, ABC):
309
362
  json_start = response_text.find("{")
310
363
  response_text = response_text[json_start:]
311
364
  response_text = response_text.replace("```", "").strip()
312
- if response_text[-1] != "}":
365
+ if not response_text.endswith("}"):
313
366
  response_text += "}"
314
- response_json = json.loads(response_text)
315
- return response_json
367
+ return json.loads(response_text)
316
368
  except json.JSONDecodeError as e:
317
- logger.warning(
318
- f"Failed to parse LLM response as JSON: {e}\nRaw response:\n{response_text}"
319
- )
369
+ logger.error(f"[JSONParse] Failed to decode JSON: {e}\nRaw:\n{response_text}")
370
+ return {}
371
+ except Exception as e:
372
+ logger.error(f"[JSONParse] Unexpected error: {e}")
320
373
  return {}
321
374
 
322
375
  def transform_memreader(self, data: dict) -> list[TextualMemoryItem]:
File without changes