jl-ecms-client 0.2.8__py3-none-any.whl → 0.2.22__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 jl-ecms-client might be problematic. Click here for more details.

Files changed (40) hide show
  1. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.22.dist-info}/METADATA +6 -1
  2. jl_ecms_client-0.2.22.dist-info/RECORD +67 -0
  3. mirix/__init__.py +41 -0
  4. mirix/client/client.py +1 -1
  5. mirix/constants.py +251 -0
  6. mirix/errors.py +238 -0
  7. mirix/functions/__init__.py +0 -0
  8. mirix/functions/ast_parsers.py +113 -0
  9. mirix/functions/function_sets/__init__.py +1 -0
  10. mirix/functions/function_sets/base.py +330 -0
  11. mirix/functions/function_sets/extras.py +271 -0
  12. mirix/functions/function_sets/memory_tools.py +933 -0
  13. mirix/functions/functions.py +199 -0
  14. mirix/functions/helpers.py +311 -0
  15. mirix/functions/schema_generator.py +511 -0
  16. mirix/helpers/json_helpers.py +3 -3
  17. mirix/log.py +163 -0
  18. mirix/schemas/agent.py +1 -1
  19. mirix/schemas/block.py +1 -1
  20. mirix/schemas/embedding_config.py +0 -3
  21. mirix/schemas/enums.py +12 -0
  22. mirix/schemas/episodic_memory.py +1 -1
  23. mirix/schemas/knowledge_vault.py +1 -1
  24. mirix/schemas/memory.py +1 -1
  25. mirix/schemas/message.py +1 -1
  26. mirix/schemas/mirix_request.py +1 -1
  27. mirix/schemas/procedural_memory.py +1 -1
  28. mirix/schemas/providers.py +1 -1
  29. mirix/schemas/resource_memory.py +1 -1
  30. mirix/schemas/sandbox_config.py +1 -3
  31. mirix/schemas/semantic_memory.py +1 -1
  32. mirix/schemas/tool.py +241 -241
  33. mirix/schemas/user.py +3 -3
  34. mirix/settings.py +280 -0
  35. mirix/system.py +261 -0
  36. jl_ecms_client-0.2.8.dist-info/RECORD +0 -53
  37. mirix/client/constants.py +0 -60
  38. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.22.dist-info}/WHEEL +0 -0
  39. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.22.dist-info}/licenses/LICENSE +0 -0
  40. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.22.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,933 @@
1
+ import re
2
+ import os
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from copy import deepcopy
5
+ from typing import List, Optional
6
+
7
+ from mirix.agent import Agent, AgentState
8
+ from mirix.schemas.episodic_memory import EpisodicEventForLLM
9
+ from mirix.schemas.knowledge_vault import KnowledgeVaultItemBase
10
+ from mirix.schemas.mirix_message_content import TextContent
11
+ from mirix.schemas.procedural_memory import ProceduralMemoryItemBase
12
+ from mirix.schemas.resource_memory import ResourceMemoryItemBase
13
+ from mirix.schemas.semantic_memory import SemanticMemoryItemBase
14
+
15
+
16
+ def core_memory_append(
17
+ self: "Agent", agent_state: "AgentState", label: str, content: str
18
+ ) -> Optional[str]: # type: ignore
19
+ """
20
+ Append to the contents of core memory. The content will be appended to the end of the block with the given label. If you hit the limit, you can use `core_memory_rewrite` to rewrite the entire block to shorten the content. Note that "Line n:" is only for your visualization of the memory, and you should not include it in the content.
21
+
22
+ Args:
23
+ label (str): Section of the memory to be edited (persona or human).
24
+ content (str): Content to write to the memory. All unicode (including emojis) are supported.
25
+
26
+ Returns:
27
+ Optional[str]: None is always returned as this function does not produce a response.
28
+ """
29
+ # check if the content starts with something like "Line n:" (here n is a number) using regex
30
+ if re.match(r"^Line \d+:", content):
31
+ raise ValueError(
32
+ "You should not include 'Line n:' (here n is a number) in the content."
33
+ )
34
+
35
+ current_value = str(agent_state.memory.get_block(label).value)
36
+ new_value = (current_value + "\n" + str(content)).strip()
37
+ agent_state.memory.update_block_value(label=label, value=new_value)
38
+ return None
39
+
40
+
41
+ def core_memory_rewrite(
42
+ self: "Agent", agent_state: "AgentState", label: str, content: str
43
+ ) -> Optional[str]: # type: ignore
44
+ """
45
+ Rewrite the entire content of block <label> in core memory. The entire content in that block will be replaced with the new content. If the old content is full, and you have to rewrite the entire content, make sure to be extremely concise and make it shorter than 20% of the limit.
46
+
47
+ Args:
48
+ label (str): Section of the memory to be edited (persona or human).
49
+ content (str): Content to write to the memory. All unicode (including emojis) are supported.
50
+ Returns:
51
+ Optional[str]: None is always returned as this function does not produce a response.
52
+ """
53
+ current_value = str(agent_state.memory.get_block(label).value)
54
+ new_value = content.strip()
55
+ if current_value != new_value:
56
+ agent_state.memory.update_block_value(label=label, value=new_value)
57
+ return None
58
+
59
+
60
+ def episodic_memory_insert(self: "Agent", items: List[EpisodicEventForLLM]):
61
+ """
62
+ The tool to update episodic memory. The item being inserted into the episodic memory is an event either happened on the user or the assistant.
63
+
64
+ Args:
65
+ items (array): List of episodic memory items to insert.
66
+
67
+ Returns:
68
+ Optional[str]: None is always returned as this function does not produce a response.
69
+ """
70
+ agent_id = (
71
+ self.agent_state.parent_id
72
+ if self.agent_state.parent_id is not None
73
+ else self.agent_state.id
74
+ )
75
+
76
+ # Get filter_tags and use_cache from agent instance
77
+ filter_tags = getattr(self, 'filter_tags', None)
78
+ use_cache = getattr(self, 'use_cache', True)
79
+
80
+ for item in items:
81
+ self.episodic_memory_manager.insert_event(
82
+ actor=self.user,
83
+ agent_state=self.agent_state,
84
+ agent_id=agent_id,
85
+ timestamp=item["occurred_at"],
86
+ event_type=item["event_type"],
87
+ event_actor=item["actor"],
88
+ summary=item["summary"],
89
+ details=item["details"],
90
+ organization_id=self.user.organization_id,
91
+ filter_tags=filter_tags if filter_tags else None,
92
+ use_cache=use_cache,
93
+ )
94
+ response = "Events inserted! Now you need to check if there are repeated events shown in the system prompt."
95
+ return response
96
+
97
+
98
+ def episodic_memory_merge(
99
+ self: "Agent",
100
+ event_id: str,
101
+ combined_summary: str = None,
102
+ combined_details: str = None,
103
+ ):
104
+ """
105
+ The tool to merge the new episodic event into the selected episodic event by event_id, should be used when the user is continuing doing the same thing with more details. The combined_summary and combined_details will overwrite the old summary and details of the selected episodic event. Thus DO NOT use "User continues xxx" as the combined_summary because the old one WILL BE OVERWRITTEN and then we can only see "User continus xxx" without the old event.
106
+
107
+ Args:
108
+ event_id (str): This is the id of which episodic event to append to.
109
+ combined_summary (str): The updated summary. Note that it will overwrite the old summary so make sure to include the information from the old summary. The new summary needs to be only slightly different from the old summary.
110
+ combined_details (str): The new details to add into the details of the selected episodic event.
111
+
112
+ Returns:
113
+ Optional[str]: None is always returned as this function does not produce a response.
114
+ """
115
+
116
+ episodic_memory = self.episodic_memory_manager.update_event(
117
+ event_id=event_id,
118
+ new_summary=combined_summary,
119
+ new_details=combined_details,
120
+ actor=self.user,
121
+ )
122
+ response = (
123
+ "These are the `summary` and the `details` of the updated event:\n",
124
+ str(
125
+ {
126
+ "event_id": episodic_memory.id,
127
+ "summary": episodic_memory.summary,
128
+ "details": episodic_memory.details,
129
+ }
130
+ )
131
+ + "\nIf the `details` are too verbose, or the `summary` cannot cover the information in the `details`, call episodic_memory_replace to update this event.",
132
+ )
133
+ return response
134
+
135
+
136
+ def episodic_memory_replace(
137
+ self: "Agent", event_ids: List[str], new_items: List[EpisodicEventForLLM]
138
+ ):
139
+ """
140
+ The tool to replace or delete items in the episodic memory. To replace the memory, set the event_ids to be the ids of the events that needs to be replaced and new_items as the updated events. Note that the number of new items does not need to be the same as the number of event_ids as it is not a one-to-one mapping. To delete the memory, set the event_ids to be the ids of the events that needs to be deleted and new_items as an empty list. To insert new events, use episodic_memory_insert function.
141
+
142
+ Args:
143
+ event_ids (str): The ids of the episodic events to be deleted (or replaced).
144
+ new_items (array): List of new episodic memory items to insert. If this is an empty list, then it means that the items are being deleted.
145
+ """
146
+ agent_id = (
147
+ self.agent_state.parent_id
148
+ if self.agent_state.parent_id is not None
149
+ else self.agent_state.id
150
+ )
151
+
152
+ for event_id in event_ids:
153
+ # It will raise an error if the event_id is not found in the episodic memory.
154
+ self.episodic_memory_manager.get_episodic_memory_by_id(
155
+ event_id, actor=self.user
156
+ )
157
+
158
+ for event_id in event_ids:
159
+ self.episodic_memory_manager.delete_event_by_id(event_id, actor=self.user)
160
+
161
+ for new_item in new_items:
162
+ self.episodic_memory_manager.insert_event(
163
+ actor=self.user,
164
+ agent_state=self.agent_state,
165
+ agent_id=agent_id,
166
+ timestamp=new_item["occurred_at"],
167
+ event_type=new_item["event_type"],
168
+ event_actor=new_item["actor"],
169
+ summary=new_item["summary"],
170
+ details=new_item["details"],
171
+ organization_id=self.user.organization_id,
172
+ )
173
+
174
+
175
+ def check_episodic_memory(
176
+ self: "Agent", event_ids: List[str], timezone_str: str
177
+ ) -> List[EpisodicEventForLLM]:
178
+ """
179
+ The tool to check the episodic memory. This function will return the episodic events with the given event_ids.
180
+
181
+ Args:
182
+ event_ids (str): The ids of the episodic events to be checked.
183
+
184
+ Returns:
185
+ List[EpisodicEventForLLM]: List of episodic events with the given event_ids.
186
+ """
187
+ episodic_memory = [
188
+ self.episodic_memory_manager.get_episodic_memory_by_id(
189
+ event_id, timezone_str=timezone_str, actor=self.user
190
+ )
191
+ for event_id in event_ids
192
+ ]
193
+
194
+ formatted_results = [
195
+ {
196
+ "event_id": x.id,
197
+ "timestamp": x.occurred_at,
198
+ "event_type": x.event_type,
199
+ "actor": x.actor,
200
+ "summary": x.summary,
201
+ "details": x.details,
202
+ }
203
+ for x in episodic_memory
204
+ ]
205
+
206
+ return formatted_results
207
+
208
+
209
+ def resource_memory_insert(self: "Agent", items: List[ResourceMemoryItemBase]):
210
+ """
211
+ The tool to insert new items into resource memory.
212
+
213
+ Args:
214
+ items (array): List of resource memory items to insert.
215
+
216
+ Returns:
217
+ Optional[str]: Message about insertion results including any duplicates detected.
218
+ """
219
+ # No imports needed - using agent instance attributes
220
+
221
+ agent_id = (
222
+ self.agent_state.parent_id
223
+ if self.agent_state.parent_id is not None
224
+ else self.agent_state.id
225
+ )
226
+
227
+ # Get filter_tags and use_cache from agent instance
228
+ filter_tags = getattr(self, 'filter_tags', None)
229
+ use_cache = getattr(self, 'use_cache', True)
230
+
231
+ inserted_count = 0
232
+ skipped_count = 0
233
+ skipped_titles = []
234
+
235
+ for item in items:
236
+ # Check for existing similar resources (by title, summary, and filter_tags)
237
+ existing_resources = self.resource_memory_manager.list_resources(
238
+ agent_state=self.agent_state,
239
+ actor=self.user,
240
+ query="", # Get all resources
241
+ limit=1000, # Get enough to check for duplicates
242
+ filter_tags=filter_tags if filter_tags else None,
243
+ use_cache=use_cache,
244
+ )
245
+
246
+ # Check if this resource already exists
247
+ is_duplicate = False
248
+ for existing in existing_resources:
249
+ if (existing.title == item["title"] and
250
+ existing.summary == item["summary"] and
251
+ existing.content == item["content"]):
252
+ is_duplicate = True
253
+ skipped_count += 1
254
+ skipped_titles.append(item["title"])
255
+ break
256
+
257
+ if not is_duplicate:
258
+ self.resource_memory_manager.insert_resource(
259
+ agent_state=self.agent_state,
260
+ agent_id=agent_id,
261
+ title=item["title"],
262
+ summary=item["summary"],
263
+ resource_type=item["resource_type"],
264
+ content=item["content"],
265
+ actor=self.user,
266
+ organization_id=self.user.organization_id,
267
+ filter_tags=filter_tags if filter_tags else None,
268
+ use_cache=use_cache,
269
+ )
270
+ inserted_count += 1
271
+
272
+ # Return feedback message
273
+ if skipped_count > 0:
274
+ skipped_list = ", ".join(f"'{t}'" for t in skipped_titles[:3])
275
+ if len(skipped_titles) > 3:
276
+ skipped_list += f" and {len(skipped_titles) - 3} more"
277
+ return f"Inserted {inserted_count} new resource(s). Skipped {skipped_count} duplicate(s): {skipped_list}."
278
+ elif inserted_count > 0:
279
+ return f"Successfully inserted {inserted_count} new resource(s)."
280
+ else:
281
+ return "No resources were inserted."
282
+
283
+
284
+ def resource_memory_update(
285
+ self: "Agent", old_ids: List[str], new_items: List[ResourceMemoryItemBase]
286
+ ):
287
+ """
288
+ The tool to update and delete items in the resource memory. To update the memory, set the old_ids to be the ids of the items that needs to be updated and new_items as the updated items. Note that the number of new items does not need to be the same as the number of old ids as it is not a one-to-one mapping. To delete the memory, set the old_ids to be the ids of the items that needs to be deleted and new_items as an empty list.
289
+
290
+ Args:
291
+ old_ids (array): List of ids of the items to be deleted (or updated).
292
+ new_items (array): List of new resource memory items to insert. If this is an empty list, then it means that the items are being deleted.
293
+ """
294
+ agent_id = (
295
+ self.agent_state.parent_id
296
+ if self.agent_state.parent_id is not None
297
+ else self.agent_state.id
298
+ )
299
+
300
+ # Get filter_tags and use_cache from agent instance
301
+ filter_tags = getattr(self, 'filter_tags', None)
302
+ use_cache = getattr(self, 'use_cache', True)
303
+
304
+ for old_id in old_ids:
305
+ self.resource_memory_manager.delete_resource_by_id(
306
+ resource_id=old_id, actor=self.user
307
+ )
308
+
309
+ for item in new_items:
310
+ self.resource_memory_manager.insert_resource(
311
+ agent_state=self.agent_state,
312
+ agent_id=agent_id,
313
+ title=item["title"],
314
+ summary=item["summary"],
315
+ resource_type=item["resource_type"],
316
+ content=item["content"],
317
+ actor=self.user,
318
+ organization_id=self.user.organization_id,
319
+ filter_tags=filter_tags if filter_tags else None,
320
+ use_cache=use_cache,
321
+ )
322
+
323
+
324
+ def procedural_memory_insert(self: "Agent", items: List[ProceduralMemoryItemBase]):
325
+ """
326
+ The tool to insert new procedures into procedural memory. Note that the `summary` should not be a general term such as "guide" or "workflow" but rather a more informative description of the procedure.
327
+
328
+ Args:
329
+ items (array): List of procedural memory items to insert.
330
+
331
+ Returns:
332
+ Optional[str]: Message about insertion results including any duplicates detected.
333
+ """
334
+ agent_id = (
335
+ self.agent_state.parent_id
336
+ if self.agent_state.parent_id is not None
337
+ else self.agent_state.id
338
+ )
339
+
340
+ # Get filter_tags and use_cache from agent instance
341
+ filter_tags = getattr(self, 'filter_tags', None)
342
+ use_cache = getattr(self, 'use_cache', True)
343
+
344
+ inserted_count = 0
345
+ skipped_count = 0
346
+ skipped_summaries = []
347
+
348
+ for item in items:
349
+ # Check for existing similar procedures (by summary and filter_tags)
350
+ existing_procedures = self.procedural_memory_manager.list_procedures(
351
+ agent_state=self.agent_state,
352
+ actor=self.user,
353
+ query="", # Get all procedures
354
+ limit=1000, # Get enough to check for duplicates
355
+ filter_tags=filter_tags if filter_tags else None,
356
+ use_cache=use_cache,
357
+ )
358
+
359
+ # Check if this procedure already exists
360
+ is_duplicate = False
361
+ for existing in existing_procedures:
362
+ if (existing.summary == item["summary"] and
363
+ existing.steps == item["steps"]):
364
+ is_duplicate = True
365
+ skipped_count += 1
366
+ skipped_summaries.append(item["summary"])
367
+ break
368
+
369
+ if not is_duplicate:
370
+ self.procedural_memory_manager.insert_procedure(
371
+ agent_state=self.agent_state,
372
+ agent_id=agent_id,
373
+ entry_type=item["entry_type"],
374
+ summary=item["summary"],
375
+ steps=item["steps"],
376
+ actor=self.user,
377
+ organization_id=self.user.organization_id,
378
+ filter_tags=filter_tags if filter_tags else None,
379
+ use_cache=use_cache,
380
+ )
381
+ inserted_count += 1
382
+
383
+ # Return feedback message
384
+ if skipped_count > 0:
385
+ skipped_list = ", ".join(f"'{s}'" for s in skipped_summaries[:3])
386
+ if len(skipped_summaries) > 3:
387
+ skipped_list += f" and {len(skipped_summaries) - 3} more"
388
+ return f"Inserted {inserted_count} new procedure(s). Skipped {skipped_count} duplicate(s): {skipped_list}."
389
+ elif inserted_count > 0:
390
+ return f"Successfully inserted {inserted_count} new procedure(s)."
391
+ else:
392
+ return "No procedures were inserted."
393
+
394
+
395
+ def procedural_memory_update(
396
+ self: "Agent", old_ids: List[str], new_items: List[ProceduralMemoryItemBase]
397
+ ):
398
+ """
399
+ The tool to update/delete items in the procedural memory. To update the memory, set the old_ids to be the ids of the items that needs to be updated and new_items as the updated items. Note that the number of new items does not need to be the same as the number of old ids as it is not a one-to-one mapping. To delete the memory, set the old_ids to be the ids of the items that needs to be deleted and new_items as an empty list.
400
+
401
+ Args:
402
+ old_ids (array): List of ids of the items to be deleted (or updated).
403
+ new_items (array): List of new procedural memory items to insert. If this is an empty list, then it means that the items are being deleted.
404
+
405
+ Returns:
406
+ Optional[str]: None is always returned as this function does not produce a response.
407
+ """
408
+ agent_id = (
409
+ self.agent_state.parent_id
410
+ if self.agent_state.parent_id is not None
411
+ else self.agent_state.id
412
+ )
413
+
414
+ # Get filter_tags and use_cache from agent instance
415
+ filter_tags = getattr(self, 'filter_tags', None)
416
+ use_cache = getattr(self, 'use_cache', True)
417
+
418
+ for old_id in old_ids:
419
+ self.procedural_memory_manager.delete_procedure_by_id(
420
+ procedure_id=old_id, actor=self.user
421
+ )
422
+
423
+ for item in new_items:
424
+ self.procedural_memory_manager.insert_procedure(
425
+ agent_state=self.agent_state,
426
+ agent_id=agent_id,
427
+ entry_type=item["entry_type"],
428
+ summary=item["summary"],
429
+ steps=item["steps"],
430
+ actor=self.user,
431
+ organization_id=self.user.organization_id,
432
+ filter_tags=filter_tags if filter_tags else None,
433
+ use_cache=use_cache,
434
+ )
435
+
436
+
437
+ def check_semantic_memory(
438
+ self: "Agent", semantic_item_ids: List[str], timezone_str: str
439
+ ) -> List[SemanticMemoryItemBase]:
440
+ """
441
+ The tool to check the semantic memory. This function will return the semantic memory items with the given ids.
442
+
443
+ Args:
444
+ semantic_item_ids (str): The ids of the semantic memory items to be checked.
445
+
446
+ Returns:
447
+ List[SemanticMemoryItemBase]: List of semantic memory items with the given ids.
448
+ """
449
+ semantic_memory = [
450
+ self.semantic_memory_manager.get_semantic_item_by_id(
451
+ semantic_memory_id=id, timezone_str=timezone_str, actor=self.user
452
+ )
453
+ for id in semantic_item_ids
454
+ ]
455
+
456
+ formatted_results = [
457
+ {
458
+ "semantic_item_id": x.id,
459
+ "name": x.name,
460
+ "summary": x.summary,
461
+ "details": x.details,
462
+ "source": x.source,
463
+ }
464
+ for x in semantic_memory
465
+ ]
466
+
467
+ return formatted_results
468
+
469
+
470
+ def semantic_memory_insert(self: "Agent", items: List[SemanticMemoryItemBase]):
471
+ """
472
+ The tool to insert items into semantic memory.
473
+
474
+ Args:
475
+ items (array): List of semantic memory items to insert.
476
+
477
+ Returns:
478
+ Optional[str]: Message about insertion results including any duplicates detected.
479
+ """
480
+ agent_id = (
481
+ self.agent_state.parent_id
482
+ if self.agent_state.parent_id is not None
483
+ else self.agent_state.id
484
+ )
485
+
486
+ # Get filter_tags and use_cache from agent instance
487
+ filter_tags = getattr(self, 'filter_tags', None)
488
+ use_cache = getattr(self, 'use_cache', True)
489
+
490
+ inserted_count = 0
491
+ skipped_count = 0
492
+ skipped_names = []
493
+
494
+ for item in items:
495
+ # Check for existing similar semantic items (by name, summary, and filter_tags)
496
+ existing_items = self.semantic_memory_manager.list_semantic_items(
497
+ agent_state=self.agent_state,
498
+ actor=self.user,
499
+ query="", # Get all items
500
+ limit=1000, # Get enough to check for duplicates
501
+ filter_tags=filter_tags if filter_tags else None,
502
+ use_cache=use_cache,
503
+ )
504
+
505
+ # Check if this semantic item already exists
506
+ is_duplicate = False
507
+ for existing in existing_items:
508
+ if (existing.name == item["name"] and
509
+ existing.summary == item["summary"] and
510
+ existing.details == item["details"]):
511
+ is_duplicate = True
512
+ skipped_count += 1
513
+ skipped_names.append(item["name"])
514
+ break
515
+
516
+ if not is_duplicate:
517
+ self.semantic_memory_manager.insert_semantic_item(
518
+ agent_state=self.agent_state,
519
+ agent_id=agent_id,
520
+ name=item["name"],
521
+ summary=item["summary"],
522
+ details=item["details"],
523
+ source=item["source"],
524
+ organization_id=self.user.organization_id,
525
+ actor=self.user,
526
+ filter_tags=filter_tags if filter_tags else None,
527
+ use_cache=use_cache,
528
+ )
529
+ inserted_count += 1
530
+
531
+ # Return feedback message
532
+ if skipped_count > 0:
533
+ skipped_list = ", ".join(f"'{n}'" for n in skipped_names[:3])
534
+ if len(skipped_names) > 3:
535
+ skipped_list += f" and {len(skipped_names) - 3} more"
536
+ return f"Inserted {inserted_count} new semantic item(s). Skipped {skipped_count} duplicate(s): {skipped_list}."
537
+ elif inserted_count > 0:
538
+ return f"Successfully inserted {inserted_count} new semantic item(s)."
539
+ else:
540
+ return "No semantic items were inserted."
541
+
542
+
543
+ def semantic_memory_update(
544
+ self: "Agent",
545
+ old_semantic_item_ids: List[str],
546
+ new_items: List[SemanticMemoryItemBase],
547
+ ):
548
+ """
549
+ The tool to update/delete items in the semantic memory. To update the memory, set the old_ids to be the ids of the items that needs to be updated and new_items as the updated items. Note that the number of new items does not need to be the same as the number of old ids as it is not a one-to-one mapping. To delete the memory, set the old_ids to be the ids of the items that needs to be deleted and new_items as an empty list.
550
+
551
+ Args:
552
+ old_semantic_item_ids (array): List of ids of the items to be deleted (or updated).
553
+ new_items (array): List of new semantic memory items to insert. If this is an empty list, then it means that the items are being deleted.
554
+
555
+ Returns:
556
+ Optional[str]: None is always returned as this function does not produce a response.
557
+ """
558
+ agent_id = (
559
+ self.agent_state.parent_id
560
+ if self.agent_state.parent_id is not None
561
+ else self.agent_state.id
562
+ )
563
+
564
+ # Get filter_tags and use_cache from agent instance
565
+ filter_tags = getattr(self, 'filter_tags', None)
566
+ use_cache = getattr(self, 'use_cache', True)
567
+
568
+ for old_id in old_semantic_item_ids:
569
+ self.semantic_memory_manager.delete_semantic_item_by_id(
570
+ semantic_memory_id=old_id, actor=self.user
571
+ )
572
+
573
+ new_ids = []
574
+ for item in new_items:
575
+ inserted_item = self.semantic_memory_manager.insert_semantic_item(
576
+ agent_state=self.agent_state,
577
+ agent_id=agent_id,
578
+ name=item["name"],
579
+ summary=item["summary"],
580
+ details=item["details"],
581
+ source=item["source"],
582
+ actor=self.user,
583
+ organization_id=self.user.organization_id,
584
+ filter_tags=filter_tags if filter_tags else None,
585
+ use_cache=use_cache,
586
+ )
587
+ new_ids.append(inserted_item.id)
588
+
589
+ message_to_return = (
590
+ "Semantic memory with the following ids have been deleted: "
591
+ + str(old_semantic_item_ids)
592
+ + f". New semantic memory items are created: {str(new_ids)}"
593
+ )
594
+ return message_to_return
595
+
596
+
597
+ def knowledge_vault_insert(self: "Agent", items: List[KnowledgeVaultItemBase]):
598
+ """
599
+ The tool to update knowledge vault.
600
+
601
+ Args:
602
+ items (array): List of knowledge vault items to insert.
603
+
604
+ Returns:
605
+ Optional[str]: Message about insertion results including any duplicates detected.
606
+ """
607
+ agent_id = (
608
+ self.agent_state.parent_id
609
+ if self.agent_state.parent_id is not None
610
+ else self.agent_state.id
611
+ )
612
+
613
+ # Get filter_tags and use_cache from agent instance
614
+ filter_tags = getattr(self, 'filter_tags', None)
615
+ use_cache = getattr(self, 'use_cache', True)
616
+
617
+ inserted_count = 0
618
+ skipped_count = 0
619
+ skipped_captions = []
620
+
621
+ for item in items:
622
+ # Check for existing similar knowledge vault items (by caption, source, and filter_tags)
623
+ existing_items = self.knowledge_vault_manager.list_knowledge(
624
+ agent_state=self.agent_state,
625
+ actor=self.user,
626
+ query="", # Get all items
627
+ limit=1000, # Get enough to check for duplicates
628
+ filter_tags=filter_tags if filter_tags else None,
629
+ use_cache=use_cache,
630
+ )
631
+
632
+ # Check if this knowledge vault item already exists
633
+ is_duplicate = False
634
+ for existing in existing_items:
635
+ if (existing.caption == item["caption"] and
636
+ existing.source == item["source"] and
637
+ existing.secret_value == item["secret_value"]):
638
+ is_duplicate = True
639
+ skipped_count += 1
640
+ skipped_captions.append(item["caption"])
641
+ break
642
+
643
+ if not is_duplicate:
644
+ self.knowledge_vault_manager.insert_knowledge(
645
+ agent_state=self.agent_state,
646
+ agent_id=agent_id,
647
+ entry_type=item["entry_type"],
648
+ source=item["source"],
649
+ sensitivity=item["sensitivity"],
650
+ secret_value=item["secret_value"],
651
+ caption=item["caption"],
652
+ actor=self.user,
653
+ organization_id=self.user.organization_id,
654
+ filter_tags=filter_tags if filter_tags else None,
655
+ use_cache=use_cache,
656
+ )
657
+ inserted_count += 1
658
+
659
+ # Return feedback message
660
+ if skipped_count > 0:
661
+ skipped_list = ", ".join(f"'{c}'" for c in skipped_captions[:3])
662
+ if len(skipped_captions) > 3:
663
+ skipped_list += f" and {len(skipped_captions) - 3} more"
664
+ return f"Inserted {inserted_count} new knowledge vault item(s). Skipped {skipped_count} duplicate(s): {skipped_list}."
665
+ elif inserted_count > 0:
666
+ return f"Successfully inserted {inserted_count} new knowledge vault item(s)."
667
+ else:
668
+ return "No knowledge vault items were inserted."
669
+
670
+
671
+ def knowledge_vault_update(
672
+ self: "Agent", old_ids: List[str], new_items: List[KnowledgeVaultItemBase]
673
+ ):
674
+ """
675
+ The tool to update/delete items in the knowledge vault. To update the knowledge_vault, set the old_ids to be the ids of the items that needs to be updated and new_items as the updated items. Note that the number of new items does not need to be the same as the number of old ids as it is not a one-to-one mapping. To delete the memory, set the old_ids to be the ids of the items that needs to be deleted and new_items as an empty list.
676
+
677
+ Args:
678
+ old_ids (array): List of ids of the items to be deleted (or updated).
679
+ new_items (array): List of new knowledge vault items to insert. If this is an empty list, then it means that the items are being deleted.
680
+
681
+ Returns:
682
+ Optional[str]: None is always returned as this function does not produce a response
683
+ """
684
+ agent_id = (
685
+ self.agent_state.parent_id
686
+ if self.agent_state.parent_id is not None
687
+ else self.agent_state.id
688
+ )
689
+
690
+ # Get filter_tags and use_cache from agent instance
691
+ filter_tags = getattr(self, 'filter_tags', None)
692
+ use_cache = getattr(self, 'use_cache', True)
693
+
694
+ for old_id in old_ids:
695
+ self.knowledge_vault_manager.delete_knowledge_by_id(
696
+ knowledge_vault_item_id=old_id, actor=self.user
697
+ )
698
+
699
+ for item in new_items:
700
+ self.knowledge_vault_manager.insert_knowledge(
701
+ agent_state=self.agent_state,
702
+ agent_id=agent_id,
703
+ entry_type=item["entry_type"],
704
+ source=item["source"],
705
+ sensitivity=item["sensitivity"],
706
+ secret_value=item["secret_value"],
707
+ caption=item["caption"],
708
+ actor=self.user,
709
+ organization_id=self.user.organization_id,
710
+ filter_tags=filter_tags if filter_tags else None,
711
+ use_cache=use_cache,
712
+ )
713
+
714
+
715
+ def trigger_memory_update_with_instruction(
716
+ self: "Agent", user_message: object, instruction: str, memory_type: str
717
+ ) -> Optional[str]:
718
+ """
719
+ Choose which memory to update. The function will trigger one specific memory agent with the instruction telling the agent what to do.
720
+
721
+ Args:
722
+ instruction (str): The instruction to the memory agent.
723
+ memory_type (str): The type of memory to update. It should be chosen from the following: "core", "episodic", "resource", "procedural", "knowledge_vault", "semantic". For instance, ['episodic', 'resource'].
724
+
725
+ Returns:
726
+ Optional[str]: None is always returned as this function does not produce a response.
727
+ """
728
+
729
+ from mirix import create_client
730
+
731
+ client = create_client()
732
+ agents = client.list_agents()
733
+
734
+ # Validate that user_message is a dictionary
735
+ if not isinstance(user_message, dict):
736
+ raise TypeError(
737
+ f"user_message must be a dictionary, got {type(user_message).__name__}: {user_message}"
738
+ )
739
+
740
+ # Fallback to sequential processing for backward compatibility
741
+ response = ""
742
+
743
+ if memory_type == "core":
744
+ agent_type = "core_memory_agent"
745
+ elif memory_type == "episodic":
746
+ agent_type = "episodic_memory_agent"
747
+ elif memory_type == "resource":
748
+ agent_type = "resource_memory_agent"
749
+ elif memory_type == "procedural":
750
+ agent_type = "procedural_memory_agent"
751
+ elif memory_type == "knowledge_vault":
752
+ agent_type = "knowledge_vault_memory_agent"
753
+ elif memory_type == "semantic":
754
+ agent_type = "semantic_memory_agent"
755
+ else:
756
+ raise ValueError(
757
+ f"Memory type '{memory_type}' is not supported. Please choose from 'core', 'episodic', 'resource', 'procedural', 'knowledge_vault', 'semantic'."
758
+ )
759
+
760
+ matching_agent = None
761
+ for agent in agents:
762
+ if agent.agent_type == agent_type:
763
+ matching_agent = agent
764
+ break
765
+
766
+ if matching_agent is None:
767
+ raise ValueError(f"No agent found with type '{agent_type}'")
768
+
769
+ client.send_message(
770
+ role="user",
771
+ user_id=self.user.id,
772
+ agent_id=matching_agent.id,
773
+ message="[Message from Chat Agent (Now you are allowed to make multiple function calls sequentially)] "
774
+ + instruction,
775
+ existing_file_uris=user_message["existing_file_uris"],
776
+ retrieved_memories=user_message.get("retrieved_memories", None),
777
+ )
778
+ response += (
779
+ "[System Message] Agent "
780
+ + matching_agent.name
781
+ + " has been triggered to update the memory.\n"
782
+ )
783
+
784
+ return response.strip()
785
+
786
+
787
+ def trigger_memory_update(
788
+ self: "Agent", user_message: object, memory_types: List[str]
789
+ ) -> Optional[str]:
790
+ """
791
+ Choose which memory to update. This function will trigger another memory agent which is specifically in charge of handling the corresponding memory to update its memory. Trigger all necessary memory updates at once. Put the explanations in the `internal_monologue` field.
792
+
793
+ Args:
794
+ memory_types (List[str]): The types of memory to update. It should be chosen from the following: "core", "episodic", "resource", "procedural", "knowledge_vault", "semantic". For instance, ['episodic', 'resource'].
795
+
796
+ Returns:
797
+ Optional[str]: None is always returned as this function does not produce a response.
798
+ """
799
+
800
+ from mirix.agent import (
801
+ CoreMemoryAgent,
802
+ EpisodicMemoryAgent,
803
+ KnowledgeVaultAgent,
804
+ ProceduralMemoryAgent,
805
+ ResourceMemoryAgent,
806
+ SemanticMemoryAgent,
807
+ )
808
+
809
+ # Validate that user_message is a dictionary
810
+ if not isinstance(user_message, dict):
811
+ raise TypeError(
812
+ f"user_message must be a dictionary, got {type(user_message).__name__}: {user_message}"
813
+ )
814
+
815
+ # Map memory types to agent classes
816
+ memory_type_to_agent_class = {
817
+ "core": CoreMemoryAgent,
818
+ "episodic": EpisodicMemoryAgent,
819
+ "resource": ResourceMemoryAgent,
820
+ "procedural": ProceduralMemoryAgent,
821
+ "knowledge_vault": KnowledgeVaultAgent,
822
+ "semantic": SemanticMemoryAgent,
823
+ }
824
+
825
+ # Validate memory types
826
+ for memory_type in memory_types:
827
+ if memory_type not in memory_type_to_agent_class:
828
+ raise ValueError(
829
+ f"Memory type '{memory_type}' is not supported. Please choose from 'core', 'episodic', 'resource', 'procedural', 'knowledge_vault', 'semantic'."
830
+ )
831
+
832
+ # Get child agents
833
+ child_agent_states = self.agent_manager.list_agents(parent_id=self.agent_state.id, actor=self.user)
834
+
835
+ # Map agent types to agent states
836
+ agent_type_to_state = {
837
+ agent_state.agent_type: agent_state for agent_state in child_agent_states
838
+ }
839
+
840
+ def _run_single_memory_update(memory_type: str) -> str:
841
+ agent_class = memory_type_to_agent_class[memory_type]
842
+ agent_type_str = f"{memory_type}_memory_agent"
843
+
844
+ agent_state = agent_type_to_state.get(agent_type_str)
845
+ if agent_state is None:
846
+ raise ValueError(f"No agent found with type '{agent_type_str}'")
847
+
848
+ # Get filter_tags and use_cache from parent agent instance
849
+ # Deep copy filter_tags to ensure complete isolation between child agents
850
+ parent_filter_tags = getattr(self, 'filter_tags', None)
851
+ # Don't use 'or {}' because empty dict {} is valid and different from None
852
+ filter_tags = deepcopy(parent_filter_tags) if parent_filter_tags is not None else None
853
+ use_cache = getattr(self, 'use_cache', True)
854
+
855
+ import logging
856
+ logger = logging.getLogger(__name__)
857
+ logger.info(f"🏷️ Creating {memory_type} agent with filter_tags={filter_tags}")
858
+
859
+ memory_agent = agent_class(
860
+ agent_state=agent_state,
861
+ interface=self.interface,
862
+ user=self.user,
863
+ filter_tags=filter_tags,
864
+ use_cache=use_cache,
865
+ )
866
+
867
+ # Work on a copy of the user message so parallel updates do not interfere
868
+ if "message" not in user_message:
869
+ raise KeyError("user_message must contain a 'message' field")
870
+
871
+ if hasattr(user_message["message"], "model_copy"):
872
+ message_copy = user_message["message"].model_copy(deep=True) # type: ignore[attr-defined]
873
+ else:
874
+ message_copy = deepcopy(user_message["message"])
875
+
876
+ system_msg = TextContent(
877
+ text=(
878
+ "[System Message] According to the instructions, the retrieved memories "
879
+ "and the above content, update the corresponding memory."
880
+ )
881
+ )
882
+
883
+ if isinstance(message_copy.content, str):
884
+ message_copy.content = [TextContent(text=message_copy.content), system_msg]
885
+ elif isinstance(message_copy.content, list):
886
+ message_copy.content = list(message_copy.content) + [system_msg]
887
+ else:
888
+ message_copy.content = [system_msg]
889
+
890
+ memory_agent.step(
891
+ input_messages=message_copy,
892
+ chaining=user_message.get("chaining", False),
893
+ )
894
+
895
+ return (
896
+ f"[System Message] Agent {agent_state.name} has been triggered to update the memory.\n"
897
+ )
898
+
899
+ max_workers = min(len(memory_types), max(os.cpu_count() or 1, 1))
900
+ responses: dict[int, str] = {}
901
+
902
+ if not memory_types:
903
+ return ""
904
+
905
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
906
+ future_to_index = {
907
+ executor.submit(_run_single_memory_update, memory_type): index
908
+ for index, memory_type in enumerate(memory_types)
909
+ }
910
+ for future in as_completed(future_to_index):
911
+ index = future_to_index[future]
912
+ memory_type = memory_types[index]
913
+ try:
914
+ responses[index] = future.result()
915
+ except Exception as exc:
916
+ raise RuntimeError(
917
+ f"Failed to trigger memory update for '{memory_type}'"
918
+ ) from exc
919
+
920
+ ordered_responses = [responses[i] for i in range(len(memory_types)) if i in responses]
921
+ return "".join(ordered_responses).strip()
922
+
923
+
924
+ def finish_memory_update(self: "Agent"):
925
+ """
926
+ Finish the memory update process. This function should be called after the Memory is updated.
927
+
928
+ Note: This function takes no parameters. Call it without any arguments.
929
+
930
+ Returns:
931
+ Optional[str]: None is always returned as this function does not produce a response.
932
+ """
933
+ return None