letta-nightly 0.12.1.dev20251024104217__py3-none-any.whl → 0.13.0.dev20251024223017__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.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/__init__.py +2 -3
- letta/adapters/letta_llm_adapter.py +1 -0
- letta/adapters/simple_llm_request_adapter.py +8 -5
- letta/adapters/simple_llm_stream_adapter.py +22 -6
- letta/agents/agent_loop.py +10 -3
- letta/agents/base_agent.py +4 -1
- letta/agents/helpers.py +41 -9
- letta/agents/letta_agent.py +11 -10
- letta/agents/letta_agent_v2.py +47 -37
- letta/agents/letta_agent_v3.py +395 -300
- letta/agents/voice_agent.py +8 -6
- letta/agents/voice_sleeptime_agent.py +3 -3
- letta/constants.py +30 -7
- letta/errors.py +20 -0
- letta/functions/function_sets/base.py +55 -3
- letta/functions/mcp_client/types.py +33 -57
- letta/functions/schema_generator.py +135 -23
- letta/groups/sleeptime_multi_agent_v3.py +6 -11
- letta/groups/sleeptime_multi_agent_v4.py +227 -0
- letta/helpers/converters.py +78 -4
- letta/helpers/crypto_utils.py +6 -2
- letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
- letta/interfaces/anthropic_streaming_interface.py +3 -4
- letta/interfaces/gemini_streaming_interface.py +4 -6
- letta/interfaces/openai_streaming_interface.py +63 -28
- letta/llm_api/anthropic_client.py +7 -4
- letta/llm_api/deepseek_client.py +6 -4
- letta/llm_api/google_ai_client.py +3 -12
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/helpers.py +90 -61
- letta/llm_api/llm_api_tools.py +4 -1
- letta/llm_api/openai.py +12 -12
- letta/llm_api/openai_client.py +53 -16
- letta/local_llm/constants.py +4 -3
- letta/local_llm/json_parser.py +5 -2
- letta/local_llm/utils.py +2 -3
- letta/log.py +171 -7
- letta/orm/agent.py +43 -9
- letta/orm/archive.py +4 -0
- letta/orm/custom_columns.py +15 -0
- letta/orm/identity.py +11 -11
- letta/orm/mcp_server.py +9 -0
- letta/orm/message.py +6 -1
- letta/orm/run_metrics.py +7 -2
- letta/orm/sqlalchemy_base.py +2 -2
- letta/orm/tool.py +3 -0
- letta/otel/tracing.py +2 -0
- letta/prompts/prompt_generator.py +7 -2
- letta/schemas/agent.py +41 -10
- letta/schemas/agent_file.py +3 -0
- letta/schemas/archive.py +4 -2
- letta/schemas/block.py +2 -1
- letta/schemas/enums.py +36 -3
- letta/schemas/file.py +3 -3
- letta/schemas/folder.py +2 -1
- letta/schemas/group.py +2 -1
- letta/schemas/identity.py +18 -9
- letta/schemas/job.py +3 -1
- letta/schemas/letta_message.py +71 -12
- letta/schemas/letta_request.py +7 -3
- letta/schemas/letta_stop_reason.py +0 -25
- letta/schemas/llm_config.py +8 -2
- letta/schemas/mcp.py +80 -83
- letta/schemas/mcp_server.py +349 -0
- letta/schemas/memory.py +20 -8
- letta/schemas/message.py +212 -67
- letta/schemas/providers/anthropic.py +13 -6
- letta/schemas/providers/azure.py +6 -4
- letta/schemas/providers/base.py +8 -4
- letta/schemas/providers/bedrock.py +6 -2
- letta/schemas/providers/cerebras.py +7 -3
- letta/schemas/providers/deepseek.py +2 -1
- letta/schemas/providers/google_gemini.py +15 -6
- letta/schemas/providers/groq.py +2 -1
- letta/schemas/providers/lmstudio.py +9 -6
- letta/schemas/providers/mistral.py +2 -1
- letta/schemas/providers/openai.py +7 -2
- letta/schemas/providers/together.py +9 -3
- letta/schemas/providers/xai.py +7 -3
- letta/schemas/run.py +7 -2
- letta/schemas/run_metrics.py +2 -1
- letta/schemas/sandbox_config.py +2 -2
- letta/schemas/secret.py +3 -158
- letta/schemas/source.py +2 -2
- letta/schemas/step.py +2 -2
- letta/schemas/tool.py +24 -1
- letta/schemas/usage.py +0 -1
- letta/server/rest_api/app.py +123 -7
- letta/server/rest_api/dependencies.py +3 -0
- letta/server/rest_api/interface.py +7 -4
- letta/server/rest_api/redis_stream_manager.py +16 -1
- letta/server/rest_api/routers/v1/__init__.py +7 -0
- letta/server/rest_api/routers/v1/agents.py +332 -322
- letta/server/rest_api/routers/v1/archives.py +127 -40
- letta/server/rest_api/routers/v1/blocks.py +54 -6
- letta/server/rest_api/routers/v1/chat_completions.py +146 -0
- letta/server/rest_api/routers/v1/folders.py +27 -35
- letta/server/rest_api/routers/v1/groups.py +23 -35
- letta/server/rest_api/routers/v1/identities.py +24 -10
- letta/server/rest_api/routers/v1/internal_runs.py +107 -0
- letta/server/rest_api/routers/v1/internal_templates.py +162 -179
- letta/server/rest_api/routers/v1/jobs.py +15 -27
- letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
- letta/server/rest_api/routers/v1/messages.py +23 -34
- letta/server/rest_api/routers/v1/organizations.py +6 -27
- letta/server/rest_api/routers/v1/providers.py +35 -62
- letta/server/rest_api/routers/v1/runs.py +30 -43
- letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
- letta/server/rest_api/routers/v1/sources.py +26 -42
- letta/server/rest_api/routers/v1/steps.py +16 -29
- letta/server/rest_api/routers/v1/tools.py +17 -13
- letta/server/rest_api/routers/v1/users.py +5 -17
- letta/server/rest_api/routers/v1/voice.py +18 -27
- letta/server/rest_api/streaming_response.py +5 -2
- letta/server/rest_api/utils.py +187 -25
- letta/server/server.py +27 -22
- letta/server/ws_api/server.py +5 -4
- letta/services/agent_manager.py +148 -26
- letta/services/agent_serialization_manager.py +6 -1
- letta/services/archive_manager.py +168 -15
- letta/services/block_manager.py +14 -4
- letta/services/file_manager.py +33 -29
- letta/services/group_manager.py +10 -0
- letta/services/helpers/agent_manager_helper.py +65 -11
- letta/services/identity_manager.py +105 -4
- letta/services/job_manager.py +11 -1
- letta/services/mcp/base_client.py +2 -2
- letta/services/mcp/oauth_utils.py +33 -8
- letta/services/mcp_manager.py +174 -78
- letta/services/mcp_server_manager.py +1331 -0
- letta/services/message_manager.py +109 -4
- letta/services/organization_manager.py +4 -4
- letta/services/passage_manager.py +9 -25
- letta/services/provider_manager.py +91 -15
- letta/services/run_manager.py +72 -15
- letta/services/sandbox_config_manager.py +45 -3
- letta/services/source_manager.py +15 -8
- letta/services/step_manager.py +24 -1
- letta/services/streaming_service.py +581 -0
- letta/services/summarizer/summarizer.py +1 -1
- letta/services/tool_executor/core_tool_executor.py +111 -0
- letta/services/tool_executor/files_tool_executor.py +5 -3
- letta/services/tool_executor/sandbox_tool_executor.py +2 -2
- letta/services/tool_executor/tool_execution_manager.py +1 -1
- letta/services/tool_manager.py +10 -3
- letta/services/tool_sandbox/base.py +61 -1
- letta/services/tool_sandbox/local_sandbox.py +1 -3
- letta/services/user_manager.py +2 -2
- letta/settings.py +49 -5
- letta/system.py +14 -5
- letta/utils.py +73 -1
- letta/validators.py +105 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/METADATA +4 -2
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/RECORD +157 -151
- letta/schemas/letta_ping.py +0 -28
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/WHEEL +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251024223017.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.
|
|
8
|
+
__version__ = "0.13.0"
|
|
9
9
|
|
|
10
10
|
if os.environ.get("LETTA_VERSION"):
|
|
11
11
|
__version__ = os.environ["LETTA_VERSION"]
|
|
@@ -28,8 +28,7 @@ from letta.schemas.embedding_config import EmbeddingConfig
|
|
|
28
28
|
from letta.schemas.enums import JobStatus
|
|
29
29
|
from letta.schemas.file import FileMetadata
|
|
30
30
|
from letta.schemas.job import Job
|
|
31
|
-
from letta.schemas.letta_message import LettaMessage
|
|
32
|
-
from letta.schemas.letta_ping import LettaPing
|
|
31
|
+
from letta.schemas.letta_message import LettaMessage, LettaPing
|
|
33
32
|
from letta.schemas.letta_stop_reason import LettaStopReason
|
|
34
33
|
from letta.schemas.llm_config import LLMConfig
|
|
35
34
|
from letta.schemas.memory import ArchivalMemorySummary, BasicBlockMemory, ChatMemory, Memory, RecallMemorySummary
|
|
@@ -30,6 +30,7 @@ class LettaLLMAdapter(ABC):
|
|
|
30
30
|
self.reasoning_content: list[TextContent | ReasoningContent | RedactedReasoningContent] | None = None
|
|
31
31
|
self.content: list[TextContent | ReasoningContent | RedactedReasoningContent] | None = None
|
|
32
32
|
self.tool_call: ToolCall | None = None
|
|
33
|
+
self.tool_calls: list[ToolCall] = []
|
|
33
34
|
self.usage: LettaUsageStatistics = LettaUsageStatistics()
|
|
34
35
|
self.telemetry_manager: TelemetryManager = TelemetryManager()
|
|
35
36
|
self.llm_request_finish_timestamp_ns: int | None = None
|
|
@@ -38,7 +38,11 @@ class SimpleLLMRequestAdapter(LettaLLMRequestAdapter):
|
|
|
38
38
|
self.request_data = request_data
|
|
39
39
|
|
|
40
40
|
# Make the blocking LLM request
|
|
41
|
-
|
|
41
|
+
try:
|
|
42
|
+
self.response_data = await self.llm_client.request_async(request_data, self.llm_config)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
raise self.llm_client.handle_llm_error(e)
|
|
45
|
+
|
|
42
46
|
self.llm_request_finish_timestamp_ns = get_utc_timestamp_ns()
|
|
43
47
|
|
|
44
48
|
# Convert response to chat completion format
|
|
@@ -71,10 +75,9 @@ class SimpleLLMRequestAdapter(LettaLLMRequestAdapter):
|
|
|
71
75
|
self.content = self.reasoning_content + (self.content or [])
|
|
72
76
|
|
|
73
77
|
# Extract tool call
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
else
|
|
77
|
-
self.tool_call = None
|
|
78
|
+
tool_calls = self.chat_completions_response.choices[0].message.tool_calls or []
|
|
79
|
+
self.tool_calls = list(tool_calls)
|
|
80
|
+
self.tool_call = self.tool_calls[0] if self.tool_calls else None
|
|
78
81
|
|
|
79
82
|
# Extract usage statistics
|
|
80
83
|
self.usage.step_count = 1
|
|
@@ -25,6 +25,24 @@ class SimpleLLMStreamAdapter(LettaLLMStreamAdapter):
|
|
|
25
25
|
specific streaming formats.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
def _extract_tool_calls(self) -> list:
|
|
29
|
+
"""extract tool calls from interface, trying parallel API first then single API"""
|
|
30
|
+
# try multi-call api if available
|
|
31
|
+
if hasattr(self.interface, "get_tool_call_objects"):
|
|
32
|
+
try:
|
|
33
|
+
calls = self.interface.get_tool_call_objects()
|
|
34
|
+
if calls:
|
|
35
|
+
return calls
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
# fallback to single-call api
|
|
40
|
+
try:
|
|
41
|
+
single = self.interface.get_tool_call_object()
|
|
42
|
+
return [single] if single else []
|
|
43
|
+
except Exception:
|
|
44
|
+
return []
|
|
45
|
+
|
|
28
46
|
async def invoke_llm(
|
|
29
47
|
self,
|
|
30
48
|
request_data: dict,
|
|
@@ -102,12 +120,10 @@ class SimpleLLMStreamAdapter(LettaLLMStreamAdapter):
|
|
|
102
120
|
# After streaming completes, extract the accumulated data
|
|
103
121
|
self.llm_request_finish_timestamp_ns = get_utc_timestamp_ns()
|
|
104
122
|
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# No tool call, handle upstream
|
|
110
|
-
self.tool_call = None
|
|
123
|
+
# extract tool calls from interface (supports both single and parallel calls)
|
|
124
|
+
self.tool_calls = self._extract_tool_calls()
|
|
125
|
+
# preserve legacy single-call field for existing consumers
|
|
126
|
+
self.tool_call = self.tool_calls[-1] if self.tool_calls else None
|
|
111
127
|
|
|
112
128
|
# Extract reasoning content from the interface
|
|
113
129
|
# TODO this should probably just be called "content"?
|
letta/agents/agent_loop.py
CHANGED
|
@@ -4,6 +4,7 @@ from letta.agents.base_agent_v2 import BaseAgentV2
|
|
|
4
4
|
from letta.agents.letta_agent_v2 import LettaAgentV2
|
|
5
5
|
from letta.agents.letta_agent_v3 import LettaAgentV3
|
|
6
6
|
from letta.groups.sleeptime_multi_agent_v3 import SleeptimeMultiAgentV3
|
|
7
|
+
from letta.groups.sleeptime_multi_agent_v4 import SleeptimeMultiAgentV4
|
|
7
8
|
from letta.schemas.agent import AgentState
|
|
8
9
|
from letta.schemas.enums import AgentType
|
|
9
10
|
|
|
@@ -16,13 +17,19 @@ class AgentLoop:
|
|
|
16
17
|
|
|
17
18
|
@staticmethod
|
|
18
19
|
def load(agent_state: AgentState, actor: "User") -> BaseAgentV2:
|
|
19
|
-
if agent_state.
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if agent_state.agent_type == AgentType.letta_v1_agent:
|
|
21
|
+
if agent_state.enable_sleeptime:
|
|
22
|
+
return SleeptimeMultiAgentV4(
|
|
23
|
+
agent_state=agent_state,
|
|
24
|
+
actor=actor,
|
|
25
|
+
group=agent_state.multi_agent_group,
|
|
26
|
+
)
|
|
22
27
|
return LettaAgentV3(
|
|
23
28
|
agent_state=agent_state,
|
|
24
29
|
actor=actor,
|
|
25
30
|
)
|
|
31
|
+
elif agent_state.enable_sleeptime and agent_state.agent_type != AgentType.voice_convo_agent:
|
|
32
|
+
return SleeptimeMultiAgentV3(agent_state=agent_state, actor=actor, group=agent_state.multi_agent_group)
|
|
26
33
|
else:
|
|
27
34
|
return LettaAgentV2(
|
|
28
35
|
agent_state=agent_state,
|
letta/agents/base_agent.py
CHANGED
|
@@ -140,7 +140,10 @@ class BaseAgent(ABC):
|
|
|
140
140
|
|
|
141
141
|
# generate just the memory string with current state for comparison
|
|
142
142
|
curr_memory_str = agent_state.memory.compile(
|
|
143
|
-
tool_usage_rules=tool_constraint_block,
|
|
143
|
+
tool_usage_rules=tool_constraint_block,
|
|
144
|
+
sources=agent_state.sources,
|
|
145
|
+
max_files_open=agent_state.max_files_open,
|
|
146
|
+
llm_config=agent_state.llm_config,
|
|
144
147
|
)
|
|
145
148
|
new_dynamic_section = extract_dynamic_section(curr_memory_str)
|
|
146
149
|
|
letta/agents/helpers.py
CHANGED
|
@@ -13,7 +13,7 @@ from letta.schemas.letta_message import MessageType
|
|
|
13
13
|
from letta.schemas.letta_message_content import TextContent
|
|
14
14
|
from letta.schemas.letta_response import LettaResponse
|
|
15
15
|
from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
16
|
-
from letta.schemas.message import Message, MessageCreate, MessageCreateBase
|
|
16
|
+
from letta.schemas.message import ApprovalCreate, Message, MessageCreate, MessageCreateBase
|
|
17
17
|
from letta.schemas.tool_execution_result import ToolExecutionResult
|
|
18
18
|
from letta.schemas.usage import LettaUsageStatistics
|
|
19
19
|
from letta.schemas.user import User
|
|
@@ -135,6 +135,24 @@ async def _prepare_in_context_messages_async(
|
|
|
135
135
|
return current_in_context_messages, new_in_context_messages
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
def validate_approval_tool_call_ids(approval_request_message: Message, approval_response_message: ApprovalCreate):
|
|
139
|
+
approval_requests = approval_request_message.tool_calls
|
|
140
|
+
approval_request_tool_call_ids = [approval_request.id for approval_request in approval_requests]
|
|
141
|
+
|
|
142
|
+
approval_responses = approval_response_message.approvals
|
|
143
|
+
approval_response_tool_call_ids = [approval_response.tool_call_id for approval_response in approval_responses]
|
|
144
|
+
|
|
145
|
+
request_response_diff = set(approval_request_tool_call_ids).symmetric_difference(set(approval_response_tool_call_ids))
|
|
146
|
+
if request_response_diff:
|
|
147
|
+
if len(approval_request_tool_call_ids) == 1 and approval_response_tool_call_ids[0] == approval_request_message.id:
|
|
148
|
+
# legacy case where we used to use message id instead of tool call id
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"Invalid tool call IDs. Expected '{approval_request_tool_call_ids}', but received '{approval_response_tool_call_ids}'."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
138
156
|
async def _prepare_in_context_messages_no_persist_async(
|
|
139
157
|
input_messages: List[MessageCreateBase],
|
|
140
158
|
agent_state: AgentState,
|
|
@@ -168,20 +186,18 @@ async def _prepare_in_context_messages_no_persist_async(
|
|
|
168
186
|
# Check for approval-related message validation
|
|
169
187
|
if len(input_messages) == 1 and input_messages[0].type == "approval":
|
|
170
188
|
# User is trying to send an approval response
|
|
171
|
-
if current_in_context_messages[-1].role != "approval":
|
|
189
|
+
if current_in_context_messages and current_in_context_messages[-1].role != "approval":
|
|
172
190
|
raise ValueError(
|
|
173
191
|
"Cannot process approval response: No tool call is currently awaiting approval. "
|
|
174
192
|
"Please send a regular message to interact with the agent."
|
|
175
193
|
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)
|
|
181
|
-
new_in_context_messages = create_approval_response_message_from_input(agent_state=agent_state, input_message=input_messages[0])
|
|
194
|
+
validate_approval_tool_call_ids(current_in_context_messages[-1], input_messages[0])
|
|
195
|
+
new_in_context_messages = create_approval_response_message_from_input(
|
|
196
|
+
agent_state=agent_state, input_message=input_messages[0], run_id=run_id
|
|
197
|
+
)
|
|
182
198
|
else:
|
|
183
199
|
# User is trying to send a regular message
|
|
184
|
-
if current_in_context_messages[-1].role == "approval":
|
|
200
|
+
if current_in_context_messages and current_in_context_messages[-1].role == "approval":
|
|
185
201
|
raise PendingApprovalError(pending_request_id=current_in_context_messages[-1].id)
|
|
186
202
|
|
|
187
203
|
# Create a new user message from the input but dont store it yet
|
|
@@ -400,3 +416,19 @@ def _maybe_get_approval_messages(messages: list[Message]) -> Tuple[Message | Non
|
|
|
400
416
|
if maybe_approval_request.role == "approval" and maybe_approval_response.role == "approval":
|
|
401
417
|
return maybe_approval_request, maybe_approval_response
|
|
402
418
|
return None, None
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _maybe_get_pending_tool_call_message(messages: list[Message]) -> Message | None:
|
|
422
|
+
"""
|
|
423
|
+
Only used in the case where hitl is invoked with parallel tool calling,
|
|
424
|
+
where agent calls some tools that require approval, and others that don't.
|
|
425
|
+
"""
|
|
426
|
+
if len(messages) >= 3:
|
|
427
|
+
maybe_tool_call_message = messages[-3]
|
|
428
|
+
if (
|
|
429
|
+
maybe_tool_call_message.role == "assistant"
|
|
430
|
+
and maybe_tool_call_message.tool_calls is not None
|
|
431
|
+
and len(maybe_tool_call_message.tool_calls) > 0
|
|
432
|
+
):
|
|
433
|
+
return maybe_tool_call_message
|
|
434
|
+
return None
|
letta/agents/letta_agent.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import uuid
|
|
2
3
|
from collections.abc import AsyncGenerator
|
|
3
4
|
from datetime import datetime
|
|
@@ -18,7 +19,7 @@ from letta.agents.helpers import (
|
|
|
18
19
|
_safe_load_tool_call_str,
|
|
19
20
|
generate_step_id,
|
|
20
21
|
)
|
|
21
|
-
from letta.constants import DEFAULT_MAX_STEPS, NON_USER_MSG_PREFIX
|
|
22
|
+
from letta.constants import DEFAULT_MAX_STEPS, NON_USER_MSG_PREFIX, REQUEST_HEARTBEAT_PARAM
|
|
22
23
|
from letta.errors import ContextWindowExceededError
|
|
23
24
|
from letta.helpers import ToolRulesSolver
|
|
24
25
|
from letta.helpers.datetime_helpers import AsyncTimer, get_utc_time, get_utc_timestamp_ns, ns_to_ms
|
|
@@ -41,7 +42,7 @@ from letta.schemas.letta_response import LettaResponse
|
|
|
41
42
|
from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
42
43
|
from letta.schemas.llm_config import LLMConfig
|
|
43
44
|
from letta.schemas.message import Message, MessageCreateBase
|
|
44
|
-
from letta.schemas.openai.chat_completion_response import ToolCall, UsageStatistics
|
|
45
|
+
from letta.schemas.openai.chat_completion_response import FunctionCall, ToolCall, UsageStatistics
|
|
45
46
|
from letta.schemas.provider_trace import ProviderTraceCreate
|
|
46
47
|
from letta.schemas.step import StepProgression
|
|
47
48
|
from letta.schemas.step_metrics import StepMetrics
|
|
@@ -1698,19 +1699,18 @@ class LettaAgent(BaseAgent):
|
|
|
1698
1699
|
request_heartbeat=request_heartbeat,
|
|
1699
1700
|
)
|
|
1700
1701
|
if not is_approval and tool_rules_solver.is_requires_approval_tool(tool_call_name):
|
|
1701
|
-
|
|
1702
|
+
tool_args[REQUEST_HEARTBEAT_PARAM] = request_heartbeat
|
|
1703
|
+
approval_messages = create_approval_request_message_from_llm_response(
|
|
1702
1704
|
agent_id=agent_state.id,
|
|
1703
1705
|
model=agent_state.llm_config.model,
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
actor=self.actor,
|
|
1708
|
-
continue_stepping=request_heartbeat,
|
|
1706
|
+
requested_tool_calls=[
|
|
1707
|
+
ToolCall(id=tool_call_id, function=FunctionCall(name=tool_call_name, arguments=json.dumps(tool_args)))
|
|
1708
|
+
],
|
|
1709
1709
|
reasoning_content=reasoning_content,
|
|
1710
1710
|
pre_computed_assistant_message_id=pre_computed_assistant_message_id,
|
|
1711
1711
|
step_id=step_id,
|
|
1712
1712
|
)
|
|
1713
|
-
messages_to_persist = (initial_messages or []) +
|
|
1713
|
+
messages_to_persist = (initial_messages or []) + approval_messages
|
|
1714
1714
|
continue_stepping = False
|
|
1715
1715
|
stop_reason = LettaStopReason(stop_reason=StopReasonType.requires_approval.value)
|
|
1716
1716
|
else:
|
|
@@ -1868,7 +1868,8 @@ class LettaAgent(BaseAgent):
|
|
|
1868
1868
|
start_time = get_utc_timestamp_ns()
|
|
1869
1869
|
agent_step_span.add_event(name="tool_execution_started")
|
|
1870
1870
|
|
|
1871
|
-
|
|
1871
|
+
# Decrypt environment variable values
|
|
1872
|
+
sandbox_env_vars = {var.key: var.get_value_secret().get_plaintext() for var in agent_state.secrets}
|
|
1872
1873
|
tool_execution_manager = ToolExecutionManager(
|
|
1873
1874
|
agent_state=agent_state,
|
|
1874
1875
|
message_manager=self.message_manager,
|
letta/agents/letta_agent_v2.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import json
|
|
2
2
|
import uuid
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import AsyncGenerator, Optional, Tuple
|
|
@@ -19,7 +19,7 @@ from letta.agents.helpers import (
|
|
|
19
19
|
_safe_load_tool_call_str,
|
|
20
20
|
generate_step_id,
|
|
21
21
|
)
|
|
22
|
-
from letta.constants import DEFAULT_MAX_STEPS, NON_USER_MSG_PREFIX
|
|
22
|
+
from letta.constants import DEFAULT_MAX_STEPS, NON_USER_MSG_PREFIX, REQUEST_HEARTBEAT_PARAM
|
|
23
23
|
from letta.errors import ContextWindowExceededError, LLMError
|
|
24
24
|
from letta.helpers import ToolRulesSolver
|
|
25
25
|
from letta.helpers.datetime_helpers import get_utc_time, get_utc_timestamp_ns, ns_to_ms
|
|
@@ -37,9 +37,10 @@ from letta.schemas.letta_message_content import OmittedReasoningContent, Reasoni
|
|
|
37
37
|
from letta.schemas.letta_response import LettaResponse
|
|
38
38
|
from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
39
39
|
from letta.schemas.message import Message, MessageCreate, MessageUpdate
|
|
40
|
-
from letta.schemas.openai.chat_completion_response import ToolCall, UsageStatistics
|
|
40
|
+
from letta.schemas.openai.chat_completion_response import FunctionCall, ToolCall, UsageStatistics
|
|
41
41
|
from letta.schemas.step import Step, StepProgression
|
|
42
42
|
from letta.schemas.step_metrics import StepMetrics
|
|
43
|
+
from letta.schemas.tool import Tool
|
|
43
44
|
from letta.schemas.tool_execution_result import ToolExecutionResult
|
|
44
45
|
from letta.schemas.usage import LettaUsageStatistics
|
|
45
46
|
from letta.schemas.user import User
|
|
@@ -470,7 +471,7 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
470
471
|
# Handle the AI response with the extracted data
|
|
471
472
|
if tool_call is None and llm_adapter.tool_call is None:
|
|
472
473
|
self.stop_reason = LettaStopReason(stop_reason=StopReasonType.no_tool_call.value)
|
|
473
|
-
raise
|
|
474
|
+
raise LLMError("No tool calls found in response, model must make a tool call")
|
|
474
475
|
|
|
475
476
|
# TODO: how should be associate input messages with runs?
|
|
476
477
|
## Set run_id on input messages before persisting
|
|
@@ -535,7 +536,7 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
535
536
|
)
|
|
536
537
|
step_progression, step_metrics = await self._step_checkpoint_finish(step_metrics, agent_step_span, logged_step)
|
|
537
538
|
except Exception as e:
|
|
538
|
-
self.logger.
|
|
539
|
+
self.logger.warning(f"Error during step processing: {e}")
|
|
539
540
|
self.job_update_metadata = {"error": str(e)}
|
|
540
541
|
|
|
541
542
|
# This indicates we failed after we decided to stop stepping, which indicates a bug with our flow.
|
|
@@ -699,7 +700,10 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
699
700
|
|
|
700
701
|
# generate just the memory string with current state for comparison
|
|
701
702
|
curr_memory_str = agent_state.memory.compile(
|
|
702
|
-
tool_usage_rules=tool_constraint_block,
|
|
703
|
+
tool_usage_rules=tool_constraint_block,
|
|
704
|
+
sources=agent_state.sources,
|
|
705
|
+
max_files_open=agent_state.max_files_open,
|
|
706
|
+
llm_config=agent_state.llm_config,
|
|
703
707
|
)
|
|
704
708
|
new_dynamic_section = extract_dynamic_section(curr_memory_str)
|
|
705
709
|
|
|
@@ -933,20 +937,19 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
933
937
|
)
|
|
934
938
|
|
|
935
939
|
if not is_approval and tool_rules_solver.is_requires_approval_tool(tool_call_name):
|
|
936
|
-
|
|
940
|
+
tool_args[REQUEST_HEARTBEAT_PARAM] = request_heartbeat
|
|
941
|
+
approval_messages = create_approval_request_message_from_llm_response(
|
|
937
942
|
agent_id=agent_state.id,
|
|
938
943
|
model=agent_state.llm_config.model,
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
actor=self.actor,
|
|
943
|
-
continue_stepping=request_heartbeat,
|
|
944
|
+
requested_tool_calls=[
|
|
945
|
+
ToolCall(id=tool_call_id, function=FunctionCall(name=tool_call_name, arguments=json.dumps(tool_args)))
|
|
946
|
+
],
|
|
944
947
|
reasoning_content=reasoning_content,
|
|
945
948
|
pre_computed_assistant_message_id=pre_computed_assistant_message_id,
|
|
946
949
|
step_id=step_id,
|
|
947
950
|
run_id=run_id,
|
|
948
951
|
)
|
|
949
|
-
messages_to_persist = (initial_messages or []) +
|
|
952
|
+
messages_to_persist = (initial_messages or []) + approval_messages
|
|
950
953
|
continue_stepping = False
|
|
951
954
|
stop_reason = LettaStopReason(stop_reason=StopReasonType.requires_approval.value)
|
|
952
955
|
else:
|
|
@@ -957,8 +960,10 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
957
960
|
else:
|
|
958
961
|
# Track tool execution time
|
|
959
962
|
tool_start_time = get_utc_timestamp_ns()
|
|
963
|
+
target_tool = next((x for x in agent_state.tools if x.name == tool_call_name), None)
|
|
964
|
+
|
|
960
965
|
tool_execution_result = await self._execute_tool(
|
|
961
|
-
|
|
966
|
+
target_tool=target_tool,
|
|
962
967
|
tool_args=tool_args,
|
|
963
968
|
agent_state=agent_state,
|
|
964
969
|
agent_step_span=agent_step_span,
|
|
@@ -1079,20 +1084,20 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
1079
1084
|
@trace_method
|
|
1080
1085
|
async def _execute_tool(
|
|
1081
1086
|
self,
|
|
1082
|
-
|
|
1087
|
+
target_tool: Tool,
|
|
1083
1088
|
tool_args: JsonDict,
|
|
1084
1089
|
agent_state: AgentState,
|
|
1085
1090
|
agent_step_span: Span | None = None,
|
|
1086
1091
|
step_id: str | None = None,
|
|
1087
|
-
run_id: str = None,
|
|
1088
1092
|
) -> "ToolExecutionResult":
|
|
1089
1093
|
"""
|
|
1090
1094
|
Executes a tool and returns the ToolExecutionResult.
|
|
1091
1095
|
"""
|
|
1092
1096
|
from letta.schemas.tool_execution_result import ToolExecutionResult
|
|
1093
1097
|
|
|
1098
|
+
tool_name = target_tool.name
|
|
1099
|
+
|
|
1094
1100
|
# Special memory case
|
|
1095
|
-
target_tool = next((x for x in agent_state.tools if x.name == tool_name), None)
|
|
1096
1101
|
if not target_tool:
|
|
1097
1102
|
# TODO: fix this error message
|
|
1098
1103
|
return ToolExecutionResult(
|
|
@@ -1106,7 +1111,8 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
1106
1111
|
start_time = get_utc_timestamp_ns()
|
|
1107
1112
|
agent_step_span.add_event(name="tool_execution_started")
|
|
1108
1113
|
|
|
1109
|
-
|
|
1114
|
+
# Decrypt environment variable values
|
|
1115
|
+
sandbox_env_vars = {var.key: var.get_value_secret().get_plaintext() for var in agent_state.secrets}
|
|
1110
1116
|
tool_execution_manager = ToolExecutionManager(
|
|
1111
1117
|
agent_state=agent_state,
|
|
1112
1118
|
message_manager=self.message_manager,
|
|
@@ -1158,25 +1164,29 @@ class LettaAgentV2(BaseAgentV2):
|
|
|
1158
1164
|
# TODO: This can be broken by bad configs, e.g. lower bound too high, initial messages too fat, etc.
|
|
1159
1165
|
# TODO: `force` and `clear` seem to no longer be used, we should remove
|
|
1160
1166
|
if not skip_summarization:
|
|
1161
|
-
|
|
1162
|
-
self.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1167
|
+
try:
|
|
1168
|
+
if force or (total_tokens and total_tokens > self.agent_state.llm_config.context_window):
|
|
1169
|
+
self.logger.warning(
|
|
1170
|
+
f"Total tokens {total_tokens} exceeds configured max tokens {self.agent_state.llm_config.context_window}, forcefully clearing message history."
|
|
1171
|
+
)
|
|
1172
|
+
new_in_context_messages, updated = await self.summarizer.summarize(
|
|
1173
|
+
in_context_messages=in_context_messages,
|
|
1174
|
+
new_letta_messages=new_letta_messages,
|
|
1175
|
+
force=True,
|
|
1176
|
+
clear=True,
|
|
1177
|
+
)
|
|
1178
|
+
else:
|
|
1179
|
+
# NOTE (Sarah): Seems like this is doing nothing?
|
|
1180
|
+
self.logger.info(
|
|
1181
|
+
f"Total tokens {total_tokens} does not exceed configured max tokens {self.agent_state.llm_config.context_window}, passing summarizing w/o force."
|
|
1182
|
+
)
|
|
1183
|
+
new_in_context_messages, updated = await self.summarizer.summarize(
|
|
1184
|
+
in_context_messages=in_context_messages,
|
|
1185
|
+
new_letta_messages=new_letta_messages,
|
|
1186
|
+
)
|
|
1187
|
+
except Exception as e:
|
|
1188
|
+
self.logger.error(f"Failed to summarize conversation history: {e}")
|
|
1189
|
+
new_in_context_messages = in_context_messages + new_letta_messages
|
|
1180
1190
|
else:
|
|
1181
1191
|
new_in_context_messages = in_context_messages + new_letta_messages
|
|
1182
1192
|
|