letta-nightly 0.7.30.dev20250603104343__py3-none-any.whl → 0.8.0.dev20250604104349__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.
- letta/__init__.py +7 -1
- letta/agent.py +14 -7
- letta/agents/base_agent.py +1 -0
- letta/agents/ephemeral_summary_agent.py +104 -0
- letta/agents/helpers.py +35 -3
- letta/agents/letta_agent.py +492 -176
- letta/agents/letta_agent_batch.py +22 -16
- letta/agents/prompts/summary_system_prompt.txt +62 -0
- letta/agents/voice_agent.py +22 -7
- letta/agents/voice_sleeptime_agent.py +13 -8
- letta/constants.py +33 -1
- letta/data_sources/connectors.py +52 -36
- letta/errors.py +4 -0
- letta/functions/ast_parsers.py +13 -30
- letta/functions/function_sets/base.py +3 -1
- letta/functions/functions.py +2 -0
- letta/functions/mcp_client/base_client.py +151 -97
- letta/functions/mcp_client/sse_client.py +49 -31
- letta/functions/mcp_client/stdio_client.py +107 -106
- letta/functions/schema_generator.py +22 -22
- letta/groups/helpers.py +3 -4
- letta/groups/sleeptime_multi_agent.py +4 -4
- letta/groups/sleeptime_multi_agent_v2.py +22 -0
- letta/helpers/composio_helpers.py +16 -0
- letta/helpers/converters.py +20 -0
- letta/helpers/datetime_helpers.py +1 -6
- letta/helpers/tool_rule_solver.py +2 -1
- letta/interfaces/anthropic_streaming_interface.py +17 -2
- letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
- letta/interfaces/openai_streaming_interface.py +18 -2
- letta/llm_api/anthropic_client.py +24 -3
- letta/llm_api/google_ai_client.py +0 -15
- letta/llm_api/google_vertex_client.py +6 -5
- letta/llm_api/llm_client_base.py +15 -0
- letta/llm_api/openai.py +2 -2
- letta/llm_api/openai_client.py +60 -8
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +45 -43
- letta/orm/base.py +0 -2
- letta/orm/block.py +1 -0
- letta/orm/custom_columns.py +13 -0
- letta/orm/enums.py +5 -0
- letta/orm/file.py +3 -1
- letta/orm/files_agents.py +68 -0
- letta/orm/mcp_server.py +48 -0
- letta/orm/message.py +1 -0
- letta/orm/organization.py +11 -2
- letta/orm/passage.py +25 -10
- letta/orm/sandbox_config.py +5 -2
- letta/orm/sqlalchemy_base.py +171 -110
- letta/prompts/system/memgpt_base.txt +6 -1
- letta/prompts/system/memgpt_v2_chat.txt +57 -0
- letta/prompts/system/sleeptime.txt +2 -0
- letta/prompts/system/sleeptime_v2.txt +28 -0
- letta/schemas/agent.py +87 -20
- letta/schemas/block.py +7 -1
- letta/schemas/file.py +57 -0
- letta/schemas/mcp.py +74 -0
- letta/schemas/memory.py +5 -2
- letta/schemas/message.py +9 -0
- letta/schemas/openai/openai.py +0 -6
- letta/schemas/providers.py +33 -4
- letta/schemas/tool.py +26 -21
- letta/schemas/tool_execution_result.py +5 -0
- letta/server/db.py +23 -8
- letta/server/rest_api/app.py +73 -56
- letta/server/rest_api/interface.py +4 -4
- letta/server/rest_api/routers/v1/agents.py +132 -47
- letta/server/rest_api/routers/v1/blocks.py +3 -2
- letta/server/rest_api/routers/v1/embeddings.py +3 -3
- letta/server/rest_api/routers/v1/groups.py +3 -3
- letta/server/rest_api/routers/v1/jobs.py +14 -17
- letta/server/rest_api/routers/v1/organizations.py +10 -10
- letta/server/rest_api/routers/v1/providers.py +12 -10
- letta/server/rest_api/routers/v1/runs.py +3 -3
- letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
- letta/server/rest_api/routers/v1/sources.py +108 -43
- letta/server/rest_api/routers/v1/steps.py +8 -6
- letta/server/rest_api/routers/v1/tools.py +134 -95
- letta/server/rest_api/utils.py +12 -1
- letta/server/server.py +272 -73
- letta/services/agent_manager.py +246 -313
- letta/services/block_manager.py +30 -9
- letta/services/context_window_calculator/__init__.py +0 -0
- letta/services/context_window_calculator/context_window_calculator.py +150 -0
- letta/services/context_window_calculator/token_counter.py +82 -0
- letta/services/file_processor/__init__.py +0 -0
- letta/services/file_processor/chunker/__init__.py +0 -0
- letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
- letta/services/file_processor/embedder/__init__.py +0 -0
- letta/services/file_processor/embedder/openai_embedder.py +84 -0
- letta/services/file_processor/file_processor.py +123 -0
- letta/services/file_processor/parser/__init__.py +0 -0
- letta/services/file_processor/parser/base_parser.py +9 -0
- letta/services/file_processor/parser/mistral_parser.py +54 -0
- letta/services/file_processor/types.py +0 -0
- letta/services/files_agents_manager.py +184 -0
- letta/services/group_manager.py +118 -0
- letta/services/helpers/agent_manager_helper.py +76 -21
- letta/services/helpers/tool_execution_helper.py +3 -0
- letta/services/helpers/tool_parser_helper.py +100 -0
- letta/services/identity_manager.py +44 -42
- letta/services/job_manager.py +21 -10
- letta/services/mcp/base_client.py +5 -2
- letta/services/mcp/sse_client.py +3 -5
- letta/services/mcp/stdio_client.py +3 -5
- letta/services/mcp_manager.py +281 -0
- letta/services/message_manager.py +40 -26
- letta/services/organization_manager.py +55 -19
- letta/services/passage_manager.py +211 -13
- letta/services/provider_manager.py +48 -2
- letta/services/sandbox_config_manager.py +105 -0
- letta/services/source_manager.py +4 -5
- letta/services/step_manager.py +9 -6
- letta/services/summarizer/summarizer.py +50 -23
- letta/services/telemetry_manager.py +7 -0
- letta/services/tool_executor/tool_execution_manager.py +11 -52
- letta/services/tool_executor/tool_execution_sandbox.py +4 -34
- letta/services/tool_executor/tool_executor.py +107 -105
- letta/services/tool_manager.py +56 -17
- letta/services/tool_sandbox/base.py +39 -92
- letta/services/tool_sandbox/e2b_sandbox.py +16 -11
- letta/services/tool_sandbox/local_sandbox.py +51 -23
- letta/services/user_manager.py +36 -3
- letta/settings.py +10 -3
- letta/templates/__init__.py +0 -0
- letta/templates/sandbox_code_file.py.j2 +47 -0
- letta/templates/template_helper.py +16 -0
- letta/tracing.py +30 -1
- letta/types/__init__.py +7 -0
- letta/utils.py +25 -1
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/METADATA +7 -2
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/RECORD +136 -110
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604104349.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,8 @@ from typing import Any, Dict, List, Literal, Optional
|
|
9
9
|
from letta.constants import (
|
10
10
|
COMPOSIO_ENTITY_ENV_VAR_KEY,
|
11
11
|
CORE_MEMORY_LINE_NUMBER_WARNING,
|
12
|
+
MCP_TOOL_TAG_NAME_PREFIX,
|
13
|
+
MEMORY_TOOLS_LINE_NUMBER_PREFIX_REGEX,
|
12
14
|
READ_ONLY_BLOCK_EDIT_ERROR,
|
13
15
|
RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE,
|
14
16
|
WEB_SEARCH_CLIP_CONTENT,
|
@@ -17,7 +19,7 @@ from letta.constants import (
|
|
17
19
|
)
|
18
20
|
from letta.functions.ast_parsers import coerce_dict_args_by_annotations, get_function_annotations_from_source
|
19
21
|
from letta.functions.composio_helpers import execute_composio_action_async, generate_composio_action_from_func_name
|
20
|
-
from letta.helpers.composio_helpers import
|
22
|
+
from letta.helpers.composio_helpers import get_composio_api_key_async
|
21
23
|
from letta.helpers.json_helpers import json_dumps
|
22
24
|
from letta.log import get_logger
|
23
25
|
from letta.schemas.agent import AgentState
|
@@ -31,12 +33,14 @@ from letta.schemas.tool_execution_result import ToolExecutionResult
|
|
31
33
|
from letta.schemas.user import User
|
32
34
|
from letta.services.agent_manager import AgentManager
|
33
35
|
from letta.services.block_manager import BlockManager
|
36
|
+
from letta.services.mcp_manager import MCPManager
|
34
37
|
from letta.services.message_manager import MessageManager
|
35
38
|
from letta.services.passage_manager import PassageManager
|
36
39
|
from letta.services.tool_sandbox.e2b_sandbox import AsyncToolSandboxE2B
|
37
40
|
from letta.services.tool_sandbox.local_sandbox import AsyncToolSandboxLocal
|
38
41
|
from letta.settings import tool_settings
|
39
42
|
from letta.tracing import trace_method
|
43
|
+
from letta.types import JsonDict
|
40
44
|
from letta.utils import get_friendly_error_msg
|
41
45
|
|
42
46
|
logger = get_logger(__name__)
|
@@ -60,13 +64,13 @@ class ToolExecutor(ABC):
|
|
60
64
|
self.actor = actor
|
61
65
|
|
62
66
|
@abstractmethod
|
63
|
-
def execute(
|
67
|
+
async def execute(
|
64
68
|
self,
|
65
69
|
function_name: str,
|
66
70
|
function_args: dict,
|
67
|
-
agent_state: AgentState,
|
68
71
|
tool: Tool,
|
69
72
|
actor: User,
|
73
|
+
agent_state: Optional[AgentState] = None,
|
70
74
|
sandbox_config: Optional[SandboxConfig] = None,
|
71
75
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
72
76
|
) -> ToolExecutionResult:
|
@@ -76,17 +80,18 @@ class ToolExecutor(ABC):
|
|
76
80
|
class LettaCoreToolExecutor(ToolExecutor):
|
77
81
|
"""Executor for LETTA core tools with direct implementation of functions."""
|
78
82
|
|
79
|
-
def execute(
|
83
|
+
async def execute(
|
80
84
|
self,
|
81
85
|
function_name: str,
|
82
86
|
function_args: dict,
|
83
|
-
agent_state: AgentState,
|
84
87
|
tool: Tool,
|
85
88
|
actor: User,
|
89
|
+
agent_state: Optional[AgentState] = None,
|
86
90
|
sandbox_config: Optional[SandboxConfig] = None,
|
87
91
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
88
92
|
) -> ToolExecutionResult:
|
89
93
|
# Map function names to method calls
|
94
|
+
assert agent_state is not None, "Agent state is required for core tools"
|
90
95
|
function_map = {
|
91
96
|
"send_message": self.send_message,
|
92
97
|
"conversation_search": self.conversation_search,
|
@@ -105,13 +110,22 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
105
110
|
|
106
111
|
# Execute the appropriate function
|
107
112
|
function_args_copy = function_args.copy() # Make a copy to avoid modifying the original
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
+
try:
|
114
|
+
function_response = await function_map[function_name](agent_state, actor, **function_args_copy)
|
115
|
+
return ToolExecutionResult(
|
116
|
+
status="success",
|
117
|
+
func_return=function_response,
|
118
|
+
agent_state=agent_state,
|
119
|
+
)
|
120
|
+
except Exception as e:
|
121
|
+
return ToolExecutionResult(
|
122
|
+
status="error",
|
123
|
+
func_return=e,
|
124
|
+
agent_state=agent_state,
|
125
|
+
stderr=[get_friendly_error_msg(function_name=function_name, exception_name=type(e).__name__, exception_message=str(e))],
|
126
|
+
)
|
113
127
|
|
114
|
-
def send_message(self, agent_state: AgentState, actor: User, message: str) -> Optional[str]:
|
128
|
+
async def send_message(self, agent_state: AgentState, actor: User, message: str) -> Optional[str]:
|
115
129
|
"""
|
116
130
|
Sends a message to the human user.
|
117
131
|
|
@@ -123,7 +137,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
123
137
|
"""
|
124
138
|
return "Sent message successfully."
|
125
139
|
|
126
|
-
def conversation_search(self, agent_state: AgentState, actor: User, query: str, page: Optional[int] = 0) -> Optional[str]:
|
140
|
+
async def conversation_search(self, agent_state: AgentState, actor: User, query: str, page: Optional[int] = 0) -> Optional[str]:
|
127
141
|
"""
|
128
142
|
Search prior conversation history using case-insensitive string matching.
|
129
143
|
|
@@ -142,7 +156,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
142
156
|
raise ValueError(f"'page' argument must be an integer")
|
143
157
|
|
144
158
|
count = RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE
|
145
|
-
messages = MessageManager().
|
159
|
+
messages = await MessageManager().list_user_messages_for_agent_async(
|
146
160
|
agent_id=agent_state.id,
|
147
161
|
actor=actor,
|
148
162
|
query_text=query,
|
@@ -161,7 +175,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
161
175
|
|
162
176
|
return results_str
|
163
177
|
|
164
|
-
def archival_memory_search(
|
178
|
+
async def archival_memory_search(
|
165
179
|
self, agent_state: AgentState, actor: User, query: str, page: Optional[int] = 0, start: Optional[int] = 0
|
166
180
|
) -> Optional[str]:
|
167
181
|
"""
|
@@ -186,7 +200,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
186
200
|
|
187
201
|
try:
|
188
202
|
# Get results using passage manager
|
189
|
-
all_results = AgentManager().
|
203
|
+
all_results = await AgentManager().list_passages_async(
|
190
204
|
actor=actor,
|
191
205
|
agent_id=agent_state.id,
|
192
206
|
query_text=query,
|
@@ -207,7 +221,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
207
221
|
except Exception as e:
|
208
222
|
raise e
|
209
223
|
|
210
|
-
def archival_memory_insert(self, agent_state: AgentState, actor: User, content: str) -> Optional[str]:
|
224
|
+
async def archival_memory_insert(self, agent_state: AgentState, actor: User, content: str) -> Optional[str]:
|
211
225
|
"""
|
212
226
|
Add to archival memory. Make sure to phrase the memory contents such that it can be easily queried later.
|
213
227
|
|
@@ -217,16 +231,16 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
217
231
|
Returns:
|
218
232
|
Optional[str]: None is always returned as this function does not produce a response.
|
219
233
|
"""
|
220
|
-
PassageManager().
|
234
|
+
await PassageManager().insert_passage_async(
|
221
235
|
agent_state=agent_state,
|
222
236
|
agent_id=agent_state.id,
|
223
237
|
text=content,
|
224
238
|
actor=actor,
|
225
239
|
)
|
226
|
-
AgentManager().
|
240
|
+
await AgentManager().rebuild_system_prompt_async(agent_id=agent_state.id, actor=actor, force=True)
|
227
241
|
return None
|
228
242
|
|
229
|
-
def core_memory_append(self, agent_state: AgentState, actor: User, label: str, content: str) -> Optional[str]:
|
243
|
+
async def core_memory_append(self, agent_state: AgentState, actor: User, label: str, content: str) -> Optional[str]:
|
230
244
|
"""
|
231
245
|
Append to the contents of core memory.
|
232
246
|
|
@@ -242,10 +256,10 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
242
256
|
current_value = str(agent_state.memory.get_block(label).value)
|
243
257
|
new_value = current_value + "\n" + str(content)
|
244
258
|
agent_state.memory.update_block_value(label=label, value=new_value)
|
245
|
-
AgentManager().
|
259
|
+
await AgentManager().update_memory_if_changed_async(agent_id=agent_state.id, new_memory=agent_state.memory, actor=actor)
|
246
260
|
return None
|
247
261
|
|
248
|
-
def core_memory_replace(
|
262
|
+
async def core_memory_replace(
|
249
263
|
self,
|
250
264
|
agent_state: AgentState,
|
251
265
|
actor: User,
|
@@ -271,10 +285,10 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
271
285
|
raise ValueError(f"Old content '{old_content}' not found in memory block '{label}'")
|
272
286
|
new_value = current_value.replace(str(old_content), str(new_content))
|
273
287
|
agent_state.memory.update_block_value(label=label, value=new_value)
|
274
|
-
AgentManager().
|
288
|
+
await AgentManager().update_memory_if_changed_async(agent_id=agent_state.id, new_memory=agent_state.memory, actor=actor)
|
275
289
|
return None
|
276
290
|
|
277
|
-
def memory_replace(
|
291
|
+
async def memory_replace(
|
278
292
|
self,
|
279
293
|
agent_state: AgentState,
|
280
294
|
actor: User,
|
@@ -289,19 +303,18 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
289
303
|
Args:
|
290
304
|
label (str): Section of the memory to be edited, identified by its label.
|
291
305
|
old_str (str): The text to replace (must match exactly, including whitespace
|
292
|
-
and indentation).
|
306
|
+
and indentation). Do not include line number prefixes.
|
293
307
|
new_str (Optional[str]): The new text to insert in place of the old text.
|
294
|
-
Omit this argument to delete the old_str.
|
308
|
+
Omit this argument to delete the old_str. Do not include line number prefixes.
|
295
309
|
|
296
310
|
Returns:
|
297
311
|
str: The success message
|
298
312
|
"""
|
299
|
-
import re
|
300
313
|
|
301
314
|
if agent_state.memory.get_block(label).read_only:
|
302
315
|
raise ValueError(f"{READ_ONLY_BLOCK_EDIT_ERROR}")
|
303
316
|
|
304
|
-
if bool(
|
317
|
+
if bool(MEMORY_TOOLS_LINE_NUMBER_PREFIX_REGEX.search(old_str)):
|
305
318
|
raise ValueError(
|
306
319
|
"old_str contains a line number prefix, which is not allowed. "
|
307
320
|
"Do not include line numbers when calling memory tools (line "
|
@@ -313,7 +326,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
313
326
|
"Do not include line number information when calling memory tools "
|
314
327
|
"(line numbers are for display purposes only)."
|
315
328
|
)
|
316
|
-
if bool(
|
329
|
+
if bool(MEMORY_TOOLS_LINE_NUMBER_PREFIX_REGEX.search(new_str)):
|
317
330
|
raise ValueError(
|
318
331
|
"new_str contains a line number prefix, which is not allowed. "
|
319
332
|
"Do not include line numbers when calling memory tools (line "
|
@@ -344,7 +357,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
344
357
|
# Write the new content to the block
|
345
358
|
agent_state.memory.update_block_value(label=label, value=new_value)
|
346
359
|
|
347
|
-
AgentManager().
|
360
|
+
await AgentManager().update_memory_if_changed_async(agent_id=agent_state.id, new_memory=agent_state.memory, actor=actor)
|
348
361
|
|
349
362
|
# Create a snippet of the edited section
|
350
363
|
SNIPPET_LINES = 3
|
@@ -367,7 +380,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
367
380
|
# return None
|
368
381
|
return success_msg
|
369
382
|
|
370
|
-
def memory_insert(
|
383
|
+
async def memory_insert(
|
371
384
|
self,
|
372
385
|
agent_state: AgentState,
|
373
386
|
actor: User,
|
@@ -381,19 +394,18 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
381
394
|
|
382
395
|
Args:
|
383
396
|
label (str): Section of the memory to be edited, identified by its label.
|
384
|
-
new_str (str): The text to insert.
|
397
|
+
new_str (str): The text to insert. Do not include line number prefixes.
|
385
398
|
insert_line (int): The line number after which to insert the text (0 for
|
386
399
|
beginning of file). Defaults to -1 (end of the file).
|
387
400
|
|
388
401
|
Returns:
|
389
402
|
str: The success message
|
390
403
|
"""
|
391
|
-
import re
|
392
404
|
|
393
405
|
if agent_state.memory.get_block(label).read_only:
|
394
406
|
raise ValueError(f"{READ_ONLY_BLOCK_EDIT_ERROR}")
|
395
407
|
|
396
|
-
if bool(
|
408
|
+
if bool(MEMORY_TOOLS_LINE_NUMBER_PREFIX_REGEX.search(new_str)):
|
397
409
|
raise ValueError(
|
398
410
|
"new_str contains a line number prefix, which is not allowed. Do not "
|
399
411
|
"include line numbers when calling memory tools (line numbers are for "
|
@@ -412,7 +424,9 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
412
424
|
n_lines = len(current_value_lines)
|
413
425
|
|
414
426
|
# Check if we're in range, from 0 (pre-line), to 1 (first line), to n_lines (last line)
|
415
|
-
if insert_line
|
427
|
+
if insert_line == -1:
|
428
|
+
insert_line = n_lines
|
429
|
+
elif insert_line < 0 or insert_line > n_lines:
|
416
430
|
raise ValueError(
|
417
431
|
f"Invalid `insert_line` parameter: {insert_line}. It should be within "
|
418
432
|
f"the range of lines of the memory block: {[0, n_lines]}, or -1 to "
|
@@ -436,7 +450,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
436
450
|
# Write into the block
|
437
451
|
agent_state.memory.update_block_value(label=label, value=new_value)
|
438
452
|
|
439
|
-
AgentManager().
|
453
|
+
await AgentManager().update_memory_if_changed_async(agent_id=agent_state.id, new_memory=agent_state.memory, actor=actor)
|
440
454
|
|
441
455
|
# Prepare the success message
|
442
456
|
success_msg = f"The core memory block with label `{label}` has been edited. "
|
@@ -453,7 +467,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
453
467
|
|
454
468
|
return success_msg
|
455
469
|
|
456
|
-
def memory_rethink(self, agent_state: AgentState, actor: User, label: str, new_memory: str) -> str:
|
470
|
+
async def memory_rethink(self, agent_state: AgentState, actor: User, label: str, new_memory: str) -> str:
|
457
471
|
"""
|
458
472
|
The memory_rethink command allows you to completely rewrite the contents of a
|
459
473
|
memory block. Use this tool to make large sweeping changes (e.g. when you want
|
@@ -463,17 +477,15 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
463
477
|
Args:
|
464
478
|
label (str): The memory block to be rewritten, identified by its label.
|
465
479
|
new_memory (str): The new memory contents with information integrated from
|
466
|
-
existing memory blocks and the conversation context.
|
480
|
+
existing memory blocks and the conversation context. Do not include line number prefixes.
|
467
481
|
|
468
482
|
Returns:
|
469
483
|
str: The success message
|
470
484
|
"""
|
471
|
-
import re
|
472
|
-
|
473
485
|
if agent_state.memory.get_block(label).read_only:
|
474
486
|
raise ValueError(f"{READ_ONLY_BLOCK_EDIT_ERROR}")
|
475
487
|
|
476
|
-
if bool(
|
488
|
+
if bool(MEMORY_TOOLS_LINE_NUMBER_PREFIX_REGEX.search(new_memory)):
|
477
489
|
raise ValueError(
|
478
490
|
"new_memory contains a line number prefix, which is not allowed. Do not "
|
479
491
|
"include line numbers when calling memory tools (line numbers are for "
|
@@ -491,7 +503,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
491
503
|
|
492
504
|
agent_state.memory.update_block_value(label=label, value=new_memory)
|
493
505
|
|
494
|
-
AgentManager().
|
506
|
+
await AgentManager().update_memory_if_changed_async(agent_id=agent_state.id, new_memory=agent_state.memory, actor=actor)
|
495
507
|
|
496
508
|
# Prepare the success message
|
497
509
|
success_msg = f"The core memory block with label `{label}` has been edited. "
|
@@ -507,7 +519,7 @@ class LettaCoreToolExecutor(ToolExecutor):
|
|
507
519
|
# return None
|
508
520
|
return success_msg
|
509
521
|
|
510
|
-
def memory_finish_edits(self, agent_state: AgentState, actor: User) -> None:
|
522
|
+
async def memory_finish_edits(self, agent_state: AgentState, actor: User) -> None:
|
511
523
|
"""
|
512
524
|
Call the memory_finish_edits command when you are finished making edits
|
513
525
|
(integrating all new information) into the memory blocks. This function
|
@@ -526,16 +538,17 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
|
|
526
538
|
self,
|
527
539
|
function_name: str,
|
528
540
|
function_args: dict,
|
529
|
-
agent_state: AgentState,
|
530
541
|
tool: Tool,
|
531
542
|
actor: User,
|
543
|
+
agent_state: Optional[AgentState] = None,
|
532
544
|
sandbox_config: Optional[SandboxConfig] = None,
|
533
545
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
534
546
|
) -> ToolExecutionResult:
|
547
|
+
assert agent_state is not None, "Agent state is required for multi-agent tools"
|
535
548
|
function_map = {
|
536
549
|
"send_message_to_agent_and_wait_for_reply": self.send_message_to_agent_and_wait_for_reply,
|
537
550
|
"send_message_to_agent_async": self.send_message_to_agent_async,
|
538
|
-
"send_message_to_agents_matching_tags": self.
|
551
|
+
"send_message_to_agents_matching_tags": self.send_message_to_agents_matching_tags_async,
|
539
552
|
}
|
540
553
|
|
541
554
|
if function_name not in function_map:
|
@@ -573,11 +586,13 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
|
|
573
586
|
|
574
587
|
return "Successfully sent message"
|
575
588
|
|
576
|
-
async def
|
589
|
+
async def send_message_to_agents_matching_tags_async(
|
577
590
|
self, agent_state: AgentState, message: str, match_all: List[str], match_some: List[str]
|
578
591
|
) -> str:
|
579
592
|
# Find matching agents
|
580
|
-
matching_agents = self.agent_manager.
|
593
|
+
matching_agents = await self.agent_manager.list_agents_matching_tags_async(
|
594
|
+
actor=self.actor, match_all=match_all, match_some=match_some
|
595
|
+
)
|
581
596
|
if not matching_agents:
|
582
597
|
return str([])
|
583
598
|
|
@@ -633,19 +648,20 @@ class ExternalComposioToolExecutor(ToolExecutor):
|
|
633
648
|
self,
|
634
649
|
function_name: str,
|
635
650
|
function_args: dict,
|
636
|
-
agent_state: AgentState,
|
637
651
|
tool: Tool,
|
638
652
|
actor: User,
|
653
|
+
agent_state: Optional[AgentState] = None,
|
639
654
|
sandbox_config: Optional[SandboxConfig] = None,
|
640
655
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
641
656
|
) -> ToolExecutionResult:
|
657
|
+
assert agent_state is not None, "Agent state is required for external Composio tools"
|
642
658
|
action_name = generate_composio_action_from_func_name(tool.name)
|
643
659
|
|
644
660
|
# Get entity ID from the agent_state
|
645
661
|
entity_id = self._get_entity_id(agent_state)
|
646
662
|
|
647
663
|
# Get composio_api_key
|
648
|
-
composio_api_key =
|
664
|
+
composio_api_key = await get_composio_api_key_async(actor=actor)
|
649
665
|
|
650
666
|
# TODO (matt): Roll in execute_composio_action into this class
|
651
667
|
function_response = await execute_composio_action_async(
|
@@ -668,53 +684,35 @@ class ExternalComposioToolExecutor(ToolExecutor):
|
|
668
684
|
class ExternalMCPToolExecutor(ToolExecutor):
|
669
685
|
"""Executor for external MCP tools."""
|
670
686
|
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
# if server_name not in agent.mcp_clients:
|
701
|
-
# raise ValueError(f"Unknown MCP server name: {server_name}")
|
702
|
-
#
|
703
|
-
# mcp_client = agent.mcp_clients[server_name]
|
704
|
-
# if not isinstance(mcp_client, BaseMCPClient):
|
705
|
-
# raise RuntimeError(f"Expected an MCPClient, but got: {type(mcp_client)}")
|
706
|
-
#
|
707
|
-
# return mcp_client
|
708
|
-
#
|
709
|
-
# def _validate_tool_exists(self, mcp_client, function_name: str, server_name: str):
|
710
|
-
# """Validate that the tool exists in the MCP server."""
|
711
|
-
# available_tools = mcp_client.list_tools()
|
712
|
-
# available_tool_names = [t.name for t in available_tools]
|
713
|
-
#
|
714
|
-
# if function_name not in available_tool_names:
|
715
|
-
# raise ValueError(
|
716
|
-
# f"{function_name} is not available in MCP server {server_name}. " f"Please check your `~/.letta/mcp_config.json` file."
|
717
|
-
# )
|
687
|
+
@trace_method
|
688
|
+
async def execute(
|
689
|
+
self,
|
690
|
+
function_name: str,
|
691
|
+
function_args: dict,
|
692
|
+
tool: Tool,
|
693
|
+
actor: User,
|
694
|
+
agent_state: Optional[AgentState] = None,
|
695
|
+
sandbox_config: Optional[SandboxConfig] = None,
|
696
|
+
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
697
|
+
) -> ToolExecutionResult:
|
698
|
+
|
699
|
+
pass
|
700
|
+
|
701
|
+
mcp_server_tag = [tag for tag in tool.tags if tag.startswith(f"{MCP_TOOL_TAG_NAME_PREFIX}:")]
|
702
|
+
if not mcp_server_tag:
|
703
|
+
raise ValueError(f"Tool {tool.name} does not have a valid MCP server tag")
|
704
|
+
mcp_server_name = mcp_server_tag[0].split(":")[1]
|
705
|
+
|
706
|
+
mcp_manager = MCPManager()
|
707
|
+
# TODO: may need to have better client connection management
|
708
|
+
function_response, success = await mcp_manager.execute_mcp_server_tool(
|
709
|
+
mcp_server_name=mcp_server_name, tool_name=function_name, tool_args=function_args, actor=actor
|
710
|
+
)
|
711
|
+
|
712
|
+
return ToolExecutionResult(
|
713
|
+
status="success" if success else "error",
|
714
|
+
func_return=function_response,
|
715
|
+
)
|
718
716
|
|
719
717
|
|
720
718
|
class SandboxToolExecutor(ToolExecutor):
|
@@ -724,22 +722,22 @@ class SandboxToolExecutor(ToolExecutor):
|
|
724
722
|
async def execute(
|
725
723
|
self,
|
726
724
|
function_name: str,
|
727
|
-
function_args:
|
728
|
-
agent_state: AgentState,
|
725
|
+
function_args: JsonDict,
|
729
726
|
tool: Tool,
|
730
727
|
actor: User,
|
728
|
+
agent_state: Optional[AgentState] = None,
|
731
729
|
sandbox_config: Optional[SandboxConfig] = None,
|
732
730
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
733
731
|
) -> ToolExecutionResult:
|
734
732
|
|
735
733
|
# Store original memory state
|
736
|
-
orig_memory_str = agent_state.memory.compile()
|
734
|
+
orig_memory_str = agent_state.memory.compile() if agent_state else None
|
737
735
|
|
738
736
|
try:
|
739
737
|
# Prepare function arguments
|
740
738
|
function_args = self._prepare_function_args(function_args, tool, function_name)
|
741
739
|
|
742
|
-
agent_state_copy = self._create_agent_state_copy(agent_state)
|
740
|
+
agent_state_copy = self._create_agent_state_copy(agent_state) if agent_state else None
|
743
741
|
|
744
742
|
# Execute in sandbox depending on API key
|
745
743
|
if tool_settings.e2b_api_key:
|
@@ -754,18 +752,20 @@ class SandboxToolExecutor(ToolExecutor):
|
|
754
752
|
tool_execution_result = await sandbox.run(agent_state=agent_state_copy)
|
755
753
|
|
756
754
|
# Verify memory integrity
|
757
|
-
|
755
|
+
if agent_state:
|
756
|
+
assert orig_memory_str == agent_state.memory.compile(), "Memory should not be modified in a sandbox tool"
|
758
757
|
|
759
758
|
# Update agent memory if needed
|
760
759
|
if tool_execution_result.agent_state is not None:
|
761
|
-
AgentManager().
|
760
|
+
await AgentManager().update_memory_if_changed_async(agent_state.id, tool_execution_result.agent_state.memory, actor)
|
762
761
|
|
763
762
|
return tool_execution_result
|
764
763
|
|
765
764
|
except Exception as e:
|
766
765
|
return self._handle_execution_error(e, function_name, traceback.format_exc())
|
767
766
|
|
768
|
-
|
767
|
+
@staticmethod
|
768
|
+
def _prepare_function_args(function_args: JsonDict, tool: Tool, function_name: str) -> dict:
|
769
769
|
"""Prepare function arguments with proper type coercion."""
|
770
770
|
try:
|
771
771
|
# Parse the source code to extract function annotations
|
@@ -777,7 +777,8 @@ class SandboxToolExecutor(ToolExecutor):
|
|
777
777
|
# This is defensive programming - we try to coerce but fall back if it fails
|
778
778
|
return function_args
|
779
779
|
|
780
|
-
|
780
|
+
@staticmethod
|
781
|
+
def _create_agent_state_copy(agent_state: AgentState):
|
781
782
|
"""Create a copy of agent state for sandbox execution."""
|
782
783
|
agent_state_copy = agent_state.__deepcopy__()
|
783
784
|
# Remove tools from copy to prevent nested tool execution
|
@@ -785,8 +786,8 @@ class SandboxToolExecutor(ToolExecutor):
|
|
785
786
|
agent_state_copy.tool_rules = []
|
786
787
|
return agent_state_copy
|
787
788
|
|
789
|
+
@staticmethod
|
788
790
|
def _handle_execution_error(
|
789
|
-
self,
|
790
791
|
exception: Exception,
|
791
792
|
function_name: str,
|
792
793
|
stderr: str,
|
@@ -810,9 +811,9 @@ class LettaBuiltinToolExecutor(ToolExecutor):
|
|
810
811
|
self,
|
811
812
|
function_name: str,
|
812
813
|
function_args: dict,
|
813
|
-
agent_state: AgentState,
|
814
814
|
tool: Tool,
|
815
815
|
actor: User,
|
816
|
+
agent_state: Optional[AgentState] = None,
|
816
817
|
sandbox_config: Optional[SandboxConfig] = None,
|
817
818
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
818
819
|
) -> ToolExecutionResult:
|
@@ -828,6 +829,7 @@ class LettaBuiltinToolExecutor(ToolExecutor):
|
|
828
829
|
return ToolExecutionResult(
|
829
830
|
status="success",
|
830
831
|
func_return=function_response,
|
832
|
+
agent_state=agent_state,
|
831
833
|
)
|
832
834
|
|
833
835
|
async def run_code(self, code: str, language: Literal["python", "js", "ts", "r", "java"]) -> str:
|