letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.11.7.dev20251008104128__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 (145) hide show
  1. letta/adapters/letta_llm_adapter.py +1 -0
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +7 -2
  4. letta/adapters/simple_llm_request_adapter.py +88 -0
  5. letta/adapters/simple_llm_stream_adapter.py +192 -0
  6. letta/agents/agent_loop.py +6 -0
  7. letta/agents/ephemeral_summary_agent.py +2 -1
  8. letta/agents/helpers.py +142 -6
  9. letta/agents/letta_agent.py +13 -33
  10. letta/agents/letta_agent_batch.py +2 -4
  11. letta/agents/letta_agent_v2.py +87 -77
  12. letta/agents/letta_agent_v3.py +899 -0
  13. letta/agents/voice_agent.py +2 -6
  14. letta/constants.py +8 -4
  15. letta/errors.py +40 -0
  16. letta/functions/function_sets/base.py +84 -4
  17. letta/functions/function_sets/multi_agent.py +0 -3
  18. letta/functions/schema_generator.py +113 -71
  19. letta/groups/dynamic_multi_agent.py +3 -2
  20. letta/groups/helpers.py +1 -2
  21. letta/groups/round_robin_multi_agent.py +3 -2
  22. letta/groups/sleeptime_multi_agent.py +3 -2
  23. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  24. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  25. letta/groups/supervisor_multi_agent.py +84 -80
  26. letta/helpers/converters.py +3 -0
  27. letta/helpers/message_helper.py +4 -0
  28. letta/helpers/tool_rule_solver.py +92 -5
  29. letta/interfaces/anthropic_streaming_interface.py +409 -0
  30. letta/interfaces/gemini_streaming_interface.py +296 -0
  31. letta/interfaces/openai_streaming_interface.py +752 -1
  32. letta/llm_api/anthropic_client.py +126 -16
  33. letta/llm_api/bedrock_client.py +4 -2
  34. letta/llm_api/deepseek_client.py +4 -1
  35. letta/llm_api/google_vertex_client.py +123 -42
  36. letta/llm_api/groq_client.py +4 -1
  37. letta/llm_api/llm_api_tools.py +11 -4
  38. letta/llm_api/llm_client_base.py +6 -2
  39. letta/llm_api/openai.py +32 -2
  40. letta/llm_api/openai_client.py +423 -18
  41. letta/llm_api/xai_client.py +4 -1
  42. letta/main.py +9 -5
  43. letta/memory.py +1 -0
  44. letta/orm/__init__.py +1 -1
  45. letta/orm/agent.py +10 -0
  46. letta/orm/block.py +7 -16
  47. letta/orm/blocks_agents.py +8 -2
  48. letta/orm/files_agents.py +2 -0
  49. letta/orm/job.py +7 -5
  50. letta/orm/mcp_oauth.py +1 -0
  51. letta/orm/message.py +21 -6
  52. letta/orm/organization.py +2 -0
  53. letta/orm/provider.py +6 -2
  54. letta/orm/run.py +71 -0
  55. letta/orm/sandbox_config.py +7 -1
  56. letta/orm/sqlalchemy_base.py +0 -306
  57. letta/orm/step.py +6 -5
  58. letta/orm/step_metrics.py +5 -5
  59. letta/otel/tracing.py +28 -3
  60. letta/plugins/defaults.py +4 -4
  61. letta/prompts/system_prompts/__init__.py +2 -0
  62. letta/prompts/system_prompts/letta_v1.py +25 -0
  63. letta/schemas/agent.py +3 -2
  64. letta/schemas/agent_file.py +9 -3
  65. letta/schemas/block.py +23 -10
  66. letta/schemas/enums.py +21 -2
  67. letta/schemas/job.py +17 -4
  68. letta/schemas/letta_message_content.py +71 -2
  69. letta/schemas/letta_stop_reason.py +5 -5
  70. letta/schemas/llm_config.py +53 -3
  71. letta/schemas/memory.py +1 -1
  72. letta/schemas/message.py +504 -117
  73. letta/schemas/openai/responses_request.py +64 -0
  74. letta/schemas/providers/__init__.py +2 -0
  75. letta/schemas/providers/anthropic.py +16 -0
  76. letta/schemas/providers/ollama.py +115 -33
  77. letta/schemas/providers/openrouter.py +52 -0
  78. letta/schemas/providers/vllm.py +2 -1
  79. letta/schemas/run.py +48 -42
  80. letta/schemas/step.py +2 -2
  81. letta/schemas/step_metrics.py +1 -1
  82. letta/schemas/tool.py +15 -107
  83. letta/schemas/tool_rule.py +88 -5
  84. letta/serialize_schemas/marshmallow_agent.py +1 -0
  85. letta/server/db.py +86 -408
  86. letta/server/rest_api/app.py +61 -10
  87. letta/server/rest_api/dependencies.py +14 -0
  88. letta/server/rest_api/redis_stream_manager.py +19 -8
  89. letta/server/rest_api/routers/v1/agents.py +364 -292
  90. letta/server/rest_api/routers/v1/blocks.py +14 -20
  91. letta/server/rest_api/routers/v1/identities.py +45 -110
  92. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  93. letta/server/rest_api/routers/v1/jobs.py +23 -6
  94. letta/server/rest_api/routers/v1/messages.py +1 -1
  95. letta/server/rest_api/routers/v1/runs.py +126 -85
  96. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  97. letta/server/rest_api/routers/v1/tools.py +281 -594
  98. letta/server/rest_api/routers/v1/voice.py +1 -1
  99. letta/server/rest_api/streaming_response.py +29 -29
  100. letta/server/rest_api/utils.py +122 -64
  101. letta/server/server.py +160 -887
  102. letta/services/agent_manager.py +236 -919
  103. letta/services/agent_serialization_manager.py +16 -0
  104. letta/services/archive_manager.py +0 -100
  105. letta/services/block_manager.py +211 -168
  106. letta/services/file_manager.py +1 -1
  107. letta/services/files_agents_manager.py +24 -33
  108. letta/services/group_manager.py +0 -142
  109. letta/services/helpers/agent_manager_helper.py +7 -2
  110. letta/services/helpers/run_manager_helper.py +85 -0
  111. letta/services/job_manager.py +96 -411
  112. letta/services/lettuce/__init__.py +6 -0
  113. letta/services/lettuce/lettuce_client_base.py +86 -0
  114. letta/services/mcp_manager.py +38 -6
  115. letta/services/message_manager.py +165 -362
  116. letta/services/organization_manager.py +0 -36
  117. letta/services/passage_manager.py +0 -345
  118. letta/services/provider_manager.py +0 -80
  119. letta/services/run_manager.py +301 -0
  120. letta/services/sandbox_config_manager.py +0 -234
  121. letta/services/step_manager.py +62 -39
  122. letta/services/summarizer/summarizer.py +9 -7
  123. letta/services/telemetry_manager.py +0 -16
  124. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  125. letta/services/tool_executor/core_tool_executor.py +397 -2
  126. letta/services/tool_executor/files_tool_executor.py +3 -3
  127. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  128. letta/services/tool_executor/tool_execution_manager.py +6 -8
  129. letta/services/tool_executor/tool_executor_base.py +3 -3
  130. letta/services/tool_manager.py +85 -339
  131. letta/services/tool_sandbox/base.py +24 -13
  132. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  133. letta/services/tool_schema_generator.py +123 -0
  134. letta/services/user_manager.py +0 -99
  135. letta/settings.py +20 -4
  136. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
  137. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
  138. letta/agents/temporal/activities/__init__.py +0 -4
  139. letta/agents/temporal/activities/example_activity.py +0 -7
  140. letta/agents/temporal/activities/prepare_messages.py +0 -10
  141. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  142. letta/agents/temporal/types.py +0 -25
  143. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
  144. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
  145. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
@@ -60,13 +60,6 @@ class GroupManager:
60
60
  groups = result.scalars().all()
61
61
  return [group.to_pydantic() for group in groups]
62
62
 
63
- @enforce_types
64
- @trace_method
65
- def retrieve_group(self, group_id: str, actor: PydanticUser) -> PydanticGroup:
66
- with db_registry.session() as session:
67
- group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
68
- return group.to_pydantic()
69
-
70
63
  @enforce_types
71
64
  @trace_method
72
65
  async def retrieve_group_async(self, group_id: str, actor: PydanticUser) -> PydanticGroup:
@@ -74,57 +67,6 @@ class GroupManager:
74
67
  group = await GroupModel.read_async(db_session=session, identifier=group_id, actor=actor)
75
68
  return group.to_pydantic()
76
69
 
77
- @enforce_types
78
- @trace_method
79
- def create_group(self, group: Union[GroupCreate, InternalTemplateGroupCreate], actor: PydanticUser) -> PydanticGroup:
80
- with db_registry.session() as session:
81
- new_group = GroupModel()
82
- new_group.organization_id = actor.organization_id
83
- new_group.description = group.description
84
-
85
- match group.manager_config.manager_type:
86
- case ManagerType.round_robin:
87
- new_group.manager_type = ManagerType.round_robin
88
- new_group.max_turns = group.manager_config.max_turns
89
- case ManagerType.dynamic:
90
- new_group.manager_type = ManagerType.dynamic
91
- new_group.manager_agent_id = group.manager_config.manager_agent_id
92
- new_group.max_turns = group.manager_config.max_turns
93
- new_group.termination_token = group.manager_config.termination_token
94
- case ManagerType.supervisor:
95
- new_group.manager_type = ManagerType.supervisor
96
- new_group.manager_agent_id = group.manager_config.manager_agent_id
97
- case ManagerType.sleeptime:
98
- new_group.manager_type = ManagerType.sleeptime
99
- new_group.manager_agent_id = group.manager_config.manager_agent_id
100
- new_group.sleeptime_agent_frequency = group.manager_config.sleeptime_agent_frequency
101
- if new_group.sleeptime_agent_frequency:
102
- new_group.turns_counter = -1
103
- case ManagerType.voice_sleeptime:
104
- new_group.manager_type = ManagerType.voice_sleeptime
105
- new_group.manager_agent_id = group.manager_config.manager_agent_id
106
- max_message_buffer_length = group.manager_config.max_message_buffer_length
107
- min_message_buffer_length = group.manager_config.min_message_buffer_length
108
- # Safety check for buffer length range
109
- self.ensure_buffer_length_range_valid(max_value=max_message_buffer_length, min_value=min_message_buffer_length)
110
- new_group.max_message_buffer_length = max_message_buffer_length
111
- new_group.min_message_buffer_length = min_message_buffer_length
112
- case _:
113
- raise ValueError(f"Unsupported manager type: {group.manager_config.manager_type}")
114
-
115
- if isinstance(group, InternalTemplateGroupCreate):
116
- new_group.base_template_id = group.base_template_id
117
- new_group.template_id = group.template_id
118
- new_group.deployment_id = group.deployment_id
119
-
120
- self._process_agent_relationship(session=session, group=new_group, agent_ids=group.agent_ids, allow_partial=False)
121
-
122
- if group.shared_block_ids:
123
- self._process_shared_block_relationship(session=session, group=new_group, block_ids=group.shared_block_ids)
124
-
125
- new_group.create(session, actor=actor)
126
- return new_group.to_pydantic()
127
-
128
70
  @enforce_types
129
71
  async def create_group_async(self, group: Union[GroupCreate, InternalTemplateGroupCreate], actor: PydanticUser) -> PydanticGroup:
130
72
  async with db_registry.async_session() as session:
@@ -238,14 +180,6 @@ class GroupManager:
238
180
  await group.update_async(session, actor=actor)
239
181
  return group.to_pydantic()
240
182
 
241
- @enforce_types
242
- @trace_method
243
- def delete_group(self, group_id: str, actor: PydanticUser) -> None:
244
- with db_registry.session() as session:
245
- # Retrieve the agent
246
- group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
247
- group.hard_delete(session)
248
-
249
183
  @enforce_types
250
184
  @trace_method
251
185
  async def delete_group_async(self, group_id: str, actor: PydanticUser) -> None:
@@ -253,43 +187,6 @@ class GroupManager:
253
187
  group = await GroupModel.read_async(db_session=session, identifier=group_id, actor=actor)
254
188
  await group.hard_delete_async(session)
255
189
 
256
- @enforce_types
257
- @trace_method
258
- def list_group_messages(
259
- self,
260
- actor: PydanticUser,
261
- group_id: Optional[str] = None,
262
- before: Optional[str] = None,
263
- after: Optional[str] = None,
264
- limit: Optional[int] = 50,
265
- use_assistant_message: bool = True,
266
- assistant_message_tool_name: str = "send_message",
267
- assistant_message_tool_kwarg: str = "message",
268
- ) -> list[LettaMessage]:
269
- with db_registry.session() as session:
270
- filters = {
271
- "organization_id": actor.organization_id,
272
- "group_id": group_id,
273
- }
274
- messages = MessageModel.list(
275
- db_session=session,
276
- before=before,
277
- after=after,
278
- limit=limit,
279
- **filters,
280
- )
281
-
282
- messages = PydanticMessage.to_letta_messages_from_list(
283
- messages=[msg.to_pydantic() for msg in messages],
284
- use_assistant_message=use_assistant_message,
285
- assistant_message_tool_name=assistant_message_tool_name,
286
- assistant_message_tool_kwarg=assistant_message_tool_kwarg,
287
- )
288
-
289
- # TODO: filter messages to return a clean conversation history
290
-
291
- return messages
292
-
293
190
  @enforce_types
294
191
  @trace_method
295
192
  async def list_group_messages_async(
@@ -327,20 +224,6 @@ class GroupManager:
327
224
 
328
225
  return messages
329
226
 
330
- @enforce_types
331
- @trace_method
332
- def reset_messages(self, group_id: str, actor: PydanticUser) -> None:
333
- with db_registry.session() as session:
334
- # Ensure group is loadable by user
335
- group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
336
-
337
- # Delete all messages in the group
338
- session.query(MessageModel).filter(
339
- MessageModel.organization_id == actor.organization_id, MessageModel.group_id == group_id
340
- ).delete(synchronize_session=False)
341
-
342
- session.commit()
343
-
344
227
  @enforce_types
345
228
  @trace_method
346
229
  async def reset_messages_async(self, group_id: str, actor: PydanticUser) -> None:
@@ -356,18 +239,6 @@ class GroupManager:
356
239
 
357
240
  await session.commit()
358
241
 
359
- @enforce_types
360
- @trace_method
361
- def bump_turns_counter(self, group_id: str, actor: PydanticUser) -> int:
362
- with db_registry.session() as session:
363
- # Ensure group is loadable by user
364
- group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
365
-
366
- # Update turns counter
367
- group.turns_counter = (group.turns_counter + 1) % group.sleeptime_agent_frequency
368
- group.update(session, actor=actor)
369
- return group.turns_counter
370
-
371
242
  @enforce_types
372
243
  @trace_method
373
244
  async def bump_turns_counter_async(self, group_id: str, actor: PydanticUser) -> int:
@@ -380,19 +251,6 @@ class GroupManager:
380
251
  await group.update_async(session, actor=actor)
381
252
  return group.turns_counter
382
253
 
383
- @enforce_types
384
- def get_last_processed_message_id_and_update(self, group_id: str, last_processed_message_id: str, actor: PydanticUser) -> str:
385
- with db_registry.session() as session:
386
- # Ensure group is loadable by user
387
- group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
388
-
389
- # Update last processed message id
390
- prev_last_processed_message_id = group.last_processed_message_id
391
- group.last_processed_message_id = last_processed_message_id
392
- group.update(session, actor=actor)
393
-
394
- return prev_last_processed_message_id
395
-
396
254
  @enforce_types
397
255
  @trace_method
398
256
  async def get_last_processed_message_id_and_update_async(
@@ -19,6 +19,7 @@ from letta.constants import (
19
19
  MULTI_AGENT_TOOLS,
20
20
  STRUCTURED_OUTPUT_MODELS,
21
21
  )
22
+ from letta.errors import LettaAgentNotFoundError
22
23
  from letta.helpers import ToolRulesSolver
23
24
  from letta.helpers.datetime_helpers import get_local_time
24
25
  from letta.llm_api.llm_client import LLMClient
@@ -206,6 +207,10 @@ def derive_system_message(agent_type: AgentType, enable_sleeptime: Optional[bool
206
207
  elif agent_type == AgentType.react_agent:
207
208
  system = gpt_system.get_system_text("react")
208
209
 
210
+ # Letta v1
211
+ elif agent_type == AgentType.letta_v1_agent:
212
+ system = gpt_system.get_system_text("letta_v1")
213
+
209
214
  # Workflow
210
215
  elif agent_type == AgentType.workflow_agent:
211
216
  system = gpt_system.get_system_text("workflow")
@@ -419,7 +424,7 @@ async def initialize_message_sequence_async(
419
424
  # Some LMStudio models (e.g. ministral) require the tool call ID to be 9 alphanumeric characters
420
425
  tool_call_id = uuid_str[:9] if llm_config.provider_name == "lmstudio_openai" else uuid_str
421
426
 
422
- if agent_state.agent_type == AgentType.sleeptime_agent:
427
+ if agent_state.agent_type == AgentType.sleeptime_agent or agent_state.agent_type == AgentType.letta_v1_agent:
423
428
  initial_boot_messages = []
424
429
  elif llm_config.model is not None and "gpt-3.5" in llm_config.model:
425
430
  initial_boot_messages = get_initial_boot_messages("startup_with_send_message_gpt35", agent_state.timezone, tool_call_id)
@@ -1238,4 +1243,4 @@ async def validate_agent_exists_async(session, agent_id: str, actor: User) -> No
1238
1243
  result = await session.execute(agent_exists_query)
1239
1244
 
1240
1245
  if not result.scalar():
1241
- raise NoResultFound(f"Agent with ID {agent_id} not found")
1246
+ raise LettaAgentNotFoundError(f"Agent with ID {agent_id} not found")
@@ -0,0 +1,85 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from sqlalchemy import asc, desc, nulls_last, select
5
+ from letta.settings import DatabaseChoice, settings
6
+
7
+ from letta.orm.run import Run as RunModel
8
+ from letta.settings import DatabaseChoice, settings
9
+ from sqlalchemy import asc, desc
10
+ from typing import Optional
11
+
12
+ from letta.services.helpers.agent_manager_helper import _cursor_filter
13
+
14
+
15
+ async def _apply_pagination_async(
16
+ query,
17
+ before: Optional[str],
18
+ after: Optional[str],
19
+ session,
20
+ ascending: bool = True,
21
+ sort_by: str = "created_at",
22
+ ) -> any:
23
+ # Determine the sort column
24
+ if sort_by == "last_run_completion":
25
+ sort_column = RunModel.last_run_completion
26
+ sort_nulls_last = True # TODO: handle this as a query param eventually
27
+ else:
28
+ sort_column = RunModel.created_at
29
+ sort_nulls_last = False
30
+
31
+ if after:
32
+ result = (
33
+ await session.execute(
34
+ select(sort_column, RunModel.id).where(RunModel.id == after)
35
+ )
36
+ ).first()
37
+ if result:
38
+ after_sort_value, after_id = result
39
+ # SQLite does not support as granular timestamping, so we need to round the timestamp
40
+ if settings.database_engine is DatabaseChoice.SQLITE and isinstance(
41
+ after_sort_value, datetime
42
+ ):
43
+ after_sort_value = after_sort_value.strftime("%Y-%m-%d %H:%M:%S")
44
+ query = query.where(
45
+ _cursor_filter(
46
+ sort_column,
47
+ RunModel.id,
48
+ after_sort_value,
49
+ after_id,
50
+ forward=ascending,
51
+ nulls_last=sort_nulls_last,
52
+ )
53
+ )
54
+
55
+ if before:
56
+ result = (
57
+ await session.execute(
58
+ select(sort_column, RunModel.id).where(RunModel.id == before)
59
+ )
60
+ ).first()
61
+ if result:
62
+ before_sort_value, before_id = result
63
+ # SQLite does not support as granular timestamping, so we need to round the timestamp
64
+ if settings.database_engine is DatabaseChoice.SQLITE and isinstance(
65
+ before_sort_value, datetime
66
+ ):
67
+ before_sort_value = before_sort_value.strftime("%Y-%m-%d %H:%M:%S")
68
+ query = query.where(
69
+ _cursor_filter(
70
+ sort_column,
71
+ RunModel.id,
72
+ before_sort_value,
73
+ before_id,
74
+ forward=not ascending,
75
+ nulls_last=sort_nulls_last,
76
+ )
77
+ )
78
+
79
+ # Apply ordering
80
+ order_fn = asc if ascending else desc
81
+ query = query.order_by(
82
+ nulls_last(order_fn(sort_column)) if sort_nulls_last else order_fn(sort_column),
83
+ order_fn(RunModel.id),
84
+ )
85
+ return query