MemoryOS 0.2.2__py3-none-any.whl → 1.0.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-0.2.2.dist-info → memoryos-1.0.1.dist-info}/METADATA +7 -1
  2. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/RECORD +81 -66
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +31 -8
  5. memos/api/context/context.py +1 -1
  6. memos/api/context/context_thread.py +96 -0
  7. memos/api/middleware/request_context.py +94 -0
  8. memos/api/product_api.py +5 -1
  9. memos/api/product_models.py +16 -0
  10. memos/api/routers/product_router.py +39 -3
  11. memos/api/start_api.py +3 -0
  12. memos/configs/internet_retriever.py +13 -0
  13. memos/configs/mem_scheduler.py +38 -16
  14. memos/configs/memory.py +13 -0
  15. memos/configs/reranker.py +18 -0
  16. memos/graph_dbs/base.py +33 -4
  17. memos/graph_dbs/nebular.py +631 -236
  18. memos/graph_dbs/neo4j.py +18 -7
  19. memos/graph_dbs/neo4j_community.py +6 -3
  20. memos/llms/vllm.py +2 -0
  21. memos/log.py +125 -8
  22. memos/mem_os/core.py +49 -11
  23. memos/mem_os/main.py +1 -1
  24. memos/mem_os/product.py +392 -215
  25. memos/mem_os/utils/default_config.py +1 -1
  26. memos/mem_os/utils/format_utils.py +11 -47
  27. memos/mem_os/utils/reference_utils.py +153 -0
  28. memos/mem_reader/simple_struct.py +112 -43
  29. memos/mem_scheduler/base_scheduler.py +58 -55
  30. memos/mem_scheduler/{modules → general_modules}/base.py +1 -2
  31. memos/mem_scheduler/{modules → general_modules}/dispatcher.py +54 -15
  32. memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +4 -4
  33. memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
  34. memos/mem_scheduler/{modules → general_modules}/retriever.py +19 -5
  35. memos/mem_scheduler/{modules → general_modules}/scheduler_logger.py +10 -4
  36. memos/mem_scheduler/general_scheduler.py +110 -67
  37. memos/mem_scheduler/monitors/__init__.py +0 -0
  38. memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
  39. memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +57 -19
  40. memos/mem_scheduler/mos_for_test_scheduler.py +7 -1
  41. memos/mem_scheduler/schemas/general_schemas.py +3 -2
  42. memos/mem_scheduler/schemas/message_schemas.py +2 -1
  43. memos/mem_scheduler/schemas/monitor_schemas.py +10 -2
  44. memos/mem_scheduler/utils/misc_utils.py +43 -2
  45. memos/mem_user/mysql_user_manager.py +4 -2
  46. memos/memories/activation/item.py +1 -1
  47. memos/memories/activation/kv.py +20 -8
  48. memos/memories/textual/base.py +1 -1
  49. memos/memories/textual/general.py +1 -1
  50. memos/memories/textual/item.py +1 -1
  51. memos/memories/textual/tree.py +31 -1
  52. memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +30 -48
  53. memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
  54. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +2 -0
  55. memos/memories/textual/tree_text_memory/organize/reorganizer.py +102 -140
  56. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +231 -0
  57. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +9 -0
  58. memos/memories/textual/tree_text_memory/retrieve/recall.py +67 -10
  59. memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
  60. memos/memories/textual/tree_text_memory/retrieve/searcher.py +246 -134
  61. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +7 -2
  62. memos/memories/textual/tree_text_memory/retrieve/utils.py +7 -5
  63. memos/memos_tools/lockfree_dict.py +120 -0
  64. memos/memos_tools/notification_utils.py +46 -0
  65. memos/memos_tools/thread_safe_dict.py +288 -0
  66. memos/reranker/__init__.py +4 -0
  67. memos/reranker/base.py +24 -0
  68. memos/reranker/cosine_local.py +95 -0
  69. memos/reranker/factory.py +43 -0
  70. memos/reranker/http_bge.py +99 -0
  71. memos/reranker/noop.py +16 -0
  72. memos/templates/mem_reader_prompts.py +290 -39
  73. memos/templates/mem_scheduler_prompts.py +23 -10
  74. memos/templates/mos_prompts.py +133 -31
  75. memos/templates/tree_reorganize_prompts.py +24 -17
  76. memos/utils.py +19 -0
  77. memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
  78. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/LICENSE +0 -0
  79. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/WHEEL +0 -0
  80. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/entry_points.txt +0 -0
  81. /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
  82. /memos/mem_scheduler/{modules → general_modules}/misc.py +0 -0
@@ -8,11 +8,12 @@ from memos.graph_dbs.factory import Neo4jGraphDB
8
8
  from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM
9
9
  from memos.log import get_logger
10
10
  from memos.memories.textual.item import SearchedTreeNodeTextualMemoryMetadata, TextualMemoryItem
11
+ from memos.reranker.base import BaseReranker
12
+ from memos.utils import timed
11
13
 
12
14
  from .internet_retriever_factory import InternetRetrieverFactory
13
15
  from .reasoner import MemoryReasoner
14
16
  from .recall import GraphMemoryRetriever
15
- from .reranker import MemoryReranker
16
17
  from .task_goal_parser import TaskGoalParser
17
18
 
18
19
 
@@ -25,21 +26,29 @@ class Searcher:
25
26
  dispatcher_llm: OpenAILLM | OllamaLLM | AzureLLM,
26
27
  graph_store: Neo4jGraphDB,
27
28
  embedder: OllamaEmbedder,
29
+ reranker: BaseReranker,
28
30
  internet_retriever: InternetRetrieverFactory | None = None,
31
+ moscube: bool = False,
29
32
  ):
30
33
  self.graph_store = graph_store
31
34
  self.embedder = embedder
32
35
 
33
36
  self.task_goal_parser = TaskGoalParser(dispatcher_llm)
34
37
  self.graph_retriever = GraphMemoryRetriever(self.graph_store, self.embedder)
35
- self.reranker = MemoryReranker(dispatcher_llm, self.embedder)
38
+ self.reranker = reranker
36
39
  self.reasoner = MemoryReasoner(dispatcher_llm)
37
40
 
38
41
  # Create internet retriever from config if provided
39
42
  self.internet_retriever = internet_retriever
43
+ self.moscube = moscube
40
44
 
45
+ self._usage_executor = concurrent.futures.ThreadPoolExecutor(
46
+ max_workers=4, thread_name_prefix="usage"
47
+ )
48
+
49
+ @timed
41
50
  def search(
42
- self, query: str, top_k: int, info=None, mode: str = "fast", memory_type: str = "All"
51
+ self, query: str, top_k: int, info=None, mode="fast", memory_type="All"
43
52
  ) -> list[TextualMemoryItem]:
44
53
  """
45
54
  Search for memories based on a query.
@@ -57,25 +66,60 @@ class Searcher:
57
66
  Returns:
58
67
  list[TextualMemoryItem]: List of matching memories.
59
68
  """
69
+ logger.info(
70
+ f"[SEARCH] Start query='{query}', top_k={top_k}, mode={mode}, memory_type={memory_type}"
71
+ )
60
72
  if not info:
61
73
  logger.warning(
62
74
  "Please input 'info' when use tree.search so that "
63
75
  "the database would store the consume history."
64
76
  )
65
77
  info = {"user_id": "", "session_id": ""}
66
- # Step 1: Parse task structure into topic, concept, and fact levels
78
+ else:
79
+ logger.debug(f"[SEARCH] Received info dict: {info}")
80
+
81
+ parsed_goal, query_embedding, context, query = self._parse_task(query, info, mode)
82
+ results = self._retrieve_paths(
83
+ query, parsed_goal, query_embedding, info, top_k, mode, memory_type
84
+ )
85
+ deduped = self._deduplicate_results(results)
86
+ final_results = self._sort_and_trim(deduped, top_k)
87
+ self._update_usage_history(final_results, info)
88
+
89
+ logger.info(f"[SEARCH] Done. Total {len(final_results)} results.")
90
+ res_results = ""
91
+ for _num_i, result in enumerate(final_results):
92
+ res_results += "\n" + (
93
+ result.id + "|" + result.metadata.memory_type + "|" + result.memory
94
+ )
95
+ logger.info(f"[SEARCH] Results. {res_results}")
96
+ return final_results
97
+
98
+ @timed
99
+ def _parse_task(self, query, info, mode, top_k=5):
100
+ """Parse user query, do embedding search and create context"""
67
101
  context = []
102
+ query_embedding = None
103
+
104
+ # fine mode will trigger initial embedding search
68
105
  if mode == "fine":
106
+ logger.info("[SEARCH] Fine mode: embedding search")
69
107
  query_embedding = self.embedder.embed([query])[0]
70
- related_node_ids = self.graph_store.search_by_embedding(query_embedding, top_k=top_k)
108
+
109
+ # retrieve related nodes by embedding
71
110
  related_nodes = [
72
- self.graph_store.get_node(related_node["id"]) for related_node in related_node_ids
111
+ self.graph_store.get_node(n["id"])
112
+ for n in self.graph_store.search_by_embedding(query_embedding, top_k=top_k)
73
113
  ]
114
+ context = list({node["memory"] for node in related_nodes})
74
115
 
75
- context = [related_node["memory"] for related_node in related_nodes]
76
- context = list(set(context))
116
+ # optional: supplement context with internet knowledge
117
+ """if self.internet_retriever:
118
+ extra = self.internet_retriever.retrieve_from_internet(query=query, top_k=3)
119
+ context.extend(item.memory.partition("\nContent: ")[-1] for item in extra)
120
+ """
77
121
 
78
- # Step 1a: Parse task structure into topic, concept, and fact levels
122
+ # parse goal using LLM
79
123
  parsed_goal = self.task_goal_parser.parse(
80
124
  task_description=query,
81
125
  context="\n".join(context),
@@ -83,150 +127,218 @@ class Searcher:
83
127
  mode=mode,
84
128
  )
85
129
 
86
- query = (
87
- parsed_goal.rephrased_query
88
- if parsed_goal.rephrased_query and len(parsed_goal.rephrased_query) > 0
89
- else query
90
- )
91
-
130
+ query = parsed_goal.rephrased_query or query
131
+ # if goal has extra memories, embed them too
92
132
  if parsed_goal.memories:
93
133
  query_embedding = self.embedder.embed(list({query, *parsed_goal.memories}))
94
134
 
95
- # Step 2a: Working memory retrieval (Path A)
96
- def retrieve_from_working_memory():
97
- """
98
- Direct structure-based retrieval from working memory.
99
- """
100
- if memory_type not in ["All", "WorkingMemory"]:
101
- return []
135
+ return parsed_goal, query_embedding, context, query
102
136
 
103
- working_memory = self.graph_retriever.retrieve(
104
- query=query, parsed_goal=parsed_goal, top_k=top_k, memory_scope="WorkingMemory"
105
- )
106
- # Rerank working_memory results
107
- ranked_memories = self.reranker.rerank(
108
- query=query,
109
- query_embedding=query_embedding[0],
110
- graph_results=working_memory,
111
- top_k=top_k,
112
- parsed_goal=parsed_goal,
137
+ @timed
138
+ def _retrieve_paths(self, query, parsed_goal, query_embedding, info, top_k, mode, memory_type):
139
+ """Run A/B/C retrieval paths in parallel"""
140
+ tasks = []
141
+ with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
142
+ tasks.append(
143
+ executor.submit(
144
+ self._retrieve_from_working_memory,
145
+ query,
146
+ parsed_goal,
147
+ query_embedding,
148
+ top_k,
149
+ memory_type,
150
+ )
113
151
  )
114
- return ranked_memories
115
-
116
- # Step 2b: Parallel long-term and user memory retrieval (Path B)
117
- def retrieve_ranked_long_term_and_user():
118
- """
119
- Retrieve from both long-term and user memory, then rank and merge results.
120
- """
121
- long_term_items = (
122
- self.graph_retriever.retrieve(
123
- query=query,
124
- query_embedding=query_embedding,
125
- parsed_goal=parsed_goal,
126
- top_k=top_k * 2,
127
- memory_scope="LongTermMemory",
152
+ tasks.append(
153
+ executor.submit(
154
+ self._retrieve_from_long_term_and_user,
155
+ query,
156
+ parsed_goal,
157
+ query_embedding,
158
+ top_k,
159
+ memory_type,
128
160
  )
129
- if memory_type in ["All", "LongTermMemory"]
130
- else []
131
161
  )
132
- user_items = (
133
- self.graph_retriever.retrieve(
134
- query=query,
135
- query_embedding=query_embedding,
136
- parsed_goal=parsed_goal,
137
- top_k=top_k * 2,
138
- memory_scope="UserMemory",
162
+ tasks.append(
163
+ executor.submit(
164
+ self._retrieve_from_internet,
165
+ query,
166
+ parsed_goal,
167
+ query_embedding,
168
+ top_k,
169
+ info,
170
+ mode,
171
+ memory_type,
139
172
  )
140
- if memory_type in ["All", "UserMemory"]
141
- else []
142
173
  )
174
+ if self.moscube:
175
+ tasks.append(
176
+ executor.submit(
177
+ self._retrieve_from_memcubes,
178
+ query,
179
+ parsed_goal,
180
+ query_embedding,
181
+ top_k,
182
+ "memos_cube01",
183
+ )
184
+ )
185
+
186
+ results = []
187
+ for t in tasks:
188
+ results.extend(t.result())
189
+
190
+ logger.info(f"[SEARCH] Total raw results: {len(results)}")
191
+ return results
192
+
193
+ # --- Path A
194
+ @timed
195
+ def _retrieve_from_working_memory(
196
+ self, query, parsed_goal, query_embedding, top_k, memory_type
197
+ ):
198
+ """Retrieve and rerank from WorkingMemory"""
199
+ if memory_type not in ["All", "WorkingMemory"]:
200
+ logger.info(f"[PATH-A] '{query}'Skipped (memory_type does not match)")
201
+ return []
202
+ items = self.graph_retriever.retrieve(
203
+ query=query, parsed_goal=parsed_goal, top_k=top_k, memory_scope="WorkingMemory"
204
+ )
205
+ return self.reranker.rerank(
206
+ query=query,
207
+ query_embedding=query_embedding[0],
208
+ graph_results=items,
209
+ top_k=top_k,
210
+ parsed_goal=parsed_goal,
211
+ )
143
212
 
144
- # Rerank combined results
145
- ranked_memories = self.reranker.rerank(
213
+ # --- Path B
214
+ @timed
215
+ def _retrieve_from_long_term_and_user(
216
+ self, query, parsed_goal, query_embedding, top_k, memory_type
217
+ ):
218
+ """Retrieve and rerank from LongTermMemory and UserMemory"""
219
+ results = []
220
+ if memory_type in ["All", "LongTermMemory"]:
221
+ results += self.graph_retriever.retrieve(
146
222
  query=query,
147
- query_embedding=query_embedding[0],
148
- graph_results=long_term_items + user_items,
149
- top_k=top_k * 2,
150
223
  parsed_goal=parsed_goal,
224
+ query_embedding=query_embedding,
225
+ top_k=top_k * 2,
226
+ memory_scope="LongTermMemory",
151
227
  )
152
- return ranked_memories
153
-
154
- # Step 2c: Internet retrieval (Path C)
155
- def retrieve_from_internet():
156
- """
157
- Retrieve information from the internet using Google Custom Search API.
158
- """
159
- if not self.internet_retriever or mode == "fast" or not parsed_goal.internet_search:
160
- return []
161
- if memory_type not in ["All"]:
162
- return []
163
- internet_items = self.internet_retriever.retrieve_from_internet(
164
- query=query, top_k=top_k, parsed_goal=parsed_goal, info=info
165
- )
166
-
167
- # Convert to the format expected by reranker
168
- ranked_memories = self.reranker.rerank(
228
+ if memory_type in ["All", "UserMemory"]:
229
+ results += self.graph_retriever.retrieve(
169
230
  query=query,
170
- query_embedding=query_embedding[0],
171
- graph_results=internet_items,
172
- top_k=min(top_k, 5),
173
231
  parsed_goal=parsed_goal,
232
+ query_embedding=query_embedding,
233
+ top_k=top_k * 2,
234
+ memory_scope="UserMemory",
174
235
  )
175
- return ranked_memories
176
-
177
- # Step 3: Parallel execution of all paths (enable internet search accoeding to parameter in the parsed goal)
178
- if parsed_goal.internet_search:
179
- with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
180
- future_working = executor.submit(retrieve_from_working_memory)
181
- future_hybrid = executor.submit(retrieve_ranked_long_term_and_user)
182
- future_internet = executor.submit(retrieve_from_internet)
183
-
184
- working_results = future_working.result()
185
- hybrid_results = future_hybrid.result()
186
- internet_results = future_internet.result()
187
- searched_res = working_results + hybrid_results + internet_results
188
- else:
189
- with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
190
- future_working = executor.submit(retrieve_from_working_memory)
191
- future_hybrid = executor.submit(retrieve_ranked_long_term_and_user)
192
-
193
- working_results = future_working.result()
194
- hybrid_results = future_hybrid.result()
195
- searched_res = working_results + hybrid_results
196
-
197
- # Deduplicate by item.memory, keep higher score
198
- deduped_result = {}
199
- for item, score in searched_res:
200
- mem_key = item.memory
201
- if mem_key not in deduped_result or score > deduped_result[mem_key][1]:
202
- deduped_result[mem_key] = (item, score)
203
-
204
- searched_res = []
205
- for item, score in sorted(deduped_result.values(), key=lambda pair: pair[1], reverse=True)[
206
- :top_k
207
- ]:
236
+ return self.reranker.rerank(
237
+ query=query,
238
+ query_embedding=query_embedding[0],
239
+ graph_results=results,
240
+ top_k=top_k,
241
+ parsed_goal=parsed_goal,
242
+ )
243
+
244
+ @timed
245
+ def _retrieve_from_memcubes(
246
+ self, query, parsed_goal, query_embedding, top_k, cube_name="memos_cube01"
247
+ ):
248
+ """Retrieve and rerank from LongTermMemory and UserMemory"""
249
+ results = self.graph_retriever.retrieve_from_cube(
250
+ query_embedding=query_embedding,
251
+ top_k=top_k * 2,
252
+ memory_scope="LongTermMemory",
253
+ cube_name=cube_name,
254
+ )
255
+ return self.reranker.rerank(
256
+ query=query,
257
+ query_embedding=query_embedding[0],
258
+ graph_results=results,
259
+ top_k=top_k,
260
+ parsed_goal=parsed_goal,
261
+ )
262
+
263
+ # --- Path C
264
+ @timed
265
+ def _retrieve_from_internet(
266
+ self, query, parsed_goal, query_embedding, top_k, info, mode, memory_type
267
+ ):
268
+ """Retrieve and rerank from Internet source"""
269
+ if not self.internet_retriever or mode == "fast":
270
+ logger.info(f"[PATH-C] '{query}' Skipped (no retriever, fast mode)")
271
+ return []
272
+ if memory_type not in ["All"]:
273
+ return []
274
+ logger.info(f"[PATH-C] '{query}' Retrieving from internet...")
275
+ items = self.internet_retriever.retrieve_from_internet(
276
+ query=query, top_k=top_k, parsed_goal=parsed_goal, info=info
277
+ )
278
+ logger.info(f"[PATH-C] '{query}' Retrieved from internet {len(items)} items: {items}")
279
+ return self.reranker.rerank(
280
+ query=query,
281
+ query_embedding=query_embedding[0],
282
+ graph_results=items,
283
+ top_k=min(top_k, 5),
284
+ parsed_goal=parsed_goal,
285
+ )
286
+
287
+ @timed
288
+ def _deduplicate_results(self, results):
289
+ """Deduplicate results by memory text"""
290
+ deduped = {}
291
+ for item, score in results:
292
+ if item.memory not in deduped or score > deduped[item.memory][1]:
293
+ deduped[item.memory] = (item, score)
294
+ return list(deduped.values())
295
+
296
+ @timed
297
+ def _sort_and_trim(self, results, top_k):
298
+ """Sort results by score and trim to top_k"""
299
+ sorted_results = sorted(results, key=lambda pair: pair[1], reverse=True)[:top_k]
300
+ final_items = []
301
+ for item, score in sorted_results:
208
302
  meta_data = item.metadata.model_dump()
209
303
  if "relativity" not in meta_data:
210
304
  meta_data["relativity"] = score
211
- new_meta = SearchedTreeNodeTextualMemoryMetadata(**meta_data)
212
- searched_res.append(
213
- TextualMemoryItem(id=item.id, memory=item.memory, metadata=new_meta)
305
+ final_items.append(
306
+ TextualMemoryItem(
307
+ id=item.id,
308
+ memory=item.memory,
309
+ metadata=SearchedTreeNodeTextualMemoryMetadata(**meta_data),
310
+ )
214
311
  )
312
+ return final_items
215
313
 
216
- # Step 5: Update usage history with current timestamp
314
+ @timed
315
+ def _update_usage_history(self, items, info):
316
+ """Update usage history in graph DB"""
217
317
  now_time = datetime.now().isoformat()
218
- if "chat_history" in info:
219
- info.pop("chat_history")
220
- usage_record = json.dumps(
221
- {"time": now_time, "info": info}
222
- ) # `info` should be a serializable dict or string
223
-
224
- for item in searched_res:
225
- if (
226
- hasattr(item, "id")
227
- and hasattr(item, "metadata")
228
- and hasattr(item.metadata, "usage")
229
- ):
230
- item.metadata.usage.append(usage_record)
231
- self.graph_store.update_node(item.id, {"usage": item.metadata.usage})
232
- return searched_res
318
+ info_copy = dict(info or {})
319
+ info_copy.pop("chat_history", None)
320
+ usage_record = json.dumps({"time": now_time, "info": info_copy})
321
+ payload = []
322
+ for it in items:
323
+ try:
324
+ item_id = getattr(it, "id", None)
325
+ md = getattr(it, "metadata", None)
326
+ if md is None:
327
+ continue
328
+ if not hasattr(md, "usage") or md.usage is None:
329
+ md.usage = []
330
+ md.usage.append(usage_record)
331
+ if item_id:
332
+ payload.append((item_id, list(md.usage)))
333
+ except Exception:
334
+ logger.exception("[USAGE] snapshot item failed")
335
+
336
+ if payload:
337
+ self._usage_executor.submit(self._update_usage_history_worker, payload, usage_record)
338
+
339
+ def _update_usage_history_worker(self, payload, usage_record: str):
340
+ try:
341
+ for item_id, usage_list in payload:
342
+ self.graph_store.update_node(item_id, {"usage": usage_list})
343
+ except Exception:
344
+ logger.exception("[USAGE] update usage failed")
@@ -1,13 +1,16 @@
1
- import logging
2
1
  import traceback
3
2
 
4
3
  from string import Template
5
4
 
6
5
  from memos.llms.base import BaseLLM
6
+ from memos.log import get_logger
7
7
  from memos.memories.textual.tree_text_memory.retrieve.retrieval_mid_structs import ParsedTaskGoal
8
8
  from memos.memories.textual.tree_text_memory.retrieve.utils import TASK_PARSE_PROMPT
9
9
 
10
10
 
11
+ logger = get_logger(__name__)
12
+
13
+
11
14
  class TaskGoalParser:
12
15
  """
13
16
  Unified TaskGoalParser:
@@ -70,10 +73,12 @@ class TaskGoalParser:
70
73
  prompt = Template(TASK_PARSE_PROMPT).substitute(
71
74
  task=query.strip(), context=context, conversation=conversation_prompt
72
75
  )
76
+ logger.info(f"Parsing Goal... LLM input is {prompt}")
73
77
  response = self.llm.generate(messages=[{"role": "user", "content": prompt}])
78
+ logger.info(f"Parsing Goal... LLM Response is {response}")
74
79
  return self._parse_response(response)
75
80
  except Exception:
76
- logging.warning(f"Fail to fine-parse query {query}: {traceback.format_exc()}")
81
+ logger.warning(f"Fail to fine-parse query {query}: {traceback.format_exc()}")
77
82
  return self._parse_fast(query)
78
83
 
79
84
  def _parse_response(self, response: str) -> ParsedTaskGoal:
@@ -5,21 +5,23 @@ You are a task parsing expert. Given a user task instruction, optional former co
5
5
  2. Tags: thematic tags to help categorize and retrieve related memories.
6
6
  3. Goal Type: retrieval | qa | generation
7
7
  4. Rephrased instruction: Give a rephrased task instruction based on the former conversation to make it less confusing to look alone. If you think the task instruction is easy enough to understand, or there is no former conversation, set "rephrased_instruction" to an empty string.
8
- 5. Need for internet search: If you think you need to search the internet to finish the rephrased/original user task instruction, set "internet_search" to True. Otherwise, set it to False.
8
+ 5. Need for internet search: If the user's task instruction only involves objective facts or can be completed without introducing external knowledge, set "internet_search" to False. Otherwise, set it to True.
9
9
  6. Memories: Provide 2–5 short semantic expansions or rephrasings of the rephrased/original user task instruction. These are used for improved embedding search coverage. Each should be clear, concise, and meaningful for retrieval.
10
10
 
11
- Task description:
12
- \"\"\"$task\"\"\"
13
-
14
11
  Former conversation (if any):
15
12
  \"\"\"
16
13
  $conversation
17
14
  \"\"\"
18
15
 
16
+ Task description(User Question):
17
+ \"\"\"$task\"\"\"
18
+
19
19
  Context (if any):
20
20
  \"\"\"$context\"\"\"
21
21
 
22
- Return strictly in this JSON format:
22
+ Return strictly in this JSON format, note that the
23
+ keys/tags/rephrased_instruction/memories should use the same language as the
24
+ input query:
23
25
  {
24
26
  "keys": [...],
25
27
  "tags": [...],
@@ -0,0 +1,120 @@
1
+ """
2
+ Lock-free dictionary implementation using copy-on-write strategy.
3
+ This provides better performance but uses more memory.
4
+ """
5
+
6
+ import threading
7
+
8
+ from collections.abc import ItemsView, Iterator, KeysView, ValuesView
9
+ from typing import Generic, TypeVar
10
+
11
+
12
+ K = TypeVar("K")
13
+ V = TypeVar("V")
14
+
15
+
16
+ class CopyOnWriteDict(Generic[K, V]):
17
+ """
18
+ A lock-free dictionary using copy-on-write strategy.
19
+
20
+ Reads are completely lock-free and very fast.
21
+ Writes create a new copy of the dictionary.
22
+ Uses more memory but provides excellent read performance.
23
+ """
24
+
25
+ def __init__(self, initial_dict: dict[K, V] | None = None):
26
+ """Initialize with optional initial dictionary."""
27
+ self._dict = initial_dict.copy() if initial_dict else {}
28
+ self._write_lock = threading.Lock() # Only for writes
29
+
30
+ def __getitem__(self, key: K) -> V:
31
+ """Get item by key - completely lock-free."""
32
+ return self._dict[key]
33
+
34
+ def __setitem__(self, key: K, value: V) -> None:
35
+ """Set item by key - uses copy-on-write."""
36
+ with self._write_lock:
37
+ # Create a new dictionary with the update
38
+ new_dict = self._dict.copy()
39
+ new_dict[key] = value
40
+ # Atomic replacement
41
+ self._dict = new_dict
42
+
43
+ def __delitem__(self, key: K) -> None:
44
+ """Delete item by key - uses copy-on-write."""
45
+ with self._write_lock:
46
+ new_dict = self._dict.copy()
47
+ del new_dict[key]
48
+ self._dict = new_dict
49
+
50
+ def __contains__(self, key: K) -> bool:
51
+ """Check if key exists - completely lock-free."""
52
+ return key in self._dict
53
+
54
+ def __len__(self) -> int:
55
+ """Get length - completely lock-free."""
56
+ return len(self._dict)
57
+
58
+ def __bool__(self) -> bool:
59
+ """Check if not empty - completely lock-free."""
60
+ return bool(self._dict)
61
+
62
+ def __iter__(self) -> Iterator[K]:
63
+ """Iterate over keys - completely lock-free."""
64
+ return iter(self._dict.keys())
65
+
66
+ def get(self, key: K, default: V | None = None) -> V:
67
+ """Get with default - completely lock-free."""
68
+ return self._dict.get(key, default)
69
+
70
+ def keys(self) -> KeysView[K]:
71
+ """Get keys - completely lock-free."""
72
+ return self._dict.keys()
73
+
74
+ def values(self) -> ValuesView[V]:
75
+ """Get values - completely lock-free."""
76
+ return self._dict.values()
77
+
78
+ def items(self) -> ItemsView[K, V]:
79
+ """Get items - completely lock-free."""
80
+ return self._dict.items()
81
+
82
+ def copy(self) -> dict[K, V]:
83
+ """Create a copy - completely lock-free."""
84
+ return self._dict.copy()
85
+
86
+ def update(self, *args, **kwargs) -> None:
87
+ """Update dictionary - uses copy-on-write."""
88
+ with self._write_lock:
89
+ new_dict = self._dict.copy()
90
+ new_dict.update(*args, **kwargs)
91
+ self._dict = new_dict
92
+
93
+ def clear(self) -> None:
94
+ """Clear all items."""
95
+ with self._write_lock:
96
+ self._dict = {}
97
+
98
+ def pop(self, key: K, *args) -> V:
99
+ """Pop item by key."""
100
+ with self._write_lock:
101
+ new_dict = self._dict.copy()
102
+ result = new_dict.pop(key, *args)
103
+ self._dict = new_dict
104
+ return result
105
+
106
+ def setdefault(self, key: K, default: V | None = None) -> V:
107
+ """Set default value for key if not exists."""
108
+ # Fast path for existing keys
109
+ if key in self._dict:
110
+ return self._dict[key]
111
+
112
+ with self._write_lock:
113
+ # Double-check after acquiring lock
114
+ if key in self._dict:
115
+ return self._dict[key]
116
+
117
+ new_dict = self._dict.copy()
118
+ result = new_dict.setdefault(key, default)
119
+ self._dict = new_dict
120
+ return result