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/parsers/factory.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from typing import Any, ClassVar
2
2
 
3
3
  from memos.configs.parser import ParserConfigFactory
4
+ from memos.memos_tools.singleton import singleton_factory
4
5
  from memos.parsers.base import BaseParser
5
6
  from memos.parsers.markitdown import MarkItDownParser
6
7
 
@@ -11,6 +12,7 @@ class ParserFactory(BaseParser):
11
12
  backend_to_class: ClassVar[dict[str, Any]] = {"markitdown": MarkItDownParser}
12
13
 
13
14
  @classmethod
15
+ @singleton_factory()
14
16
  def from_config(cls, config_factory: ParserConfigFactory) -> BaseParser:
15
17
  backend = config_factory.backend
16
18
  if backend not in cls.backend_to_class:
@@ -0,0 +1,59 @@
1
+ import re
2
+
3
+ from typing import Any
4
+
5
+
6
+ _TAG1 = re.compile(r"^\s*\[[^\]]*\]\s*")
7
+
8
+
9
+ def process_source(
10
+ items: list[tuple[Any, str | dict[str, Any] | list[Any]]] | None = None, recent_num: int = 3
11
+ ) -> str:
12
+ """
13
+ Args:
14
+ items: List of tuples where each tuple contains (memory, source).
15
+ source can be str, Dict, or List.
16
+ recent_num: Number of recent items to concatenate.
17
+ Returns:
18
+ str: Concatenated source.
19
+ """
20
+ if items is None:
21
+ items = []
22
+ concat_data = []
23
+ memory = None
24
+ for item in items:
25
+ memory, source = item
26
+ for content in source:
27
+ if isinstance(content, str):
28
+ if "assistant:" in content:
29
+ continue
30
+ concat_data.append(content)
31
+ if memory is not None:
32
+ concat_data = [memory, *concat_data]
33
+ return "\n".join(concat_data)
34
+
35
+
36
+ def concat_original_source(
37
+ graph_results: list,
38
+ merge_field: list[str] | None = None,
39
+ ) -> list[str]:
40
+ """
41
+ Merge memory items with original dialogue.
42
+ Args:
43
+ graph_results (list[TextualMemoryItem]): List of memory items with embeddings.
44
+ merge_field (List[str]): List of fields to merge.
45
+ Returns:
46
+ list[str]: List of memory and concat orginal memory.
47
+ """
48
+ if merge_field is None:
49
+ merge_field = ["sources"]
50
+ documents = []
51
+ for item in graph_results:
52
+ memory = _TAG1.sub("", m) if isinstance((m := getattr(item, "memory", None)), str) else m
53
+ sources = []
54
+ for field in merge_field:
55
+ source = getattr(item.metadata, field, "")
56
+ sources.append((memory, source))
57
+ concat_string = process_source(sources)
58
+ documents.append(concat_string)
59
+ return documents
@@ -49,6 +49,7 @@ class CosineLocalReranker(BaseReranker):
49
49
  self,
50
50
  level_weights: dict[str, float] | None = None,
51
51
  level_field: str = "background",
52
+ **kwargs,
52
53
  ):
53
54
  self.level_weights = level_weights or {"topic": 1.0, "concept": 1.0, "fact": 1.0}
54
55
  self.level_field = level_field
memos/reranker/factory.py CHANGED
@@ -3,6 +3,9 @@ from __future__ import annotations
3
3
 
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
+ # Import singleton decorator
7
+ from memos.memos_tools.singleton import singleton_factory
8
+
6
9
  from .cosine_local import CosineLocalReranker
7
10
  from .http_bge import HTTPBGEReranker
8
11
  from .noop import NoopReranker
@@ -16,6 +19,7 @@ if TYPE_CHECKING:
16
19
 
17
20
  class RerankerFactory:
18
21
  @staticmethod
22
+ @singleton_factory("RerankerFactory")
19
23
  def from_config(cfg: RerankerConfigFactory | None) -> BaseReranker | None:
20
24
  if not cfg:
21
25
  return None
@@ -29,6 +33,7 @@ class RerankerFactory:
29
33
  model=c.get("model", "bge-reranker-v2-m3"),
30
34
  timeout=int(c.get("timeout", 10)),
31
35
  headers_extra=c.get("headers_extra"),
36
+ rerank_source=c.get("rerank_source"),
32
37
  )
33
38
 
34
39
  if backend in {"cosine_local", "cosine"}:
@@ -3,22 +3,74 @@ from __future__ import annotations
3
3
 
4
4
  import re
5
5
 
6
- from typing import TYPE_CHECKING
6
+ from collections.abc import Iterable
7
+ from typing import TYPE_CHECKING, Any
7
8
 
8
9
  import requests
9
10
 
11
+ from memos.log import get_logger
12
+
10
13
  from .base import BaseReranker
14
+ from .concat import concat_original_source
15
+
16
+
17
+ logger = get_logger(__name__)
11
18
 
12
19
 
13
20
  if TYPE_CHECKING:
14
21
  from memos.memories.textual.item import TextualMemoryItem
15
22
 
23
+ # Strip a leading "[...]" tag (e.g., "[2025-09-01] ..." or "[meta] ...")
24
+ # before sending text to the reranker. This keeps inputs clean and
25
+ # avoids misleading the model with bracketed prefixes.
16
26
  _TAG1 = re.compile(r"^\s*\[[^\]]*\]\s*")
27
+ DEFAULT_BOOST_WEIGHTS = {"user_id": 0.5, "tags": 0.2, "session_id": 0.3}
28
+
29
+
30
+ def _value_matches(item_value: Any, wanted: Any) -> bool:
31
+ """
32
+ Generic matching:
33
+ - if item_value is list/tuple/set: check membership (any match if wanted is iterable)
34
+ - else: equality (any match if wanted is iterable)
35
+ """
36
+
37
+ def _iterable(x):
38
+ # exclude strings from "iterable"
39
+ return isinstance(x, Iterable) and not isinstance(x, str | bytes)
40
+
41
+ if _iterable(item_value):
42
+ if _iterable(wanted):
43
+ return any(w in item_value for w in wanted)
44
+ return wanted in item_value
45
+ else:
46
+ if _iterable(wanted):
47
+ return any(item_value == w for w in wanted)
48
+ return item_value == wanted
17
49
 
18
50
 
19
51
  class HTTPBGEReranker(BaseReranker):
20
52
  """
21
- HTTP-based BGE reranker. Mirrors your old MemoryReranker, but configurable.
53
+ HTTP-based BGE reranker.
54
+
55
+ This class sends (query, documents[]) to a remote HTTP endpoint that
56
+ performs cross-encoder-style re-ranking (e.g., BGE reranker) and returns
57
+ relevance scores. It then maps those scores back onto the original
58
+ TextualMemoryItem list and returns (item, score) pairs sorted by score.
59
+
60
+ Notes
61
+ -----
62
+ - The endpoint is expected to accept JSON:
63
+ {
64
+ "model": "<model-name>",
65
+ "query": "<query text>",
66
+ "documents": ["doc1", "doc2", ...]
67
+ }
68
+ - Two response shapes are supported:
69
+ 1) {"results": [{"index": <int>, "relevance_score": <float>}, ...]}
70
+ where "index" refers to the *position in the documents array*.
71
+ 2) {"data": [{"score": <float>}, ...]} (aligned by list order)
72
+ - If the service fails or responds unexpectedly, this falls back to
73
+ returning the original items with 0.0 scores (best-effort).
22
74
  """
23
75
 
24
76
  def __init__(
@@ -28,7 +80,26 @@ class HTTPBGEReranker(BaseReranker):
28
80
  model: str = "bge-reranker-v2-m3",
29
81
  timeout: int = 10,
30
82
  headers_extra: dict | None = None,
83
+ rerank_source: list[str] | None = None,
84
+ boost_weights: dict[str, float] | None = None,
85
+ boost_default: float = 0.0,
86
+ warn_unknown_filter_keys: bool = True,
87
+ **kwargs,
31
88
  ):
89
+ """
90
+ Parameters
91
+ ----------
92
+ reranker_url : str
93
+ HTTP endpoint for the reranker service.
94
+ token : str, optional
95
+ Bearer token for auth. If non-empty, added to the Authorization header.
96
+ model : str, optional
97
+ Model identifier understood by the server.
98
+ timeout : int, optional
99
+ Request timeout (seconds).
100
+ headers_extra : dict | None, optional
101
+ Additional headers to merge into the request headers.
102
+ """
32
103
  if not reranker_url:
33
104
  raise ValueError("reranker_url must not be empty")
34
105
  self.reranker_url = reranker_url
@@ -36,22 +107,62 @@ class HTTPBGEReranker(BaseReranker):
36
107
  self.model = model
37
108
  self.timeout = timeout
38
109
  self.headers_extra = headers_extra or {}
110
+ self.concat_source = rerank_source
111
+
112
+ self.boost_weights = (
113
+ DEFAULT_BOOST_WEIGHTS.copy()
114
+ if boost_weights is None
115
+ else {k: float(v) for k, v in boost_weights.items()}
116
+ )
117
+ self.boost_default = float(boost_default)
118
+ self.warn_unknown_filter_keys = bool(warn_unknown_filter_keys)
119
+ self._warned_missing_keys: set[str] = set()
39
120
 
40
121
  def rerank(
41
122
  self,
42
123
  query: str,
43
- graph_results: list,
124
+ graph_results: list[TextualMemoryItem],
44
125
  top_k: int,
126
+ search_filter: dict | None = None,
45
127
  **kwargs,
46
128
  ) -> list[tuple[TextualMemoryItem, float]]:
129
+ """
130
+ Rank candidate memories by relevance to the query.
131
+
132
+ Parameters
133
+ ----------
134
+ query : str
135
+ The search query.
136
+ graph_results : list[TextualMemoryItem]
137
+ Candidate items to re-rank. Each item is expected to have a
138
+ `.memory` str field; non-strings are ignored.
139
+ top_k : int
140
+ Return at most this many items.
141
+ search_filter : dict | None
142
+ Currently unused. Present to keep signature compatible.
143
+
144
+ Returns
145
+ -------
146
+ list[tuple[TextualMemoryItem, float]]
147
+ Re-ranked items with scores, sorted descending by score.
148
+ """
47
149
  if not graph_results:
48
150
  return []
49
151
 
50
- documents = [
51
- (_TAG1.sub("", m) if isinstance((m := getattr(item, "memory", None)), str) else m)
52
- for item in graph_results
53
- ]
54
- documents = [d for d in documents if isinstance(d, str) and d]
152
+ # Build a mapping from "payload docs index" -> "original graph_results index"
153
+ # Only include items that have a non-empty string memory. This ensures that
154
+ # any index returned by the server can be mapped back correctly.
155
+ if self.concat_source:
156
+ documents = concat_original_source(graph_results, self.concat_source)
157
+ else:
158
+ documents = [
159
+ (_TAG1.sub("", m) if isinstance((m := getattr(item, "memory", None)), str) else m)
160
+ for item in graph_results
161
+ ]
162
+ documents = [d for d in documents if isinstance(d, str) and d]
163
+
164
+ logger.info(f"[HTTPBGERerankerSample] query: {query} , documents: {documents[:5]}...")
165
+
55
166
  if not documents:
56
167
  return []
57
168
 
@@ -59,6 +170,7 @@ class HTTPBGEReranker(BaseReranker):
59
170
  payload = {"model": self.model, "query": query, "documents": documents}
60
171
 
61
172
  try:
173
+ # Make the HTTP request to the reranker service
62
174
  resp = requests.post(
63
175
  self.reranker_url, headers=headers, json=payload, timeout=self.timeout
64
176
  )
@@ -68,18 +180,28 @@ class HTTPBGEReranker(BaseReranker):
68
180
  scored_items: list[tuple[TextualMemoryItem, float]] = []
69
181
 
70
182
  if "results" in data:
183
+ # Format:
184
+ # dict("results": [{"index": int, "relevance_score": float},
185
+ # ...])
71
186
  rows = data.get("results", [])
72
187
  for r in rows:
73
188
  idx = r.get("index")
189
+ # The returned index refers to 'documents' (i.e., our 'pairs' order),
190
+ # so we must map it back to the original graph_results index.
74
191
  if isinstance(idx, int) and 0 <= idx < len(graph_results):
75
- score = float(r.get("relevance_score", r.get("score", 0.0)))
76
- scored_items.append((graph_results[idx], score))
192
+ raw_score = float(r.get("relevance_score", r.get("score", 0.0)))
193
+ item = graph_results[idx]
194
+ # generic boost
195
+ score = self._apply_boost_generic(item, raw_score, search_filter)
196
+ scored_items.append((item, score))
77
197
 
78
198
  scored_items.sort(key=lambda x: x[1], reverse=True)
79
199
  return scored_items[: min(top_k, len(scored_items))]
80
200
 
81
201
  elif "data" in data:
202
+ # Format: {"data": [{"score": float}, ...]} aligned by list order
82
203
  rows = data.get("data", [])
204
+ # Build a list of scores aligned with our 'documents' (pairs)
83
205
  score_list = [float(r.get("score", 0.0)) for r in rows]
84
206
 
85
207
  if len(score_list) < len(graph_results):
@@ -87,13 +209,104 @@ class HTTPBGEReranker(BaseReranker):
87
209
  elif len(score_list) > len(graph_results):
88
210
  score_list = score_list[: len(graph_results)]
89
211
 
90
- scored_items = list(zip(graph_results, score_list, strict=False))
212
+ scored_items = []
213
+ for item, raw_score in zip(graph_results, score_list, strict=False):
214
+ score = self._apply_boost_generic(item, raw_score, search_filter)
215
+ scored_items.append((item, score))
216
+
91
217
  scored_items.sort(key=lambda x: x[1], reverse=True)
92
218
  return scored_items[: min(top_k, len(scored_items))]
93
219
 
94
220
  else:
221
+ # Unexpected response schema: return a 0.0-scored fallback of the first top_k valid docs
222
+ # Note: we use 'pairs' to keep alignment with valid (string) docs.
95
223
  return [(item, 0.0) for item in graph_results[:top_k]]
96
224
 
97
225
  except Exception as e:
98
- print(f"[HTTPBGEReranker] request failed: {e}")
226
+ # Network error, timeout, JSON decode error, etc.
227
+ # Degrade gracefully by returning first top_k valid docs with 0.0 score.
228
+ logger.error(f"[HTTPBGEReranker] request failed: {e}")
99
229
  return [(item, 0.0) for item in graph_results[:top_k]]
230
+
231
+ def _get_attr_or_key(self, obj: Any, key: str) -> Any:
232
+ """
233
+ Resolve `key` on `obj` with one-level fallback into `obj.metadata`.
234
+
235
+ Priority:
236
+ 1) obj.<key>
237
+ 2) obj[key]
238
+ 3) obj.metadata.<key>
239
+ 4) obj.metadata[key]
240
+ """
241
+ if obj is None:
242
+ return None
243
+
244
+ # support input like "metadata.user_id"
245
+ if "." in key:
246
+ head, tail = key.split(".", 1)
247
+ base = self._get_attr_or_key(obj, head)
248
+ return self._get_attr_or_key(base, tail)
249
+
250
+ def _resolve(o: Any, k: str):
251
+ if o is None:
252
+ return None
253
+ v = getattr(o, k, None)
254
+ if v is not None:
255
+ return v
256
+ if hasattr(o, "get"):
257
+ try:
258
+ return o.get(k)
259
+ except Exception:
260
+ return None
261
+ return None
262
+
263
+ # 1) find in obj
264
+ v = _resolve(obj, key)
265
+ if v is not None:
266
+ return v
267
+
268
+ # 2) find in obj.metadata
269
+ meta = _resolve(obj, "metadata")
270
+ if meta is not None:
271
+ return _resolve(meta, key)
272
+
273
+ return None
274
+
275
+ def _apply_boost_generic(
276
+ self,
277
+ item: TextualMemoryItem,
278
+ base_score: float,
279
+ search_filter: dict | None,
280
+ ) -> float:
281
+ """
282
+ Multiply base_score by (1 + weight) for each matching key in search_filter.
283
+ - key resolution: self._get_attr_or_key(item, key)
284
+ - weight = boost_weights.get(key, self.boost_default)
285
+ - unknown key -> one-time warning
286
+ """
287
+ if not search_filter:
288
+ return base_score
289
+
290
+ score = float(base_score)
291
+
292
+ for key, wanted in search_filter.items():
293
+ # _get_attr_or_key automatically find key in item and
294
+ # item.metadata ("metadata.user_id" supported)
295
+ resolved = self._get_attr_or_key(item, key)
296
+
297
+ if resolved is None:
298
+ if self.warn_unknown_filter_keys and key not in self._warned_missing_keys:
299
+ logger.warning(
300
+ "[HTTPBGEReranker] search_filter key '%s' not found on TextualMemoryItem or metadata",
301
+ key,
302
+ )
303
+ self._warned_missing_keys.add(key)
304
+ continue
305
+
306
+ if _value_matches(resolved, wanted):
307
+ w = float(self.boost_weights.get(key, self.boost_default))
308
+ if w != 0.0:
309
+ score *= 1.0 + w
310
+ score = min(max(0.0, score), 1.0)
311
+
312
+ return score
@@ -151,11 +151,253 @@ You are an intelligent keyword extraction system. Your task is to identify and e
151
151
  Answer:
152
152
  """
153
153
 
154
+ MEMORY_FILTERING_PROMPT = """
155
+ # Memory Relevance Filtering Task
156
+
157
+ ## Role
158
+ You are an intelligent memory filtering system. Your primary function is to analyze memory relevance and filter out memories that are completely unrelated to the user's query history.
159
+
160
+ ## Task Description
161
+ Analyze the provided memories and determine which ones are relevant to the user's query history:
162
+ 1. Evaluate semantic relationship between each memory and the query history
163
+ 2. Identify memories that are completely unrelated or irrelevant
164
+ 3. Filter out memories that don't contribute to answering the queries
165
+ 4. Preserve memories that provide context, evidence, or relevant information
166
+
167
+ ## Relevance Criteria
168
+ A memory is considered RELEVANT if it:
169
+ - Directly answers questions from the query history
170
+ - Provides context or background information related to the queries
171
+ - Contains information that could be useful for understanding the queries
172
+ - Shares semantic similarity with query topics or themes
173
+ - Contains keywords or concepts mentioned in the queries
174
+
175
+ A memory is considered IRRELEVANT if it:
176
+ - Has no semantic connection to any query in the history
177
+ - Discusses completely unrelated topics
178
+ - Contains information that cannot help answer any query
179
+ - Is too generic or vague to be useful
180
+
181
+ ## Input Format
182
+ - Query History: List of user queries (chronological order)
183
+ - Memories: List of memory texts to be evaluated
184
+
185
+ ## Output Format Requirements
186
+ You MUST output a valid JSON object with EXACTLY the following structure:
187
+ {{
188
+ "relevant_memories": [array_of_memory_indices],
189
+ "filtered_count": <number_of_filtered_memories>,
190
+ "reasoning": "string_explanation"
191
+ }}
192
+
193
+ ## Important Notes:
194
+ - Only output the JSON object, nothing else
195
+ - Do not include any markdown formatting or code block notation
196
+ - Ensure all brackets and quotes are properly closed
197
+ - The output must be parseable by a JSON parser
198
+ - Memory indices should correspond to the input order (0-based)
199
+
200
+ ## Processing Guidelines
201
+ 1. Be conservative in filtering - when in doubt, keep the memory
202
+ 2. Consider both direct and indirect relevance
203
+ 3. Look for thematic connections, not just exact keyword matches
204
+ 4. Preserve memories that provide valuable context
205
+
206
+ ## Current Task
207
+ Query History: {query_history}
208
+ Memories to Filter: {memories}
209
+
210
+ Please provide your filtering analysis:
211
+ """
212
+
213
+ MEMORY_REDUNDANCY_FILTERING_PROMPT = """
214
+ # Memory Redundancy Filtering Task
215
+
216
+ ## Role
217
+ You are an intelligent memory optimization system. Your primary function is to analyze memories and remove redundancy to improve memory quality and relevance.
218
+
219
+ ## Task Description
220
+ Analyze the provided memories and identify redundant ones:
221
+ 1. **Redundancy Detection**: Find memories that contain the same core facts relevant to queries
222
+ 2. **Best Memory Selection**: Keep only the most concise and focused version of redundant information
223
+ 3. **Quality Preservation**: Ensure the final set covers all necessary information without redundancy
224
+
225
+ ## Redundancy Detection Criteria
226
+ A memory is considered REDUNDANT if it:
227
+ - Contains the same core fact as another memory that's relevant to the queries
228
+ - Provides the same information but with additional irrelevant details
229
+ - Repeats information that's already covered by a more concise memory
230
+ - Has overlapping content with another memory that serves the same purpose
231
+
232
+ When redundancy is found, KEEP the memory that:
233
+ - Is more concise and focused
234
+ - Contains less irrelevant information
235
+ - Is more directly relevant to the queries
236
+ - Has higher information density
237
+
238
+ ## Input Format
239
+ - Query History: List of user queries (chronological order)
240
+ - Memories: List of memory texts to be evaluated
241
+
242
+ ## Output Format Requirements
243
+ You MUST output a valid JSON object with EXACTLY the following structure:
244
+ {{
245
+ "kept_memories": [array_of_memory_indices_to_keep],
246
+ "redundant_groups": [
247
+ {{
248
+ "group_id": <number>,
249
+ "memories": [array_of_redundant_memory_indices],
250
+ "kept_memory": <index_of_best_memory_in_group>,
251
+ "reason": "explanation_of_why_this_memory_was_kept"
252
+ }}
253
+ ],
254
+ "reasoning": "string_explanation_of_filtering_decisions"
255
+ }}
256
+
257
+ ## Important Notes:
258
+ - Only output the JSON object, nothing else
259
+ - Do not include any markdown formatting or code block notation
260
+ - Ensure all brackets and quotes are properly closed
261
+ - The output must be parseable by a JSON parser
262
+ - Memory indices should correspond to the input order (0-based)
263
+ - Be conservative in filtering - when in doubt, keep the memory
264
+ - Focus on semantic similarity, not just exact text matches
265
+
266
+ ## Processing Guidelines
267
+ 1. First identify which memories are relevant to the queries
268
+ 2. Group relevant memories by semantic similarity and core facts
269
+ 3. Within each group, select the best memory (most concise, least noise)
270
+ 4. Ensure the final set covers all necessary information without redundancy
271
+
272
+ ## Current Task
273
+ Query History: {query_history}
274
+ Memories to Filter: {memories}
275
+
276
+ Please provide your redundancy filtering analysis:
277
+ """
278
+
279
+ MEMORY_COMBINED_FILTERING_PROMPT = """
280
+ # Memory Combined Filtering Task
281
+
282
+ ## Role
283
+ You are an intelligent memory optimization system. Your primary function is to analyze memories and perform two types of filtering in sequence:
284
+ 1. **Unrelated Memory Removal**: Remove memories that are completely unrelated to the user's query history
285
+ 2. **Redundancy Removal**: Remove redundant memories by keeping only the most informative version
286
+
287
+ ## Task Description
288
+ Analyze the provided memories and perform comprehensive filtering:
289
+ 1. **First Step - Unrelated Filtering**: Identify and remove memories that have no semantic connection to any query
290
+ 2. **Second Step - Redundancy Filtering**: Group similar memories and keep only the best version from each group
291
+
292
+ ## Unrelated Memory Detection Criteria
293
+ A memory is considered UNRELATED if it:
294
+ - Has no semantic connection to any query in the history
295
+ - Discusses completely unrelated topics
296
+ - Contains information that cannot help answer any query
297
+ - Is too generic or vague to be useful
298
+
299
+ ## Redundancy Detection Criteria
300
+ A memory is considered REDUNDANT if it:
301
+ - Contains the same core fact as another memory that's relevant to the queries
302
+ - Provides the same information but with additional irrelevant details
303
+ - Repeats information that's already covered by a more concise memory
304
+ - Has overlapping content with another memory that serves the same purpose
305
+
306
+ When redundancy is found, KEEP the memory that:
307
+ - Is more concise and focused
308
+ - Contains less irrelevant information
309
+ - Is more directly relevant to the queries
310
+ - Has higher information density
311
+
312
+ ## Input Format
313
+ - Query History: List of user queries (chronological order)
314
+ - Memories: List of memory texts to be evaluated
315
+
316
+ ## Output Format Requirements
317
+ You MUST output a valid JSON object with EXACTLY the following structure:
318
+ {{
319
+ "kept_memories": [array_of_memory_indices_to_keep],
320
+ "unrelated_removed_count": <number_of_unrelated_memories_removed>,
321
+ "redundant_removed_count": <number_of_redundant_memories_removed>,
322
+ "redundant_groups": [
323
+ {{
324
+ "group_id": <number>,
325
+ "memories": [array_of_redundant_memory_indices],
326
+ "kept_memory": <index_of_best_memory_in_group>,
327
+ "reason": "explanation_of_why_this_memory_was_kept"
328
+ }}
329
+ ],
330
+ "reasoning": "string_explanation_of_filtering_decisions"
331
+ }}
332
+
333
+ ## Important Notes:
334
+ - Only output the JSON object, nothing else
335
+ - Do not include any markdown formatting or code block notation
336
+ - Ensure all brackets and quotes are properly closed
337
+ - The output must be parseable by a JSON parser
338
+ - Memory indices should correspond to the input order (0-based)
339
+ - Be conservative in filtering - when in doubt, keep the memory
340
+ - Focus on semantic similarity, not just exact text matches
341
+
342
+ ## Processing Guidelines
343
+ 1. **First, identify unrelated memories** and mark them for removal
344
+ 2. **Then, group remaining memories** by semantic similarity and core facts
345
+ 3. **Within each group, select the best memory** (most concise, least noise)
346
+ 4. **Ensure the final set covers all necessary information** without redundancy
347
+ 5. **Count how many memories were removed** for each reason
348
+
349
+ ## Current Task
350
+ Query History: {query_history}
351
+ Memories to Filter: {memories}
352
+
353
+ Please provide your combined filtering analysis:
354
+ """
355
+
356
+
357
+ MEMORY_ANSWER_ABILITY_EVALUATION_PROMPT = """
358
+ # Memory Answer Ability Evaluation Task
359
+
360
+ ## Task
361
+ Evaluate whether the provided memories contain sufficient information to answer the user's query.
362
+
363
+ ## Evaluation Criteria
364
+ Consider these factors:
365
+ 1. **Answer completeness**: Do the memories cover all aspects of the query?
366
+ 2. **Evidence relevance**: Do the memories directly support answering the query?
367
+ 3. **Detail specificity**: Do the memories contain necessary granularity?
368
+ 4. **Information gaps**: Are there obvious missing pieces of information?
369
+
370
+ ## Decision Rules
371
+ - Return `True` for "result" ONLY when memories provide complete, relevant answers
372
+ - Return `False` for "result" if memories are insufficient, irrelevant, or incomplete
373
+
374
+ ## User Query
375
+ {query}
376
+
377
+ ## Available Memories
378
+ {memory_list}
379
+
380
+ ## Required Output
381
+ Return a JSON object with this exact structure:
382
+ {{
383
+ "result": <boolean>,
384
+ "reason": "<brief explanation of your decision>"
385
+ }}
386
+
387
+ ## Instructions
388
+ - Only output the JSON object, nothing else
389
+ - Be conservative: if there's any doubt about completeness, return true
390
+ - Focus on whether the memories can fully answer the query without additional information
391
+ """
154
392
 
155
393
  PROMPT_MAPPING = {
156
394
  "intent_recognizing": INTENT_RECOGNIZING_PROMPT,
157
395
  "memory_reranking": MEMORY_RERANKING_PROMPT,
158
396
  "query_keywords_extraction": QUERY_KEYWORDS_EXTRACTION_PROMPT,
397
+ "memory_filtering": MEMORY_FILTERING_PROMPT,
398
+ "memory_redundancy_filtering": MEMORY_REDUNDANCY_FILTERING_PROMPT,
399
+ "memory_combined_filtering": MEMORY_COMBINED_FILTERING_PROMPT,
400
+ "memory_answer_ability_evaluation": MEMORY_ANSWER_ABILITY_EVALUATION_PROMPT,
159
401
  }
160
402
 
161
403
  MEMORY_ASSEMBLY_TEMPLATE = """The retrieved memories are listed as follows:\n\n {memory_text}"""