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.
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/METADATA +7 -2
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/RECORD +79 -65
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/WHEEL +1 -1
- memos/__init__.py +1 -1
- memos/api/client.py +109 -0
- memos/api/config.py +11 -9
- memos/api/context/dependencies.py +15 -55
- memos/api/middleware/request_context.py +9 -40
- memos/api/product_api.py +2 -3
- memos/api/product_models.py +91 -16
- memos/api/routers/product_router.py +23 -16
- memos/api/start_api.py +10 -0
- memos/configs/graph_db.py +4 -0
- memos/configs/mem_scheduler.py +38 -3
- memos/context/context.py +255 -0
- memos/embedders/factory.py +2 -0
- memos/graph_dbs/nebular.py +230 -232
- memos/graph_dbs/neo4j.py +35 -1
- memos/graph_dbs/neo4j_community.py +7 -0
- memos/llms/factory.py +2 -0
- memos/llms/openai.py +74 -2
- memos/log.py +27 -15
- memos/mem_cube/general.py +3 -1
- memos/mem_os/core.py +60 -22
- memos/mem_os/main.py +3 -6
- memos/mem_os/product.py +35 -11
- memos/mem_reader/factory.py +2 -0
- memos/mem_reader/simple_struct.py +127 -74
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +569 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +126 -56
- memos/mem_scheduler/general_modules/dispatcher.py +2 -2
- memos/mem_scheduler/general_modules/misc.py +99 -1
- memos/mem_scheduler/general_modules/scheduler_logger.py +17 -11
- memos/mem_scheduler/general_scheduler.py +40 -88
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +308 -0
- memos/mem_scheduler/{general_modules → memory_manage_modules}/retriever.py +34 -7
- memos/mem_scheduler/monitors/dispatcher_monitor.py +9 -8
- memos/mem_scheduler/monitors/general_monitor.py +119 -39
- memos/mem_scheduler/optimized_scheduler.py +124 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/base_model.py +635 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/scheduler_factory.py +2 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +96 -29
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +33 -0
- memos/mem_scheduler/utils/filter_utils.py +1 -1
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/memories/activation/kv.py +2 -1
- memos/memories/textual/item.py +95 -16
- memos/memories/textual/naive.py +1 -1
- memos/memories/textual/tree.py +27 -3
- memos/memories/textual/tree_text_memory/organize/handler.py +4 -2
- memos/memories/textual/tree_text_memory/organize/manager.py +28 -14
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +1 -2
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +75 -23
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +7 -5
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -2
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +70 -22
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +101 -33
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +5 -4
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +22 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/parsers/factory.py +2 -0
- memos/reranker/concat.py +59 -0
- memos/reranker/cosine_local.py +1 -0
- memos/reranker/factory.py +5 -0
- memos/reranker/http_bge.py +225 -12
- memos/templates/mem_scheduler_prompts.py +242 -0
- memos/types.py +4 -1
- memos/api/context/context.py +0 -147
- memos/api/context/context_thread.py +0 -96
- memos/mem_scheduler/mos_for_test_scheduler.py +0 -146
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/entry_points.txt +0 -0
- {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info/licenses}/LICENSE +0 -0
- /memos/mem_scheduler/{general_modules → webservice_modules}/rabbitmq_service.py +0 -0
- /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 =
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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]
|
memos/mem_reader/factory.py
CHANGED
|
@@ -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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
|
-
|
|
243
|
-
result.append(mem)
|
|
292
|
+
result.append(item)
|
|
244
293
|
else:
|
|
245
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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":
|
|
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
|
|
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
|
|
365
|
+
if not response_text.endswith("}"):
|
|
313
366
|
response_text += "}"
|
|
314
|
-
|
|
315
|
-
return response_json
|
|
367
|
+
return json.loads(response_text)
|
|
316
368
|
except json.JSONDecodeError as e:
|
|
317
|
-
logger.
|
|
318
|
-
|
|
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
|