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
@@ -7,8 +7,7 @@ from typing import TYPE_CHECKING, AsyncGenerator, Dict, Iterable, List, Optional
7
7
 
8
8
  from fastapi import Header, HTTPException
9
9
  from openai.types.chat import ChatCompletionMessageParam
10
- from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall
11
- from openai.types.chat.chat_completion_message_tool_call import Function as OpenAIFunction
10
+ from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall, Function as OpenAIFunction
12
11
  from openai.types.chat.completion_create_params import CompletionCreateParams
13
12
  from pydantic import BaseModel
14
13
 
@@ -26,10 +25,11 @@ from letta.log import get_logger
26
25
  from letta.otel.context import get_ctx_attributes
27
26
  from letta.otel.metric_registry import MetricRegistry
28
27
  from letta.otel.tracing import tracer
28
+ from letta.schemas.agent import AgentState
29
29
  from letta.schemas.enums import MessageRole
30
30
  from letta.schemas.letta_message_content import OmittedReasoningContent, ReasoningContent, RedactedReasoningContent, TextContent
31
31
  from letta.schemas.llm_config import LLMConfig
32
- from letta.schemas.message import Message, MessageCreate, ToolReturn
32
+ from letta.schemas.message import ApprovalCreate, Message, MessageCreate, ToolReturn
33
33
  from letta.schemas.tool_execution_result import ToolExecutionResult
34
34
  from letta.schemas.usage import LettaUsageStatistics
35
35
  from letta.schemas.user import User
@@ -177,25 +177,31 @@ def create_input_messages(input_messages: List[MessageCreate], agent_id: str, ti
177
177
  return messages
178
178
 
179
179
 
180
- def create_letta_messages_from_llm_response(
180
+ def create_approval_response_message_from_input(agent_state: AgentState, input_message: ApprovalCreate) -> List[Message]:
181
+ return [
182
+ Message(
183
+ role=MessageRole.approval,
184
+ agent_id=agent_state.id,
185
+ model=agent_state.llm_config.model,
186
+ approval_request_id=input_message.approval_request_id,
187
+ approve=input_message.approve,
188
+ denial_reason=input_message.reason,
189
+ )
190
+ ]
191
+
192
+
193
+ def create_approval_request_message_from_llm_response(
181
194
  agent_id: str,
182
195
  model: str,
183
196
  function_name: str,
184
197
  function_arguments: Dict,
185
- tool_execution_result: ToolExecutionResult,
186
198
  tool_call_id: str,
187
- function_call_success: bool,
188
- function_response: Optional[str],
189
- timezone: str,
190
199
  actor: User,
191
200
  continue_stepping: bool = False,
192
- heartbeat_reason: Optional[str] = None,
193
201
  reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
194
202
  pre_computed_assistant_message_id: Optional[str] = None,
195
- llm_batch_item_id: Optional[str] = None,
196
203
  step_id: str | None = None,
197
- ) -> List[Message]:
198
- messages = []
204
+ ) -> Message:
199
205
  # Construct the tool call with the assistant's message
200
206
  # Force set request_heartbeat in tool_args to calculated continue_stepping
201
207
  function_arguments[REQUEST_HEARTBEAT_PARAM] = continue_stepping
@@ -209,19 +215,68 @@ def create_letta_messages_from_llm_response(
209
215
  )
210
216
  # TODO: Use ToolCallContent instead of tool_calls
211
217
  # TODO: This helps preserve ordering
212
- assistant_message = Message(
213
- role=MessageRole.assistant,
218
+ approval_message = Message(
219
+ role=MessageRole.approval,
214
220
  content=reasoning_content if reasoning_content else [],
215
221
  agent_id=agent_id,
216
222
  model=model,
217
223
  tool_calls=[tool_call],
218
224
  tool_call_id=tool_call_id,
219
225
  created_at=get_utc_time(),
220
- batch_item_id=llm_batch_item_id,
226
+ step_id=step_id,
221
227
  )
222
228
  if pre_computed_assistant_message_id:
223
- assistant_message.id = pre_computed_assistant_message_id
224
- messages.append(assistant_message)
229
+ approval_message.id = pre_computed_assistant_message_id
230
+ return approval_message
231
+
232
+
233
+ def create_letta_messages_from_llm_response(
234
+ agent_id: str,
235
+ model: str,
236
+ function_name: str,
237
+ function_arguments: Dict,
238
+ tool_execution_result: ToolExecutionResult,
239
+ tool_call_id: str,
240
+ function_call_success: bool,
241
+ function_response: Optional[str],
242
+ timezone: str,
243
+ actor: User,
244
+ continue_stepping: bool = False,
245
+ heartbeat_reason: Optional[str] = None,
246
+ reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
247
+ pre_computed_assistant_message_id: Optional[str] = None,
248
+ llm_batch_item_id: Optional[str] = None,
249
+ step_id: str | None = None,
250
+ is_approval_response: bool | None = None,
251
+ ) -> List[Message]:
252
+ messages = []
253
+ if not is_approval_response:
254
+ # Construct the tool call with the assistant's message
255
+ # Force set request_heartbeat in tool_args to calculated continue_stepping
256
+ function_arguments[REQUEST_HEARTBEAT_PARAM] = continue_stepping
257
+ tool_call = OpenAIToolCall(
258
+ id=tool_call_id,
259
+ function=OpenAIFunction(
260
+ name=function_name,
261
+ arguments=json.dumps(function_arguments),
262
+ ),
263
+ type="function",
264
+ )
265
+ # TODO: Use ToolCallContent instead of tool_calls
266
+ # TODO: This helps preserve ordering
267
+ assistant_message = Message(
268
+ role=MessageRole.assistant,
269
+ content=reasoning_content if reasoning_content else [],
270
+ agent_id=agent_id,
271
+ model=model,
272
+ tool_calls=[tool_call],
273
+ tool_call_id=tool_call_id,
274
+ created_at=get_utc_time(),
275
+ batch_item_id=llm_batch_item_id,
276
+ )
277
+ if pre_computed_assistant_message_id:
278
+ assistant_message.id = pre_computed_assistant_message_id
279
+ messages.append(assistant_message)
225
280
 
226
281
  # TODO: Use ToolReturnContent instead of TextContent
227
282
  # TODO: This helps preserve ordering
@@ -394,7 +449,9 @@ def convert_in_context_letta_messages_to_openai(in_context_messages: List[Messag
394
449
  pass # It's not JSON, leave as-is
395
450
 
396
451
  # Finally, convert to dict using your existing method
397
- openai_messages.append(msg.to_openai_dict())
452
+ m = msg.to_openai_dict()
453
+ assert m is not None
454
+ openai_messages.append(m)
398
455
 
399
456
  return openai_messages
400
457
 
letta/server/server.py CHANGED
@@ -30,8 +30,10 @@ from letta.helpers.datetime_helpers import get_utc_time
30
30
  from letta.helpers.json_helpers import json_dumps, json_loads
31
31
 
32
32
  # TODO use custom interface
33
- from letta.interface import AgentInterface # abstract
34
- from letta.interface import CLIInterface # for printing to terminal
33
+ from letta.interface import (
34
+ AgentInterface, # abstract
35
+ CLIInterface, # for printing to terminal
36
+ )
35
37
  from letta.log import get_logger
36
38
  from letta.orm.errors import NoResultFound
37
39
  from letta.otel.tracing import log_event, trace_method
@@ -52,7 +54,7 @@ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
52
54
  from letta.schemas.llm_config import LLMConfig
53
55
  from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary
54
56
  from letta.schemas.message import Message, MessageCreate, MessageUpdate
55
- from letta.schemas.passage import Passage, PassageUpdate
57
+ from letta.schemas.passage import Passage
56
58
  from letta.schemas.pip_requirement import PipRequirement
57
59
  from letta.schemas.providers import (
58
60
  AnthropicProvider,
@@ -1114,7 +1116,7 @@ class SyncServer(Server):
1114
1116
  ascending: Optional[bool] = True,
1115
1117
  ) -> List[Passage]:
1116
1118
  # iterate over records
1117
- records = await self.agent_manager.list_agent_passages_async(
1119
+ records = await self.agent_manager.query_agent_passages_async(
1118
1120
  actor=actor,
1119
1121
  agent_id=agent_id,
1120
1122
  after=after,
@@ -1125,18 +1127,17 @@ class SyncServer(Server):
1125
1127
  )
1126
1128
  return records
1127
1129
 
1128
- async def insert_archival_memory_async(self, agent_id: str, memory_contents: str, actor: User) -> List[Passage]:
1130
+ async def insert_archival_memory_async(
1131
+ self, agent_id: str, memory_contents: str, actor: User, tags: Optional[List[str]], created_at: Optional[datetime]
1132
+ ) -> List[Passage]:
1129
1133
  # Get the agent object (loaded in memory)
1130
1134
  agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
1131
1135
 
1132
- # Insert passages into the archive
1133
- passages = await self.passage_manager.insert_passage(agent_state=agent_state, text=memory_contents, actor=actor)
1134
-
1135
- return passages
1136
+ # Use passage manager which handles dual-write to Turbopuffer if enabled
1137
+ passages = await self.passage_manager.insert_passage(
1138
+ agent_state=agent_state, text=memory_contents, tags=tags, actor=actor, created_at=created_at
1139
+ )
1136
1140
 
1137
- def modify_archival_memory(self, agent_id: str, memory_id: str, passage: PassageUpdate, actor: User) -> List[Passage]:
1138
- passage = Passage(**passage.model_dump(exclude_unset=True, exclude_none=True))
1139
- passages = self.passage_manager.update_passage_by_id(passage_id=memory_id, passage=passage, actor=actor)
1140
1141
  return passages
1141
1142
 
1142
1143
  async def delete_archival_memory_async(self, memory_id: str, actor: User):
@@ -1270,7 +1271,7 @@ class SyncServer(Server):
1270
1271
  await self.source_manager.delete_source(source_id=source_id, actor=actor)
1271
1272
 
1272
1273
  # delete data from passage store
1273
- passages_to_be_deleted = await self.agent_manager.list_passages_async(actor=actor, source_id=source_id, limit=None)
1274
+ passages_to_be_deleted = await self.agent_manager.query_source_passages_async(actor=actor, source_id=source_id, limit=None)
1274
1275
  await self.passage_manager.delete_source_passages_async(actor=actor, passages=passages_to_be_deleted)
1275
1276
 
1276
1277
  # TODO: delete data from agent passage stores (?)
@@ -1316,27 +1317,6 @@ class SyncServer(Server):
1316
1317
  async def sleeptime_document_ingest_async(
1317
1318
  self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
1318
1319
  ) -> None:
1319
- # TEMPORARILY DISABLE UNTIL V2
1320
- # sleeptime_agent_state = await self.create_document_sleeptime_agent_async(main_agent, source, actor, clear_history)
1321
- # sleeptime_agent = LettaAgent(
1322
- # agent_id=sleeptime_agent_state.id,
1323
- # message_manager=self.message_manager,
1324
- # agent_manager=self.agent_manager,
1325
- # block_manager=self.block_manager,
1326
- # job_manager=self.job_manager,
1327
- # passage_manager=self.passage_manager,
1328
- # actor=actor,
1329
- # step_manager=self.step_manager,
1330
- # telemetry_manager=self.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
1331
- # )
1332
- # passages = await self.agent_manager.list_passages_async(actor=actor, source_id=source.id)
1333
- # for passage in passages:
1334
- # await sleeptime_agent.step(
1335
- # input_messages=[
1336
- # MessageCreate(role="user", content=passage.text),
1337
- # ]
1338
- # )
1339
- # await self.agent_manager.delete_agent_async(agent_id=sleeptime_agent_state.id, actor=actor)
1340
1320
  pass
1341
1321
 
1342
1322
  async def _remove_file_from_agent(self, agent_id: str, file_id: str, actor: User) -> None:
@@ -1556,7 +1536,16 @@ class SyncServer(Server):
1556
1536
  local_configs = self.get_local_llm_configs()
1557
1537
  llm_models.extend(local_configs)
1558
1538
 
1559
- return llm_models
1539
+ # dedupe by handle for uniqueness
1540
+ # Seems like this is required from the tests?
1541
+ seen_handles = set()
1542
+ unique_models = []
1543
+ for model in llm_models:
1544
+ if model.handle not in seen_handles:
1545
+ seen_handles.add(model.handle)
1546
+ unique_models.append(model)
1547
+
1548
+ return unique_models
1560
1549
 
1561
1550
  def list_embedding_models(self, actor: User) -> List[EmbeddingConfig]:
1562
1551
  """List available embedding models"""