jl-ecms-client 0.2.8__py3-none-any.whl → 0.2.23__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.
- {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/METADATA +6 -1
- jl_ecms_client-0.2.23.dist-info/RECORD +67 -0
- mirix/__init__.py +41 -0
- mirix/client/client.py +1 -1
- mirix/constants.py +251 -0
- mirix/errors.py +238 -0
- mirix/functions/__init__.py +0 -0
- mirix/functions/ast_parsers.py +113 -0
- mirix/functions/function_sets/__init__.py +1 -0
- mirix/functions/function_sets/base.py +330 -0
- mirix/functions/function_sets/extras.py +271 -0
- mirix/functions/function_sets/memory_tools.py +933 -0
- mirix/functions/functions.py +199 -0
- mirix/functions/helpers.py +311 -0
- mirix/functions/schema_generator.py +511 -0
- mirix/helpers/json_helpers.py +3 -3
- mirix/log.py +163 -0
- mirix/schemas/agent.py +1 -1
- mirix/schemas/block.py +1 -1
- mirix/schemas/embedding_config.py +0 -3
- mirix/schemas/enums.py +12 -0
- mirix/schemas/episodic_memory.py +1 -1
- mirix/schemas/knowledge_vault.py +1 -1
- mirix/schemas/memory.py +1 -1
- mirix/schemas/message.py +1 -1
- mirix/schemas/mirix_request.py +1 -1
- mirix/schemas/procedural_memory.py +1 -1
- mirix/schemas/providers.py +1 -1
- mirix/schemas/resource_memory.py +1 -1
- mirix/schemas/sandbox_config.py +1 -3
- mirix/schemas/semantic_memory.py +1 -1
- mirix/schemas/tool.py +241 -241
- mirix/schemas/user.py +3 -3
- mirix/settings.py +280 -0
- mirix/system.py +261 -0
- jl_ecms_client-0.2.8.dist-info/RECORD +0 -53
- mirix/client/constants.py +0 -60
- {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/WHEEL +0 -0
- {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/licenses/LICENSE +0 -0
- {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.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
|