letta-nightly 0.7.30.dev20250603104343__py3-none-any.whl → 0.8.0.dev20250604201135__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 (136) hide show
  1. letta/__init__.py +7 -1
  2. letta/agent.py +14 -7
  3. letta/agents/base_agent.py +1 -0
  4. letta/agents/ephemeral_summary_agent.py +104 -0
  5. letta/agents/helpers.py +35 -3
  6. letta/agents/letta_agent.py +492 -176
  7. letta/agents/letta_agent_batch.py +22 -16
  8. letta/agents/prompts/summary_system_prompt.txt +62 -0
  9. letta/agents/voice_agent.py +22 -7
  10. letta/agents/voice_sleeptime_agent.py +13 -8
  11. letta/constants.py +33 -1
  12. letta/data_sources/connectors.py +52 -36
  13. letta/errors.py +4 -0
  14. letta/functions/ast_parsers.py +13 -30
  15. letta/functions/function_sets/base.py +3 -1
  16. letta/functions/functions.py +2 -0
  17. letta/functions/mcp_client/base_client.py +151 -97
  18. letta/functions/mcp_client/sse_client.py +49 -31
  19. letta/functions/mcp_client/stdio_client.py +107 -106
  20. letta/functions/schema_generator.py +22 -22
  21. letta/groups/helpers.py +3 -4
  22. letta/groups/sleeptime_multi_agent.py +4 -4
  23. letta/groups/sleeptime_multi_agent_v2.py +22 -0
  24. letta/helpers/composio_helpers.py +16 -0
  25. letta/helpers/converters.py +20 -0
  26. letta/helpers/datetime_helpers.py +1 -6
  27. letta/helpers/tool_rule_solver.py +2 -1
  28. letta/interfaces/anthropic_streaming_interface.py +17 -2
  29. letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
  30. letta/interfaces/openai_streaming_interface.py +18 -2
  31. letta/llm_api/anthropic_client.py +24 -3
  32. letta/llm_api/google_ai_client.py +0 -15
  33. letta/llm_api/google_vertex_client.py +6 -5
  34. letta/llm_api/llm_client_base.py +15 -0
  35. letta/llm_api/openai.py +2 -2
  36. letta/llm_api/openai_client.py +60 -8
  37. letta/orm/__init__.py +2 -0
  38. letta/orm/agent.py +45 -43
  39. letta/orm/base.py +0 -2
  40. letta/orm/block.py +1 -0
  41. letta/orm/custom_columns.py +13 -0
  42. letta/orm/enums.py +5 -0
  43. letta/orm/file.py +3 -1
  44. letta/orm/files_agents.py +68 -0
  45. letta/orm/mcp_server.py +48 -0
  46. letta/orm/message.py +1 -0
  47. letta/orm/organization.py +11 -2
  48. letta/orm/passage.py +25 -10
  49. letta/orm/sandbox_config.py +5 -2
  50. letta/orm/sqlalchemy_base.py +171 -110
  51. letta/prompts/system/memgpt_base.txt +6 -1
  52. letta/prompts/system/memgpt_v2_chat.txt +57 -0
  53. letta/prompts/system/sleeptime.txt +2 -0
  54. letta/prompts/system/sleeptime_v2.txt +28 -0
  55. letta/schemas/agent.py +87 -20
  56. letta/schemas/block.py +7 -1
  57. letta/schemas/file.py +57 -0
  58. letta/schemas/mcp.py +74 -0
  59. letta/schemas/memory.py +5 -2
  60. letta/schemas/message.py +9 -0
  61. letta/schemas/openai/openai.py +0 -6
  62. letta/schemas/providers.py +33 -4
  63. letta/schemas/tool.py +26 -21
  64. letta/schemas/tool_execution_result.py +5 -0
  65. letta/server/db.py +23 -8
  66. letta/server/rest_api/app.py +73 -56
  67. letta/server/rest_api/interface.py +4 -4
  68. letta/server/rest_api/routers/v1/agents.py +132 -47
  69. letta/server/rest_api/routers/v1/blocks.py +3 -2
  70. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  71. letta/server/rest_api/routers/v1/groups.py +3 -3
  72. letta/server/rest_api/routers/v1/jobs.py +14 -17
  73. letta/server/rest_api/routers/v1/organizations.py +10 -10
  74. letta/server/rest_api/routers/v1/providers.py +12 -10
  75. letta/server/rest_api/routers/v1/runs.py +3 -3
  76. letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
  77. letta/server/rest_api/routers/v1/sources.py +108 -43
  78. letta/server/rest_api/routers/v1/steps.py +8 -6
  79. letta/server/rest_api/routers/v1/tools.py +134 -95
  80. letta/server/rest_api/utils.py +12 -1
  81. letta/server/server.py +272 -73
  82. letta/services/agent_manager.py +246 -313
  83. letta/services/block_manager.py +30 -9
  84. letta/services/context_window_calculator/__init__.py +0 -0
  85. letta/services/context_window_calculator/context_window_calculator.py +150 -0
  86. letta/services/context_window_calculator/token_counter.py +82 -0
  87. letta/services/file_processor/__init__.py +0 -0
  88. letta/services/file_processor/chunker/__init__.py +0 -0
  89. letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
  90. letta/services/file_processor/embedder/__init__.py +0 -0
  91. letta/services/file_processor/embedder/openai_embedder.py +84 -0
  92. letta/services/file_processor/file_processor.py +123 -0
  93. letta/services/file_processor/parser/__init__.py +0 -0
  94. letta/services/file_processor/parser/base_parser.py +9 -0
  95. letta/services/file_processor/parser/mistral_parser.py +54 -0
  96. letta/services/file_processor/types.py +0 -0
  97. letta/services/files_agents_manager.py +184 -0
  98. letta/services/group_manager.py +118 -0
  99. letta/services/helpers/agent_manager_helper.py +76 -21
  100. letta/services/helpers/tool_execution_helper.py +3 -0
  101. letta/services/helpers/tool_parser_helper.py +100 -0
  102. letta/services/identity_manager.py +44 -42
  103. letta/services/job_manager.py +21 -10
  104. letta/services/mcp/base_client.py +5 -2
  105. letta/services/mcp/sse_client.py +3 -5
  106. letta/services/mcp/stdio_client.py +3 -5
  107. letta/services/mcp_manager.py +281 -0
  108. letta/services/message_manager.py +40 -26
  109. letta/services/organization_manager.py +55 -19
  110. letta/services/passage_manager.py +211 -13
  111. letta/services/provider_manager.py +48 -2
  112. letta/services/sandbox_config_manager.py +105 -0
  113. letta/services/source_manager.py +4 -5
  114. letta/services/step_manager.py +9 -6
  115. letta/services/summarizer/summarizer.py +50 -23
  116. letta/services/telemetry_manager.py +7 -0
  117. letta/services/tool_executor/tool_execution_manager.py +11 -52
  118. letta/services/tool_executor/tool_execution_sandbox.py +4 -34
  119. letta/services/tool_executor/tool_executor.py +107 -105
  120. letta/services/tool_manager.py +56 -17
  121. letta/services/tool_sandbox/base.py +39 -92
  122. letta/services/tool_sandbox/e2b_sandbox.py +16 -11
  123. letta/services/tool_sandbox/local_sandbox.py +51 -23
  124. letta/services/user_manager.py +36 -3
  125. letta/settings.py +10 -3
  126. letta/templates/__init__.py +0 -0
  127. letta/templates/sandbox_code_file.py.j2 +47 -0
  128. letta/templates/template_helper.py +16 -0
  129. letta/tracing.py +30 -1
  130. letta/types/__init__.py +7 -0
  131. letta/utils.py +25 -1
  132. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/METADATA +7 -2
  133. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/RECORD +136 -110
  134. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/LICENSE +0 -0
  135. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/WHEEL +0 -0
  136. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/entry_points.txt +0 -0
letta/__init__.py CHANGED
@@ -1,4 +1,10 @@
1
- __version__ = "0.7.29"
1
+ import os
2
+
3
+ __version__ = "0.8.0"
4
+
5
+ if os.environ.get("LETTA_VERSION"):
6
+ __version__ = os.environ["LETTA_VERSION"]
7
+
2
8
 
3
9
  # import clients
4
10
  from letta.client.client import RESTClient
letta/agent.py CHANGED
@@ -27,7 +27,6 @@ from letta.errors import ContextWindowExceededError
27
27
  from letta.functions.ast_parsers import coerce_dict_args_by_annotations, get_function_annotations_from_source
28
28
  from letta.functions.composio_helpers import execute_composio_action, generate_composio_action_from_func_name
29
29
  from letta.functions.functions import get_function_from_module
30
- from letta.functions.mcp_client.base_client import BaseMCPClient
31
30
  from letta.helpers import ToolRulesSolver
32
31
  from letta.helpers.composio_helpers import get_composio_api_key
33
32
  from letta.helpers.datetime_helpers import get_utc_time
@@ -60,7 +59,9 @@ from letta.schemas.usage import LettaUsageStatistics
60
59
  from letta.services.agent_manager import AgentManager
61
60
  from letta.services.block_manager import BlockManager
62
61
  from letta.services.helpers.agent_manager_helper import check_supports_structured_output, compile_memory_metadata_block
62
+ from letta.services.helpers.tool_parser_helper import runtime_override_tool_json_schema
63
63
  from letta.services.job_manager import JobManager
64
+ from letta.services.mcp.base_client import AsyncBaseMCPClient
64
65
  from letta.services.message_manager import MessageManager
65
66
  from letta.services.passage_manager import PassageManager
66
67
  from letta.services.provider_manager import ProviderManager
@@ -103,7 +104,7 @@ class Agent(BaseAgent):
103
104
  # extras
104
105
  first_message_verify_mono: bool = True, # TODO move to config?
105
106
  # MCP sessions, state held in-memory in the server
106
- mcp_clients: Optional[Dict[str, BaseMCPClient]] = None,
107
+ mcp_clients: Optional[Dict[str, AsyncBaseMCPClient]] = None,
107
108
  save_last_response: bool = False,
108
109
  ):
109
110
  assert isinstance(agent_state.memory, Memory), f"Memory object is not of type Memory: {type(agent_state.memory)}"
@@ -168,7 +169,11 @@ class Agent(BaseAgent):
168
169
  self.logger = get_logger(agent_state.id)
169
170
 
170
171
  # MCPClient, state/sessions managed by the server
171
- self.mcp_clients = mcp_clients
172
+ # TODO: This is temporary, as a bridge
173
+ self.mcp_clients = None
174
+ # TODO: no longer supported
175
+ # if mcp_clients:
176
+ # self.mcp_clients = {client_id: client.to_sync_client() for client_id, client in mcp_clients.items()}
172
177
 
173
178
  def load_last_function_response(self):
174
179
  """Load the last function response from message history"""
@@ -217,6 +222,7 @@ class Agent(BaseAgent):
217
222
  # refresh memory from DB (using block ids)
218
223
  self.agent_state.memory = Memory(
219
224
  blocks=[self.block_manager.get_block_by_id(block.id, actor=self.user) for block in self.agent_state.memory.get_blocks()],
225
+ file_blocks=self.agent_state.memory.file_blocks,
220
226
  prompt_template=get_prompt_template_for_agent_type(self.agent_state.agent_type),
221
227
  )
222
228
 
@@ -226,6 +232,7 @@ class Agent(BaseAgent):
226
232
  self.agent_state = self.agent_manager.rebuild_system_prompt(agent_id=self.agent_state.id, actor=self.user)
227
233
 
228
234
  return True
235
+
229
236
  return False
230
237
 
231
238
  def _handle_function_error_response(
@@ -323,7 +330,9 @@ class Agent(BaseAgent):
323
330
  return None
324
331
 
325
332
  allowed_functions = [func for func in agent_state_tool_jsons if func["name"] in allowed_tool_names]
326
- allowed_functions = self._runtime_override_tool_json_schema(allowed_functions)
333
+ allowed_functions = runtime_override_tool_json_schema(
334
+ tool_list=allowed_functions, response_format=self.agent_state.response_format, request_heartbeat=True
335
+ )
327
336
 
328
337
  # For the first message, force the initial tool if one is specified
329
338
  force_tool_call = None
@@ -858,6 +867,7 @@ class Agent(BaseAgent):
858
867
  # only pulling latest block data if shared memory is being used
859
868
  current_persisted_memory = Memory(
860
869
  blocks=[self.block_manager.get_block_by_id(block.id, actor=self.user) for block in self.agent_state.memory.get_blocks()],
870
+ file_blocks=self.agent_state.memory.file_blocks,
861
871
  prompt_template=get_prompt_template_for_agent_type(self.agent_state.agent_type),
862
872
  ) # read blocks from DB
863
873
  self.update_memory_if_changed(current_persisted_memory)
@@ -1225,7 +1235,6 @@ class Agent(BaseAgent):
1225
1235
  memory_edit_timestamp=get_utc_time(),
1226
1236
  previous_message_count=self.message_manager.size(actor=self.user, agent_id=self.agent_state.id),
1227
1237
  archival_memory_size=self.agent_manager.passage_size(actor=self.user, agent_id=self.agent_state.id),
1228
- recent_passages=self.agent_manager.list_passages(actor=self.user, agent_id=self.agent_state.id, ascending=False, limit=10),
1229
1238
  )
1230
1239
  num_tokens_external_memory_summary = count_tokens(external_memory_summary)
1231
1240
 
@@ -1601,8 +1610,6 @@ class Agent(BaseAgent):
1601
1610
  if server_name not in self.mcp_clients:
1602
1611
  raise ValueError(f"Unknown MCP server name: {server_name}")
1603
1612
  mcp_client = self.mcp_clients[server_name]
1604
- if not isinstance(mcp_client, BaseMCPClient):
1605
- raise RuntimeError(f"Expected an MCPClient, but got: {type(mcp_client)}")
1606
1613
 
1607
1614
  # Check that tool exists
1608
1615
  available_tools = mcp_client.list_tools()
@@ -40,6 +40,7 @@ class BaseAgent(ABC):
40
40
  self.message_manager = message_manager
41
41
  self.agent_manager = agent_manager
42
42
  self.actor = actor
43
+ self.logger = get_logger(agent_id)
43
44
 
44
45
  @abstractmethod
45
46
  async def step(self, input_messages: List[MessageCreate], max_steps: int = 10) -> LettaResponse:
@@ -0,0 +1,104 @@
1
+ from pathlib import Path
2
+ from typing import AsyncGenerator, Dict, List
3
+
4
+ from openai import AsyncOpenAI
5
+
6
+ from letta.agents.base_agent import BaseAgent
7
+ from letta.orm.errors import NoResultFound
8
+ from letta.schemas.block import Block, BlockUpdate
9
+ from letta.schemas.enums import MessageRole
10
+ from letta.schemas.letta_message_content import TextContent
11
+ from letta.schemas.message import Message, MessageCreate
12
+ from letta.schemas.openai.chat_completion_request import ChatCompletionRequest
13
+ from letta.schemas.user import User
14
+ from letta.services.agent_manager import AgentManager
15
+ from letta.services.block_manager import BlockManager
16
+ from letta.services.message_manager import MessageManager
17
+
18
+
19
+ class EphemeralSummaryAgent(BaseAgent):
20
+ """
21
+ A stateless summarization agent (thin wrapper around OpenAI)
22
+
23
+ # TODO: Extend to more clients
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ target_block_label: str,
29
+ agent_id: str,
30
+ message_manager: MessageManager,
31
+ agent_manager: AgentManager,
32
+ block_manager: BlockManager,
33
+ actor: User,
34
+ ):
35
+ super().__init__(
36
+ agent_id=agent_id,
37
+ openai_client=AsyncOpenAI(),
38
+ message_manager=message_manager,
39
+ agent_manager=agent_manager,
40
+ actor=actor,
41
+ )
42
+ self.target_block_label = target_block_label
43
+ self.block_manager = block_manager
44
+
45
+ async def step(self, input_messages: List[MessageCreate], max_steps: int = 10) -> List[Message]:
46
+ if len(input_messages) > 1:
47
+ raise ValueError("Can only invoke EphemeralSummaryAgent with a single summarization message.")
48
+
49
+ # Check block existence
50
+ try:
51
+ block = await self.agent_manager.get_block_with_label_async(
52
+ agent_id=self.agent_id, block_label=self.target_block_label, actor=self.actor
53
+ )
54
+ except NoResultFound:
55
+ block = await self.block_manager.create_or_update_block_async(
56
+ block=Block(
57
+ value="", label=self.target_block_label, description="Contains recursive summarizations of the conversation so far"
58
+ ),
59
+ actor=self.actor,
60
+ )
61
+ await self.agent_manager.attach_block_async(agent_id=self.agent_id, block_id=block.id, actor=self.actor)
62
+
63
+ if block.value:
64
+ input_message = input_messages[0]
65
+ input_message.content[0].text += f"\n\n--- Previous Summary ---\n{block.value}\n"
66
+
67
+ openai_messages = self.pre_process_input_message(input_messages=input_messages)
68
+ request = self._build_openai_request(openai_messages)
69
+
70
+ # TODO: Extend to generic client
71
+ chat_completion = await self.openai_client.chat.completions.create(**request.model_dump(exclude_unset=True))
72
+ summary = chat_completion.choices[0].message.content.strip()
73
+
74
+ await self.block_manager.update_block_async(block_id=block.id, block_update=BlockUpdate(value=summary), actor=self.actor)
75
+
76
+ print(block)
77
+ print(summary)
78
+
79
+ return [
80
+ Message(
81
+ role=MessageRole.assistant,
82
+ content=[TextContent(text=summary)],
83
+ )
84
+ ]
85
+
86
+ def _build_openai_request(self, openai_messages: List[Dict]) -> ChatCompletionRequest:
87
+ current_dir = Path(__file__).parent
88
+ file_path = current_dir / "prompts" / "summary_system_prompt.txt"
89
+ with open(file_path, "r") as file:
90
+ system = file.read()
91
+
92
+ system_message = [{"role": "system", "content": system}]
93
+
94
+ openai_request = ChatCompletionRequest(
95
+ model="gpt-4o",
96
+ messages=system_message + openai_messages,
97
+ user=self.actor.id,
98
+ max_completion_tokens=4096,
99
+ temperature=0.7,
100
+ )
101
+ return openai_request
102
+
103
+ async def step_stream(self, input_messages: List[MessageCreate], max_steps: int = 10) -> AsyncGenerator[str, None]:
104
+ raise NotImplementedError("EphemeralAgent does not support async step.")
letta/agents/helpers.py CHANGED
@@ -85,9 +85,7 @@ async def _prepare_in_context_messages_async(
85
85
 
86
86
  if agent_state.message_buffer_autoclear:
87
87
  # If autoclear is enabled, only include the most recent system message (usually at index 0)
88
- current_in_context_messages = [
89
- (await message_manager.get_messages_by_ids_async(message_ids=agent_state.message_ids, actor=actor))[0]
90
- ]
88
+ current_in_context_messages = [await message_manager.get_message_by_id_async(message_id=agent_state.message_ids[0], actor=actor)]
91
89
  else:
92
90
  # Otherwise, include the full list of messages by ID for context
93
91
  current_in_context_messages = await message_manager.get_messages_by_ids_async(message_ids=agent_state.message_ids, actor=actor)
@@ -100,6 +98,40 @@ async def _prepare_in_context_messages_async(
100
98
  return current_in_context_messages, new_in_context_messages
101
99
 
102
100
 
101
+ async def _prepare_in_context_messages_no_persist_async(
102
+ input_messages: List[MessageCreate],
103
+ agent_state: AgentState,
104
+ message_manager: MessageManager,
105
+ actor: User,
106
+ ) -> Tuple[List[Message], List[Message]]:
107
+ """
108
+ Prepares in-context messages for an agent, based on the current state and a new user input.
109
+
110
+ Args:
111
+ input_messages (List[MessageCreate]): The new user input messages to process.
112
+ agent_state (AgentState): The current state of the agent, including message buffer config.
113
+ message_manager (MessageManager): The manager used to retrieve and create messages.
114
+ actor (User): The user performing the action, used for access control and attribution.
115
+
116
+ Returns:
117
+ Tuple[List[Message], List[Message]]: A tuple containing:
118
+ - The current in-context messages (existing context for the agent).
119
+ - The new in-context messages (messages created from the new input).
120
+ """
121
+
122
+ if agent_state.message_buffer_autoclear:
123
+ # If autoclear is enabled, only include the most recent system message (usually at index 0)
124
+ current_in_context_messages = [await message_manager.get_message_by_id_async(message_id=agent_state.message_ids[0], actor=actor)]
125
+ else:
126
+ # Otherwise, include the full list of messages by ID for context
127
+ current_in_context_messages = await message_manager.get_messages_by_ids_async(message_ids=agent_state.message_ids, actor=actor)
128
+
129
+ # Create a new user message from the input but dont store it yet
130
+ new_in_context_messages = create_input_messages(input_messages=input_messages, agent_id=agent_state.id, actor=actor)
131
+
132
+ return current_in_context_messages, new_in_context_messages
133
+
134
+
103
135
  def serialize_message_history(messages: List[str], context: str) -> str:
104
136
  """
105
137
  Produce an XML document like: