letta-nightly 0.8.0.dev20250606195656__py3-none-any.whl → 0.8.2.dev20250606215616__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 +1 -1
- letta/agent.py +1 -1
- letta/agents/letta_agent.py +49 -29
- letta/agents/letta_agent_batch.py +1 -2
- letta/agents/voice_agent.py +19 -13
- letta/agents/voice_sleeptime_agent.py +11 -3
- letta/constants.py +18 -0
- letta/data_sources/__init__.py +0 -0
- letta/data_sources/redis_client.py +282 -0
- letta/errors.py +0 -4
- letta/functions/function_sets/files.py +58 -0
- letta/functions/schema_generator.py +18 -1
- letta/groups/sleeptime_multi_agent_v2.py +1 -1
- letta/helpers/datetime_helpers.py +47 -3
- letta/helpers/decorators.py +69 -0
- letta/{services/helpers/noop_helper.py → helpers/singleton.py} +5 -0
- letta/interfaces/anthropic_streaming_interface.py +43 -24
- letta/interfaces/openai_streaming_interface.py +21 -19
- letta/llm_api/anthropic.py +1 -1
- letta/llm_api/anthropic_client.py +22 -14
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/helpers.py +36 -30
- letta/llm_api/llm_api_tools.py +1 -1
- letta/llm_api/llm_client_base.py +29 -1
- letta/llm_api/openai.py +1 -1
- letta/llm_api/openai_client.py +6 -8
- letta/local_llm/chat_completion_proxy.py +1 -1
- letta/memory.py +1 -1
- letta/orm/enums.py +1 -0
- letta/orm/file.py +80 -3
- letta/orm/files_agents.py +13 -0
- letta/orm/sqlalchemy_base.py +34 -11
- letta/otel/__init__.py +0 -0
- letta/otel/context.py +25 -0
- letta/otel/events.py +0 -0
- letta/otel/metric_registry.py +122 -0
- letta/otel/metrics.py +66 -0
- letta/otel/resource.py +26 -0
- letta/{tracing.py → otel/tracing.py} +55 -78
- letta/plugins/README.md +22 -0
- letta/plugins/__init__.py +0 -0
- letta/plugins/defaults.py +11 -0
- letta/plugins/plugins.py +72 -0
- letta/schemas/enums.py +8 -0
- letta/schemas/file.py +12 -0
- letta/schemas/tool.py +4 -0
- letta/server/db.py +7 -7
- letta/server/rest_api/app.py +8 -6
- letta/server/rest_api/routers/v1/agents.py +37 -36
- letta/server/rest_api/routers/v1/groups.py +3 -3
- letta/server/rest_api/routers/v1/sources.py +26 -3
- letta/server/rest_api/utils.py +9 -6
- letta/server/server.py +18 -12
- letta/services/agent_manager.py +185 -193
- letta/services/block_manager.py +1 -1
- letta/services/context_window_calculator/token_counter.py +3 -2
- letta/services/file_processor/chunker/line_chunker.py +34 -0
- letta/services/file_processor/file_processor.py +40 -11
- letta/services/file_processor/parser/mistral_parser.py +11 -1
- letta/services/files_agents_manager.py +96 -7
- letta/services/group_manager.py +6 -6
- letta/services/helpers/agent_manager_helper.py +373 -3
- letta/services/identity_manager.py +1 -1
- letta/services/job_manager.py +1 -1
- letta/services/llm_batch_manager.py +1 -1
- letta/services/message_manager.py +1 -1
- letta/services/organization_manager.py +1 -1
- letta/services/passage_manager.py +1 -1
- letta/services/per_agent_lock_manager.py +1 -1
- letta/services/provider_manager.py +1 -1
- letta/services/sandbox_config_manager.py +1 -1
- letta/services/source_manager.py +178 -19
- letta/services/step_manager.py +2 -2
- letta/services/summarizer/summarizer.py +1 -1
- letta/services/telemetry_manager.py +1 -1
- letta/services/tool_executor/builtin_tool_executor.py +117 -0
- letta/services/tool_executor/composio_tool_executor.py +53 -0
- letta/services/tool_executor/core_tool_executor.py +474 -0
- letta/services/tool_executor/files_tool_executor.py +131 -0
- letta/services/tool_executor/mcp_tool_executor.py +45 -0
- letta/services/tool_executor/multi_agent_tool_executor.py +123 -0
- letta/services/tool_executor/tool_execution_manager.py +34 -14
- letta/services/tool_executor/tool_execution_sandbox.py +1 -1
- letta/services/tool_executor/tool_executor.py +3 -802
- letta/services/tool_executor/tool_executor_base.py +43 -0
- letta/services/tool_manager.py +55 -59
- letta/services/tool_sandbox/e2b_sandbox.py +1 -1
- letta/services/tool_sandbox/local_sandbox.py +6 -3
- letta/services/user_manager.py +6 -3
- letta/settings.py +21 -1
- letta/utils.py +7 -2
- {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/METADATA +4 -2
- {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/RECORD +96 -74
- {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/WHEEL +0 -0
- {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Any, Dict, List, Optional
|
3
|
+
|
4
|
+
from letta.schemas.agent import AgentState
|
5
|
+
from letta.schemas.enums import MessageRole
|
6
|
+
from letta.schemas.letta_message import AssistantMessage
|
7
|
+
from letta.schemas.letta_message_content import TextContent
|
8
|
+
from letta.schemas.message import MessageCreate
|
9
|
+
from letta.schemas.sandbox_config import SandboxConfig
|
10
|
+
from letta.schemas.tool import Tool
|
11
|
+
from letta.schemas.tool_execution_result import ToolExecutionResult
|
12
|
+
from letta.schemas.user import User
|
13
|
+
from letta.services.tool_executor.tool_executor import logger
|
14
|
+
from letta.services.tool_executor.tool_executor_base import ToolExecutor
|
15
|
+
|
16
|
+
|
17
|
+
class LettaMultiAgentToolExecutor(ToolExecutor):
|
18
|
+
"""Executor for LETTA multi-agent core tools."""
|
19
|
+
|
20
|
+
async def execute(
|
21
|
+
self,
|
22
|
+
function_name: str,
|
23
|
+
function_args: dict,
|
24
|
+
tool: Tool,
|
25
|
+
actor: User,
|
26
|
+
agent_state: Optional[AgentState] = None,
|
27
|
+
sandbox_config: Optional[SandboxConfig] = None,
|
28
|
+
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
29
|
+
) -> ToolExecutionResult:
|
30
|
+
assert agent_state is not None, "Agent state is required for multi-agent tools"
|
31
|
+
function_map = {
|
32
|
+
"send_message_to_agent_and_wait_for_reply": self.send_message_to_agent_and_wait_for_reply,
|
33
|
+
"send_message_to_agent_async": self.send_message_to_agent_async,
|
34
|
+
"send_message_to_agents_matching_tags": self.send_message_to_agents_matching_tags_async,
|
35
|
+
}
|
36
|
+
|
37
|
+
if function_name not in function_map:
|
38
|
+
raise ValueError(f"Unknown function: {function_name}")
|
39
|
+
|
40
|
+
# Execute the appropriate function
|
41
|
+
function_args_copy = function_args.copy() # Make a copy to avoid modifying the original
|
42
|
+
function_response = await function_map[function_name](agent_state, **function_args_copy)
|
43
|
+
return ToolExecutionResult(
|
44
|
+
status="success",
|
45
|
+
func_return=function_response,
|
46
|
+
)
|
47
|
+
|
48
|
+
async def send_message_to_agent_and_wait_for_reply(self, agent_state: AgentState, message: str, other_agent_id: str) -> str:
|
49
|
+
augmented_message = (
|
50
|
+
f"[Incoming message from agent with ID '{agent_state.id}' - to reply to this message, "
|
51
|
+
f"make sure to use the 'send_message' at the end, and the system will notify the sender of your response] "
|
52
|
+
f"{message}"
|
53
|
+
)
|
54
|
+
|
55
|
+
return str(await self._process_agent(agent_id=other_agent_id, message=augmented_message))
|
56
|
+
|
57
|
+
async def send_message_to_agent_async(self, agent_state: AgentState, message: str, other_agent_id: str) -> str:
|
58
|
+
# 1) Build the prefixed system‐message
|
59
|
+
prefixed = (
|
60
|
+
f"[Incoming message from agent with ID '{agent_state.id}' - "
|
61
|
+
f"to reply to this message, make sure to use the "
|
62
|
+
f"'send_message_to_agent_async' tool, or the agent will not receive your message] "
|
63
|
+
f"{message}"
|
64
|
+
)
|
65
|
+
|
66
|
+
task = asyncio.create_task(self._process_agent(agent_id=other_agent_id, message=prefixed))
|
67
|
+
|
68
|
+
task.add_done_callback(lambda t: (logger.error(f"Async send_message task failed: {t.exception()}") if t.exception() else None))
|
69
|
+
|
70
|
+
return "Successfully sent message"
|
71
|
+
|
72
|
+
async def send_message_to_agents_matching_tags_async(
|
73
|
+
self, agent_state: AgentState, message: str, match_all: List[str], match_some: List[str]
|
74
|
+
) -> str:
|
75
|
+
# Find matching agents
|
76
|
+
matching_agents = await self.agent_manager.list_agents_matching_tags_async(
|
77
|
+
actor=self.actor, match_all=match_all, match_some=match_some
|
78
|
+
)
|
79
|
+
if not matching_agents:
|
80
|
+
return str([])
|
81
|
+
|
82
|
+
augmented_message = (
|
83
|
+
"[Incoming message from external Letta agent - to reply to this message, "
|
84
|
+
"make sure to use the 'send_message' at the end, and the system will notify "
|
85
|
+
"the sender of your response] "
|
86
|
+
f"{message}"
|
87
|
+
)
|
88
|
+
|
89
|
+
tasks = [
|
90
|
+
asyncio.create_task(self._process_agent(agent_id=agent_state.id, message=augmented_message)) for agent_state in matching_agents
|
91
|
+
]
|
92
|
+
results = await asyncio.gather(*tasks)
|
93
|
+
return str(results)
|
94
|
+
|
95
|
+
async def _process_agent(self, agent_id: str, message: str) -> Dict[str, Any]:
|
96
|
+
from letta.agents.letta_agent import LettaAgent
|
97
|
+
|
98
|
+
try:
|
99
|
+
letta_agent = LettaAgent(
|
100
|
+
agent_id=agent_id,
|
101
|
+
message_manager=self.message_manager,
|
102
|
+
agent_manager=self.agent_manager,
|
103
|
+
block_manager=self.block_manager,
|
104
|
+
passage_manager=self.passage_manager,
|
105
|
+
actor=self.actor,
|
106
|
+
)
|
107
|
+
|
108
|
+
letta_response = await letta_agent.step([MessageCreate(role=MessageRole.system, content=[TextContent(text=message)])])
|
109
|
+
messages = letta_response.messages
|
110
|
+
|
111
|
+
send_message_content = [message.content for message in messages if isinstance(message, AssistantMessage)]
|
112
|
+
|
113
|
+
return {
|
114
|
+
"agent_id": agent_id,
|
115
|
+
"response": send_message_content if send_message_content else ["<no response>"],
|
116
|
+
}
|
117
|
+
|
118
|
+
except Exception as e:
|
119
|
+
return {
|
120
|
+
"agent_id": agent_id,
|
121
|
+
"error": str(e),
|
122
|
+
"type": type(e).__name__,
|
123
|
+
}
|
@@ -2,8 +2,12 @@ import traceback
|
|
2
2
|
from typing import Any, Dict, Optional, Type
|
3
3
|
|
4
4
|
from letta.constants import FUNCTION_RETURN_VALUE_TRUNCATED
|
5
|
+
from letta.helpers.datetime_helpers import AsyncTimer
|
5
6
|
from letta.log import get_logger
|
6
7
|
from letta.orm.enums import ToolType
|
8
|
+
from letta.otel.context import get_ctx_attributes
|
9
|
+
from letta.otel.metric_registry import MetricRegistry
|
10
|
+
from letta.otel.tracing import trace_method
|
7
11
|
from letta.schemas.agent import AgentState
|
8
12
|
from letta.schemas.sandbox_config import SandboxConfig
|
9
13
|
from letta.schemas.tool import Tool
|
@@ -13,16 +17,14 @@ from letta.services.agent_manager import AgentManager
|
|
13
17
|
from letta.services.block_manager import BlockManager
|
14
18
|
from letta.services.message_manager import MessageManager
|
15
19
|
from letta.services.passage_manager import PassageManager
|
16
|
-
from letta.services.tool_executor.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
)
|
25
|
-
from letta.tracing import trace_method
|
20
|
+
from letta.services.tool_executor.builtin_tool_executor import LettaBuiltinToolExecutor
|
21
|
+
from letta.services.tool_executor.composio_tool_executor import ExternalComposioToolExecutor
|
22
|
+
from letta.services.tool_executor.core_tool_executor import LettaCoreToolExecutor
|
23
|
+
from letta.services.tool_executor.files_tool_executor import LettaFileToolExecutor
|
24
|
+
from letta.services.tool_executor.mcp_tool_executor import ExternalMCPToolExecutor
|
25
|
+
from letta.services.tool_executor.multi_agent_tool_executor import LettaMultiAgentToolExecutor
|
26
|
+
from letta.services.tool_executor.tool_executor import SandboxToolExecutor
|
27
|
+
from letta.services.tool_executor.tool_executor_base import ToolExecutor
|
26
28
|
from letta.utils import get_friendly_error_msg
|
27
29
|
|
28
30
|
|
@@ -35,6 +37,7 @@ class ToolExecutorFactory:
|
|
35
37
|
ToolType.LETTA_SLEEPTIME_CORE: LettaCoreToolExecutor,
|
36
38
|
ToolType.LETTA_MULTI_AGENT_CORE: LettaMultiAgentToolExecutor,
|
37
39
|
ToolType.LETTA_BUILTIN: LettaBuiltinToolExecutor,
|
40
|
+
ToolType.LETTA_FILES_CORE: LettaFileToolExecutor,
|
38
41
|
ToolType.EXTERNAL_COMPOSIO: ExternalComposioToolExecutor,
|
39
42
|
ToolType.EXTERNAL_MCP: ExternalMCPToolExecutor,
|
40
43
|
}
|
@@ -85,10 +88,13 @@ class ToolExecutionManager:
|
|
85
88
|
self.sandbox_env_vars = sandbox_env_vars
|
86
89
|
|
87
90
|
@trace_method
|
88
|
-
async def execute_tool_async(
|
91
|
+
async def execute_tool_async(
|
92
|
+
self, function_name: str, function_args: dict, tool: Tool, step_id: str | None = None
|
93
|
+
) -> ToolExecutionResult:
|
89
94
|
"""
|
90
95
|
Execute a tool asynchronously and persist any state changes.
|
91
96
|
"""
|
97
|
+
status = "error" # set as default for tracking purposes
|
92
98
|
try:
|
93
99
|
executor = ToolExecutorFactory.get_executor(
|
94
100
|
tool.tool_type,
|
@@ -98,9 +104,17 @@ class ToolExecutionManager:
|
|
98
104
|
passage_manager=self.passage_manager,
|
99
105
|
actor=self.actor,
|
100
106
|
)
|
101
|
-
|
102
|
-
|
103
|
-
|
107
|
+
|
108
|
+
def _metrics_callback(exec_time_ms: int, exc):
|
109
|
+
return MetricRegistry().tool_execution_time_ms_histogram.record(
|
110
|
+
exec_time_ms, dict(get_ctx_attributes(), **{"tool.name": tool.name})
|
111
|
+
)
|
112
|
+
|
113
|
+
async with AsyncTimer(callback_func=_metrics_callback):
|
114
|
+
result = await executor.execute(
|
115
|
+
function_name, function_args, tool, self.actor, self.agent_state, self.sandbox_config, self.sandbox_env_vars
|
116
|
+
)
|
117
|
+
status = result.status
|
104
118
|
|
105
119
|
# trim result
|
106
120
|
return_str = str(result.func_return)
|
@@ -110,6 +124,7 @@ class ToolExecutionManager:
|
|
110
124
|
return result
|
111
125
|
|
112
126
|
except Exception as e:
|
127
|
+
status = "error"
|
113
128
|
self.logger.error(f"Error executing tool {function_name}: {str(e)}")
|
114
129
|
error_message = get_friendly_error_msg(
|
115
130
|
function_name=function_name,
|
@@ -121,3 +136,8 @@ class ToolExecutionManager:
|
|
121
136
|
func_return=error_message,
|
122
137
|
stderr=[traceback.format_exc()],
|
123
138
|
)
|
139
|
+
finally:
|
140
|
+
metric_attrs = {"tool.name": tool.name, "tool.execution_success": status == "success"}
|
141
|
+
if status == "error" and step_id:
|
142
|
+
metric_attrs["step.id"] = step_id
|
143
|
+
MetricRegistry().tool_execution_counter.add(1, dict(get_ctx_attributes(), **metric_attrs))
|
@@ -11,6 +11,7 @@ from typing import Any, Dict, Optional
|
|
11
11
|
|
12
12
|
from letta.functions.helpers import generate_model_from_args_json_schema
|
13
13
|
from letta.log import get_logger
|
14
|
+
from letta.otel.tracing import log_event, trace_method
|
14
15
|
from letta.schemas.agent import AgentState
|
15
16
|
from letta.schemas.sandbox_config import SandboxConfig, SandboxType
|
16
17
|
from letta.schemas.tool import Tool
|
@@ -27,7 +28,6 @@ from letta.services.organization_manager import OrganizationManager
|
|
27
28
|
from letta.services.sandbox_config_manager import SandboxConfigManager
|
28
29
|
from letta.services.tool_manager import ToolManager
|
29
30
|
from letta.settings import tool_settings
|
30
|
-
from letta.tracing import log_event, trace_method
|
31
31
|
from letta.utils import get_friendly_error_msg
|
32
32
|
|
33
33
|
logger = get_logger(__name__)
|