letta-nightly 0.8.0.dev20250606195656__py3-none-any.whl → 0.8.3.dev20250607000559__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.
Files changed (105) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +16 -12
  3. letta/agents/base_agent.py +1 -1
  4. letta/agents/helpers.py +13 -2
  5. letta/agents/letta_agent.py +72 -34
  6. letta/agents/letta_agent_batch.py +1 -2
  7. letta/agents/voice_agent.py +19 -13
  8. letta/agents/voice_sleeptime_agent.py +23 -6
  9. letta/constants.py +18 -0
  10. letta/data_sources/__init__.py +0 -0
  11. letta/data_sources/redis_client.py +282 -0
  12. letta/errors.py +0 -4
  13. letta/functions/function_sets/files.py +58 -0
  14. letta/functions/schema_generator.py +18 -1
  15. letta/groups/sleeptime_multi_agent_v2.py +13 -3
  16. letta/helpers/datetime_helpers.py +47 -3
  17. letta/helpers/decorators.py +69 -0
  18. letta/{services/helpers/noop_helper.py → helpers/singleton.py} +5 -0
  19. letta/interfaces/anthropic_streaming_interface.py +43 -24
  20. letta/interfaces/openai_streaming_interface.py +21 -19
  21. letta/llm_api/anthropic.py +1 -1
  22. letta/llm_api/anthropic_client.py +30 -16
  23. letta/llm_api/google_vertex_client.py +1 -1
  24. letta/llm_api/helpers.py +36 -30
  25. letta/llm_api/llm_api_tools.py +1 -1
  26. letta/llm_api/llm_client_base.py +29 -1
  27. letta/llm_api/openai.py +1 -1
  28. letta/llm_api/openai_client.py +6 -8
  29. letta/local_llm/chat_completion_proxy.py +1 -1
  30. letta/memory.py +1 -1
  31. letta/orm/enums.py +1 -0
  32. letta/orm/file.py +80 -3
  33. letta/orm/files_agents.py +13 -0
  34. letta/orm/passage.py +2 -0
  35. letta/orm/sqlalchemy_base.py +34 -11
  36. letta/otel/__init__.py +0 -0
  37. letta/otel/context.py +25 -0
  38. letta/otel/events.py +0 -0
  39. letta/otel/metric_registry.py +122 -0
  40. letta/otel/metrics.py +66 -0
  41. letta/otel/resource.py +26 -0
  42. letta/{tracing.py → otel/tracing.py} +55 -78
  43. letta/plugins/README.md +22 -0
  44. letta/plugins/__init__.py +0 -0
  45. letta/plugins/defaults.py +11 -0
  46. letta/plugins/plugins.py +72 -0
  47. letta/schemas/enums.py +8 -0
  48. letta/schemas/file.py +12 -0
  49. letta/schemas/letta_request.py +6 -0
  50. letta/schemas/passage.py +1 -0
  51. letta/schemas/tool.py +4 -0
  52. letta/server/db.py +7 -7
  53. letta/server/rest_api/app.py +8 -6
  54. letta/server/rest_api/routers/v1/agents.py +46 -37
  55. letta/server/rest_api/routers/v1/groups.py +3 -3
  56. letta/server/rest_api/routers/v1/sources.py +26 -3
  57. letta/server/rest_api/routers/v1/tools.py +7 -2
  58. letta/server/rest_api/utils.py +9 -6
  59. letta/server/server.py +25 -13
  60. letta/services/agent_manager.py +186 -194
  61. letta/services/block_manager.py +1 -1
  62. letta/services/context_window_calculator/context_window_calculator.py +1 -1
  63. letta/services/context_window_calculator/token_counter.py +3 -2
  64. letta/services/file_processor/chunker/line_chunker.py +34 -0
  65. letta/services/file_processor/file_processor.py +43 -12
  66. letta/services/file_processor/parser/mistral_parser.py +11 -1
  67. letta/services/files_agents_manager.py +96 -7
  68. letta/services/group_manager.py +6 -6
  69. letta/services/helpers/agent_manager_helper.py +404 -3
  70. letta/services/identity_manager.py +1 -1
  71. letta/services/job_manager.py +1 -1
  72. letta/services/llm_batch_manager.py +1 -1
  73. letta/services/mcp/stdio_client.py +5 -1
  74. letta/services/mcp_manager.py +4 -4
  75. letta/services/message_manager.py +1 -1
  76. letta/services/organization_manager.py +1 -1
  77. letta/services/passage_manager.py +604 -19
  78. letta/services/per_agent_lock_manager.py +1 -1
  79. letta/services/provider_manager.py +1 -1
  80. letta/services/sandbox_config_manager.py +1 -1
  81. letta/services/source_manager.py +178 -19
  82. letta/services/step_manager.py +2 -2
  83. letta/services/summarizer/summarizer.py +1 -1
  84. letta/services/telemetry_manager.py +1 -1
  85. letta/services/tool_executor/builtin_tool_executor.py +117 -0
  86. letta/services/tool_executor/composio_tool_executor.py +53 -0
  87. letta/services/tool_executor/core_tool_executor.py +474 -0
  88. letta/services/tool_executor/files_tool_executor.py +138 -0
  89. letta/services/tool_executor/mcp_tool_executor.py +45 -0
  90. letta/services/tool_executor/multi_agent_tool_executor.py +123 -0
  91. letta/services/tool_executor/tool_execution_manager.py +34 -14
  92. letta/services/tool_executor/tool_execution_sandbox.py +1 -1
  93. letta/services/tool_executor/tool_executor.py +3 -802
  94. letta/services/tool_executor/tool_executor_base.py +43 -0
  95. letta/services/tool_manager.py +55 -59
  96. letta/services/tool_sandbox/e2b_sandbox.py +1 -1
  97. letta/services/tool_sandbox/local_sandbox.py +6 -3
  98. letta/services/user_manager.py +6 -3
  99. letta/settings.py +23 -2
  100. letta/utils.py +7 -2
  101. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/METADATA +4 -2
  102. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/RECORD +105 -83
  103. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/LICENSE +0 -0
  104. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/WHEEL +0 -0
  105. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.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.tool_executor import (
17
- ExternalComposioToolExecutor,
18
- ExternalMCPToolExecutor,
19
- LettaBuiltinToolExecutor,
20
- LettaCoreToolExecutor,
21
- LettaMultiAgentToolExecutor,
22
- SandboxToolExecutor,
23
- ToolExecutor,
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(self, function_name: str, function_args: dict, tool: Tool) -> ToolExecutionResult:
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
- result = await executor.execute(
102
- function_name, function_args, tool, self.actor, self.agent_state, self.sandbox_config, self.sandbox_env_vars
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__)