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.
- letta/__init__.py +7 -1
- letta/agent.py +14 -7
- letta/agents/base_agent.py +1 -0
- letta/agents/ephemeral_summary_agent.py +104 -0
- letta/agents/helpers.py +35 -3
- letta/agents/letta_agent.py +492 -176
- letta/agents/letta_agent_batch.py +22 -16
- letta/agents/prompts/summary_system_prompt.txt +62 -0
- letta/agents/voice_agent.py +22 -7
- letta/agents/voice_sleeptime_agent.py +13 -8
- letta/constants.py +33 -1
- letta/data_sources/connectors.py +52 -36
- letta/errors.py +4 -0
- letta/functions/ast_parsers.py +13 -30
- letta/functions/function_sets/base.py +3 -1
- letta/functions/functions.py +2 -0
- letta/functions/mcp_client/base_client.py +151 -97
- letta/functions/mcp_client/sse_client.py +49 -31
- letta/functions/mcp_client/stdio_client.py +107 -106
- letta/functions/schema_generator.py +22 -22
- letta/groups/helpers.py +3 -4
- letta/groups/sleeptime_multi_agent.py +4 -4
- letta/groups/sleeptime_multi_agent_v2.py +22 -0
- letta/helpers/composio_helpers.py +16 -0
- letta/helpers/converters.py +20 -0
- letta/helpers/datetime_helpers.py +1 -6
- letta/helpers/tool_rule_solver.py +2 -1
- letta/interfaces/anthropic_streaming_interface.py +17 -2
- letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
- letta/interfaces/openai_streaming_interface.py +18 -2
- letta/llm_api/anthropic_client.py +24 -3
- letta/llm_api/google_ai_client.py +0 -15
- letta/llm_api/google_vertex_client.py +6 -5
- letta/llm_api/llm_client_base.py +15 -0
- letta/llm_api/openai.py +2 -2
- letta/llm_api/openai_client.py +60 -8
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +45 -43
- letta/orm/base.py +0 -2
- letta/orm/block.py +1 -0
- letta/orm/custom_columns.py +13 -0
- letta/orm/enums.py +5 -0
- letta/orm/file.py +3 -1
- letta/orm/files_agents.py +68 -0
- letta/orm/mcp_server.py +48 -0
- letta/orm/message.py +1 -0
- letta/orm/organization.py +11 -2
- letta/orm/passage.py +25 -10
- letta/orm/sandbox_config.py +5 -2
- letta/orm/sqlalchemy_base.py +171 -110
- letta/prompts/system/memgpt_base.txt +6 -1
- letta/prompts/system/memgpt_v2_chat.txt +57 -0
- letta/prompts/system/sleeptime.txt +2 -0
- letta/prompts/system/sleeptime_v2.txt +28 -0
- letta/schemas/agent.py +87 -20
- letta/schemas/block.py +7 -1
- letta/schemas/file.py +57 -0
- letta/schemas/mcp.py +74 -0
- letta/schemas/memory.py +5 -2
- letta/schemas/message.py +9 -0
- letta/schemas/openai/openai.py +0 -6
- letta/schemas/providers.py +33 -4
- letta/schemas/tool.py +26 -21
- letta/schemas/tool_execution_result.py +5 -0
- letta/server/db.py +23 -8
- letta/server/rest_api/app.py +73 -56
- letta/server/rest_api/interface.py +4 -4
- letta/server/rest_api/routers/v1/agents.py +132 -47
- letta/server/rest_api/routers/v1/blocks.py +3 -2
- letta/server/rest_api/routers/v1/embeddings.py +3 -3
- letta/server/rest_api/routers/v1/groups.py +3 -3
- letta/server/rest_api/routers/v1/jobs.py +14 -17
- letta/server/rest_api/routers/v1/organizations.py +10 -10
- letta/server/rest_api/routers/v1/providers.py +12 -10
- letta/server/rest_api/routers/v1/runs.py +3 -3
- letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
- letta/server/rest_api/routers/v1/sources.py +108 -43
- letta/server/rest_api/routers/v1/steps.py +8 -6
- letta/server/rest_api/routers/v1/tools.py +134 -95
- letta/server/rest_api/utils.py +12 -1
- letta/server/server.py +272 -73
- letta/services/agent_manager.py +246 -313
- letta/services/block_manager.py +30 -9
- letta/services/context_window_calculator/__init__.py +0 -0
- letta/services/context_window_calculator/context_window_calculator.py +150 -0
- letta/services/context_window_calculator/token_counter.py +82 -0
- letta/services/file_processor/__init__.py +0 -0
- letta/services/file_processor/chunker/__init__.py +0 -0
- letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
- letta/services/file_processor/embedder/__init__.py +0 -0
- letta/services/file_processor/embedder/openai_embedder.py +84 -0
- letta/services/file_processor/file_processor.py +123 -0
- letta/services/file_processor/parser/__init__.py +0 -0
- letta/services/file_processor/parser/base_parser.py +9 -0
- letta/services/file_processor/parser/mistral_parser.py +54 -0
- letta/services/file_processor/types.py +0 -0
- letta/services/files_agents_manager.py +184 -0
- letta/services/group_manager.py +118 -0
- letta/services/helpers/agent_manager_helper.py +76 -21
- letta/services/helpers/tool_execution_helper.py +3 -0
- letta/services/helpers/tool_parser_helper.py +100 -0
- letta/services/identity_manager.py +44 -42
- letta/services/job_manager.py +21 -10
- letta/services/mcp/base_client.py +5 -2
- letta/services/mcp/sse_client.py +3 -5
- letta/services/mcp/stdio_client.py +3 -5
- letta/services/mcp_manager.py +281 -0
- letta/services/message_manager.py +40 -26
- letta/services/organization_manager.py +55 -19
- letta/services/passage_manager.py +211 -13
- letta/services/provider_manager.py +48 -2
- letta/services/sandbox_config_manager.py +105 -0
- letta/services/source_manager.py +4 -5
- letta/services/step_manager.py +9 -6
- letta/services/summarizer/summarizer.py +50 -23
- letta/services/telemetry_manager.py +7 -0
- letta/services/tool_executor/tool_execution_manager.py +11 -52
- letta/services/tool_executor/tool_execution_sandbox.py +4 -34
- letta/services/tool_executor/tool_executor.py +107 -105
- letta/services/tool_manager.py +56 -17
- letta/services/tool_sandbox/base.py +39 -92
- letta/services/tool_sandbox/e2b_sandbox.py +16 -11
- letta/services/tool_sandbox/local_sandbox.py +51 -23
- letta/services/user_manager.py +36 -3
- letta/settings.py +10 -3
- letta/templates/__init__.py +0 -0
- letta/templates/sandbox_code_file.py.j2 +47 -0
- letta/templates/template_helper.py +16 -0
- letta/tracing.py +30 -1
- letta/types/__init__.py +7 -0
- letta/utils.py +25 -1
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/METADATA +7 -2
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/RECORD +136 -110
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/WHEEL +0 -0
- {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
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,
|
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
|
-
|
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 =
|
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()
|
letta/agents/base_agent.py
CHANGED
@@ -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:
|