letta-nightly 0.11.6.dev20250903104037__py3-none-any.whl → 0.11.7.dev20250904045700__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 (138) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +10 -14
  3. letta/agents/base_agent.py +18 -0
  4. letta/agents/helpers.py +32 -7
  5. letta/agents/letta_agent.py +953 -762
  6. letta/agents/voice_agent.py +1 -1
  7. letta/client/streaming.py +0 -1
  8. letta/constants.py +11 -8
  9. letta/errors.py +9 -0
  10. letta/functions/function_sets/base.py +77 -69
  11. letta/functions/function_sets/builtin.py +41 -22
  12. letta/functions/function_sets/multi_agent.py +1 -2
  13. letta/functions/schema_generator.py +0 -1
  14. letta/helpers/converters.py +8 -3
  15. letta/helpers/datetime_helpers.py +5 -4
  16. letta/helpers/message_helper.py +1 -2
  17. letta/helpers/pinecone_utils.py +0 -1
  18. letta/helpers/tool_rule_solver.py +10 -0
  19. letta/helpers/tpuf_client.py +848 -0
  20. letta/interface.py +8 -8
  21. letta/interfaces/anthropic_streaming_interface.py +7 -0
  22. letta/interfaces/openai_streaming_interface.py +29 -6
  23. letta/llm_api/anthropic_client.py +188 -18
  24. letta/llm_api/azure_client.py +0 -1
  25. letta/llm_api/bedrock_client.py +1 -2
  26. letta/llm_api/deepseek_client.py +319 -5
  27. letta/llm_api/google_vertex_client.py +75 -17
  28. letta/llm_api/groq_client.py +0 -1
  29. letta/llm_api/helpers.py +2 -2
  30. letta/llm_api/llm_api_tools.py +1 -50
  31. letta/llm_api/llm_client.py +6 -8
  32. letta/llm_api/mistral.py +1 -1
  33. letta/llm_api/openai.py +16 -13
  34. letta/llm_api/openai_client.py +31 -16
  35. letta/llm_api/together_client.py +0 -1
  36. letta/llm_api/xai_client.py +0 -1
  37. letta/local_llm/chat_completion_proxy.py +7 -6
  38. letta/local_llm/settings/settings.py +1 -1
  39. letta/orm/__init__.py +1 -0
  40. letta/orm/agent.py +8 -6
  41. letta/orm/archive.py +9 -1
  42. letta/orm/block.py +3 -4
  43. letta/orm/block_history.py +3 -1
  44. letta/orm/group.py +2 -3
  45. letta/orm/identity.py +1 -2
  46. letta/orm/job.py +1 -2
  47. letta/orm/llm_batch_items.py +1 -2
  48. letta/orm/message.py +8 -4
  49. letta/orm/mixins.py +18 -0
  50. letta/orm/organization.py +2 -0
  51. letta/orm/passage.py +8 -1
  52. letta/orm/passage_tag.py +55 -0
  53. letta/orm/sandbox_config.py +1 -3
  54. letta/orm/step.py +1 -2
  55. letta/orm/tool.py +1 -0
  56. letta/otel/resource.py +2 -2
  57. letta/plugins/plugins.py +1 -1
  58. letta/prompts/prompt_generator.py +10 -2
  59. letta/schemas/agent.py +11 -0
  60. letta/schemas/archive.py +4 -0
  61. letta/schemas/block.py +13 -0
  62. letta/schemas/embedding_config.py +0 -1
  63. letta/schemas/enums.py +24 -7
  64. letta/schemas/group.py +12 -0
  65. letta/schemas/letta_message.py +55 -1
  66. letta/schemas/letta_message_content.py +28 -0
  67. letta/schemas/letta_request.py +21 -4
  68. letta/schemas/letta_stop_reason.py +9 -1
  69. letta/schemas/llm_config.py +24 -8
  70. letta/schemas/mcp.py +0 -3
  71. letta/schemas/memory.py +14 -0
  72. letta/schemas/message.py +245 -141
  73. letta/schemas/openai/chat_completion_request.py +2 -1
  74. letta/schemas/passage.py +1 -0
  75. letta/schemas/providers/bedrock.py +1 -1
  76. letta/schemas/providers/openai.py +2 -2
  77. letta/schemas/tool.py +11 -5
  78. letta/schemas/tool_execution_result.py +0 -1
  79. letta/schemas/tool_rule.py +71 -0
  80. letta/serialize_schemas/marshmallow_agent.py +1 -2
  81. letta/server/rest_api/app.py +3 -3
  82. letta/server/rest_api/auth/index.py +0 -1
  83. letta/server/rest_api/interface.py +3 -11
  84. letta/server/rest_api/redis_stream_manager.py +3 -4
  85. letta/server/rest_api/routers/v1/agents.py +143 -84
  86. letta/server/rest_api/routers/v1/blocks.py +1 -1
  87. letta/server/rest_api/routers/v1/folders.py +1 -1
  88. letta/server/rest_api/routers/v1/groups.py +23 -22
  89. letta/server/rest_api/routers/v1/internal_templates.py +68 -0
  90. letta/server/rest_api/routers/v1/sandbox_configs.py +11 -5
  91. letta/server/rest_api/routers/v1/sources.py +1 -1
  92. letta/server/rest_api/routers/v1/tools.py +167 -15
  93. letta/server/rest_api/streaming_response.py +4 -3
  94. letta/server/rest_api/utils.py +75 -18
  95. letta/server/server.py +24 -35
  96. letta/services/agent_manager.py +359 -45
  97. letta/services/agent_serialization_manager.py +23 -3
  98. letta/services/archive_manager.py +72 -3
  99. letta/services/block_manager.py +1 -2
  100. letta/services/context_window_calculator/token_counter.py +11 -6
  101. letta/services/file_manager.py +1 -3
  102. letta/services/files_agents_manager.py +2 -4
  103. letta/services/group_manager.py +73 -12
  104. letta/services/helpers/agent_manager_helper.py +5 -5
  105. letta/services/identity_manager.py +8 -3
  106. letta/services/job_manager.py +2 -14
  107. letta/services/llm_batch_manager.py +1 -3
  108. letta/services/mcp/base_client.py +1 -2
  109. letta/services/mcp_manager.py +5 -6
  110. letta/services/message_manager.py +536 -15
  111. letta/services/organization_manager.py +1 -2
  112. letta/services/passage_manager.py +287 -12
  113. letta/services/provider_manager.py +1 -3
  114. letta/services/sandbox_config_manager.py +12 -7
  115. letta/services/source_manager.py +1 -2
  116. letta/services/step_manager.py +0 -1
  117. letta/services/summarizer/summarizer.py +4 -2
  118. letta/services/telemetry_manager.py +1 -3
  119. letta/services/tool_executor/builtin_tool_executor.py +136 -316
  120. letta/services/tool_executor/core_tool_executor.py +231 -74
  121. letta/services/tool_executor/files_tool_executor.py +2 -2
  122. letta/services/tool_executor/mcp_tool_executor.py +0 -1
  123. letta/services/tool_executor/multi_agent_tool_executor.py +2 -2
  124. letta/services/tool_executor/sandbox_tool_executor.py +0 -1
  125. letta/services/tool_executor/tool_execution_sandbox.py +2 -3
  126. letta/services/tool_manager.py +181 -64
  127. letta/services/tool_sandbox/modal_deployment_manager.py +2 -2
  128. letta/services/user_manager.py +1 -2
  129. letta/settings.py +5 -3
  130. letta/streaming_interface.py +3 -3
  131. letta/system.py +1 -1
  132. letta/utils.py +0 -1
  133. {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904045700.dist-info}/METADATA +11 -7
  134. {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904045700.dist-info}/RECORD +137 -135
  135. letta/llm_api/deepseek.py +0 -303
  136. {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904045700.dist-info}/WHEEL +0 -0
  137. {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904045700.dist-info}/entry_points.txt +0 -0
  138. {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904045700.dist-info}/licenses/LICENSE +0 -0
letta/__init__.py CHANGED
@@ -5,7 +5,7 @@ try:
5
5
  __version__ = version("letta")
6
6
  except PackageNotFoundError:
7
7
  # Fallback for development installations
8
- __version__ = "0.11.6"
8
+ __version__ = "0.11.7"
9
9
 
10
10
  if os.environ.get("LETTA_VERSION"):
11
11
  __version__ = os.environ["LETTA_VERSION"]
letta/agent.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import json
3
- import os
4
3
  import time
5
4
  import traceback
6
5
  import warnings
@@ -50,9 +49,7 @@ from letta.schemas.enums import MessageRole, ProviderType, StepStatus, ToolType
50
49
  from letta.schemas.letta_message_content import ImageContent, TextContent
51
50
  from letta.schemas.memory import ContextWindowOverview, Memory
52
51
  from letta.schemas.message import Message, MessageCreate, ToolReturn
53
- from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
54
- from letta.schemas.openai.chat_completion_response import Message as ChatCompletionMessage
55
- from letta.schemas.openai.chat_completion_response import UsageStatistics
52
+ from letta.schemas.openai.chat_completion_response import ChatCompletionResponse, Message as ChatCompletionMessage, UsageStatistics
56
53
  from letta.schemas.response_format import ResponseFormatType
57
54
  from letta.schemas.tool import Tool
58
55
  from letta.schemas.tool_execution_result import ToolExecutionResult
@@ -71,7 +68,7 @@ from letta.services.step_manager import StepManager
71
68
  from letta.services.telemetry_manager import NoopTelemetryManager, TelemetryManager
72
69
  from letta.services.tool_executor.tool_execution_sandbox import ToolExecutionSandbox
73
70
  from letta.services.tool_manager import ToolManager
74
- from letta.settings import settings, summarizer_settings
71
+ from letta.settings import model_settings, settings, summarizer_settings
75
72
  from letta.streaming_interface import StreamingRefreshCLIInterface
76
73
  from letta.system import get_heartbeat, get_token_limit_warning, package_function_response, package_summarize_message, package_user_message
77
74
  from letta.utils import count_tokens, get_friendly_error_msg, get_tool_call_id, log_telemetry, parse_json, validate_function_response
@@ -872,7 +869,6 @@ class Agent(BaseAgent):
872
869
  ) -> AgentStepResponse:
873
870
  """Runs a single step in the agent loop (generates at most one LLM call)"""
874
871
  try:
875
-
876
872
  # Extract job_id from metadata if present
877
873
  job_id = metadata.get("job_id") if metadata else None
878
874
 
@@ -1085,9 +1081,9 @@ class Agent(BaseAgent):
1085
1081
  -> agent.step(messages=[Message(role='user', text=...)])
1086
1082
  """
1087
1083
  # Wrap with metadata, dumps to JSON
1088
- assert user_message_str and isinstance(
1089
- user_message_str, str
1090
- ), f"user_message_str should be a non-empty string, got {type(user_message_str)}"
1084
+ assert user_message_str and isinstance(user_message_str, str), (
1085
+ f"user_message_str should be a non-empty string, got {type(user_message_str)}"
1086
+ )
1091
1087
  user_message_json_str = package_user_message(user_message_str, self.agent_state.timezone)
1092
1088
 
1093
1089
  # Validate JSON via save/load
@@ -1110,7 +1106,7 @@ class Agent(BaseAgent):
1110
1106
 
1111
1107
  def summarize_messages_inplace(self):
1112
1108
  in_context_messages = self.agent_manager.get_in_context_messages(agent_id=self.agent_state.id, actor=self.user)
1113
- in_context_messages_openai = [m.to_openai_dict() for m in in_context_messages]
1109
+ in_context_messages_openai = Message.to_openai_dicts_from_list(in_context_messages)
1114
1110
  in_context_messages_openai_no_system = in_context_messages_openai[1:]
1115
1111
  token_counts = get_token_counts_for_messages(in_context_messages)
1116
1112
  logger.info(f"System message token count={token_counts[0]}")
@@ -1216,7 +1212,7 @@ class Agent(BaseAgent):
1216
1212
  # Grab the in-context messages
1217
1213
  # conversion of messages to OpenAI dict format, which is passed to the token counter
1218
1214
  in_context_messages = self.agent_manager.get_in_context_messages(agent_id=self.agent_state.id, actor=self.user)
1219
- in_context_messages_openai = [m.to_openai_dict() for m in in_context_messages]
1215
+ in_context_messages_openai = Message.to_openai_dicts_from_list(in_context_messages)
1220
1216
 
1221
1217
  # Check if there's a summary message in the message queue
1222
1218
  if (
@@ -1304,7 +1300,7 @@ class Agent(BaseAgent):
1304
1300
  )
1305
1301
 
1306
1302
  async def get_context_window_async(self) -> ContextWindowOverview:
1307
- if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION" and os.getenv("ANTHROPIC_API_KEY"):
1303
+ if settings.environment == "PRODUCTION" and model_settings.anthropic_api_key:
1308
1304
  return await self.get_context_window_from_anthropic_async()
1309
1305
  return await self.get_context_window_from_tiktoken_async()
1310
1306
 
@@ -1316,7 +1312,7 @@ class Agent(BaseAgent):
1316
1312
  )
1317
1313
 
1318
1314
  # conversion of messages to OpenAI dict format, which is passed to the token counter
1319
- in_context_messages_openai = [m.to_openai_dict() for m in in_context_messages]
1315
+ in_context_messages_openai = Message.to_openai_dicts_from_list(in_context_messages)
1320
1316
 
1321
1317
  # Extract system, memory and external summary
1322
1318
  if (
@@ -1446,7 +1442,7 @@ class Agent(BaseAgent):
1446
1442
  )
1447
1443
 
1448
1444
  # conversion of messages to anthropic dict format, which is passed to the token counter
1449
- in_context_messages_anthropic = [m.to_anthropic_dict() for m in in_context_messages]
1445
+ in_context_messages_anthropic = Message.to_anthropic_dicts_from_list(in_context_messages)
1450
1446
 
1451
1447
  # Extract system, memory and external summary
1452
1448
  if (
@@ -102,6 +102,23 @@ class BaseAgent(ABC):
102
102
  if tool_rules_solver is not None:
103
103
  tool_constraint_block = tool_rules_solver.compile_tool_rule_prompts()
104
104
 
105
+ # compile archive tags if there's an attached archive
106
+ from letta.services.archive_manager import ArchiveManager
107
+
108
+ archive_manager = ArchiveManager()
109
+ archive = await archive_manager.get_default_archive_for_agent_async(
110
+ agent_id=agent_state.id,
111
+ actor=self.actor,
112
+ )
113
+
114
+ if archive:
115
+ archive_tags = await self.passage_manager.get_unique_tags_for_archive_async(
116
+ archive_id=archive.id,
117
+ actor=self.actor,
118
+ )
119
+ else:
120
+ archive_tags = None
121
+
105
122
  # TODO: This is a pretty brittle pattern established all over our code, need to get rid of this
106
123
  curr_system_message = in_context_messages[0]
107
124
  curr_system_message_text = curr_system_message.content[0].text
@@ -149,6 +166,7 @@ class BaseAgent(ABC):
149
166
  timezone=agent_state.timezone,
150
167
  previous_message_count=num_messages - len(in_context_messages),
151
168
  archival_memory_size=num_archival_memories,
169
+ archive_tags=archive_tags,
152
170
  )
153
171
 
154
172
  diff = united_diff(curr_system_message_text, new_system_message_str)
letta/agents/helpers.py CHANGED
@@ -9,11 +9,11 @@ from letta.schemas.agent import AgentState
9
9
  from letta.schemas.letta_message import MessageType
10
10
  from letta.schemas.letta_response import LettaResponse
11
11
  from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
12
- from letta.schemas.message import Message, MessageCreate
12
+ from letta.schemas.message import Message, MessageCreate, MessageCreateBase
13
13
  from letta.schemas.tool_execution_result import ToolExecutionResult
14
14
  from letta.schemas.usage import LettaUsageStatistics
15
15
  from letta.schemas.user import User
16
- from letta.server.rest_api.utils import create_input_messages
16
+ from letta.server.rest_api.utils import create_approval_response_message_from_input, create_input_messages
17
17
  from letta.services.message_manager import MessageManager
18
18
 
19
19
  logger = get_logger(__name__)
@@ -36,6 +36,8 @@ def _create_letta_response(
36
36
  response_messages = Message.to_letta_messages_from_list(
37
37
  messages=filter_user_messages, use_assistant_message=use_assistant_message, reverse=False
38
38
  )
39
+ # Filter approval response messages
40
+ response_messages = [m for m in response_messages if m.message_type != "approval_response_message"]
39
41
 
40
42
  # Apply message type filtering if specified
41
43
  if include_return_message_types is not None:
@@ -115,13 +117,14 @@ async def _prepare_in_context_messages_async(
115
117
  new_in_context_messages = await message_manager.create_many_messages_async(
116
118
  create_input_messages(input_messages=input_messages, agent_id=agent_state.id, timezone=agent_state.timezone, actor=actor),
117
119
  actor=actor,
120
+ embedding_config=agent_state.embedding_config,
118
121
  )
119
122
 
120
123
  return current_in_context_messages, new_in_context_messages
121
124
 
122
125
 
123
126
  async def _prepare_in_context_messages_no_persist_async(
124
- input_messages: List[MessageCreate],
127
+ input_messages: List[MessageCreateBase],
125
128
  agent_state: AgentState,
126
129
  message_manager: MessageManager,
127
130
  actor: User,
@@ -148,10 +151,32 @@ async def _prepare_in_context_messages_no_persist_async(
148
151
  # Otherwise, include the full list of messages by ID for context
149
152
  current_in_context_messages = await message_manager.get_messages_by_ids_async(message_ids=agent_state.message_ids, actor=actor)
150
153
 
151
- # Create a new user message from the input but dont store it yet
152
- new_in_context_messages = create_input_messages(
153
- input_messages=input_messages, agent_id=agent_state.id, timezone=agent_state.timezone, actor=actor
154
- )
154
+ # Check for approval-related message validation
155
+ if len(input_messages) == 1 and input_messages[0].type == "approval":
156
+ # User is trying to send an approval response
157
+ if current_in_context_messages[-1].role != "approval":
158
+ raise ValueError(
159
+ "Cannot process approval response: No tool call is currently awaiting approval. "
160
+ "Please send a regular message to interact with the agent."
161
+ )
162
+ if input_messages[0].approval_request_id != current_in_context_messages[-1].id:
163
+ raise ValueError(
164
+ f"Invalid approval request ID. Expected '{current_in_context_messages[-1].id}' "
165
+ f"but received '{input_messages[0].approval_request_id}'."
166
+ )
167
+ new_in_context_messages = create_approval_response_message_from_input(agent_state=agent_state, input_message=input_messages[0])
168
+ else:
169
+ # User is trying to send a regular message
170
+ if current_in_context_messages[-1].role == "approval":
171
+ raise ValueError(
172
+ "Cannot send a new message: The agent is waiting for approval on a tool call. "
173
+ "Please approve or deny the pending request before continuing."
174
+ )
175
+
176
+ # Create a new user message from the input but dont store it yet
177
+ new_in_context_messages = create_input_messages(
178
+ input_messages=input_messages, agent_id=agent_state.id, timezone=agent_state.timezone, actor=actor
179
+ )
155
180
 
156
181
  return current_in_context_messages, new_in_context_messages
157
182