letta-nightly 0.12.1.dev20251024104217__py3-none-any.whl → 0.13.0.dev20251025104015__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.dev20251025104015.dist-info}/METADATA +4 -2
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.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.dev20251025104015.dist-info}/WHEEL +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
from collections.abc import AsyncGenerator
|
|
3
2
|
from datetime import datetime, timezone
|
|
4
3
|
|
|
5
4
|
from letta.agents.letta_agent_v2 import LettaAgentV2
|
|
5
|
+
from letta.agents.letta_agent_v3 import LettaAgentV3
|
|
6
6
|
from letta.constants import DEFAULT_MAX_STEPS
|
|
7
7
|
from letta.groups.helpers import stringify_message
|
|
8
8
|
from letta.otel.tracing import trace_method
|
|
9
9
|
from letta.schemas.agent import AgentState
|
|
10
|
-
from letta.schemas.enums import
|
|
10
|
+
from letta.schemas.enums import RunStatus
|
|
11
11
|
from letta.schemas.group import Group, ManagerType
|
|
12
12
|
from letta.schemas.job import JobUpdate
|
|
13
13
|
from letta.schemas.letta_message import MessageType
|
|
@@ -59,7 +59,7 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
59
59
|
request_start_timestamp_ns=request_start_timestamp_ns,
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
-
await self.run_sleeptime_agents(
|
|
62
|
+
await self.run_sleeptime_agents()
|
|
63
63
|
|
|
64
64
|
response.usage.run_ids = self.run_ids
|
|
65
65
|
return response
|
|
@@ -92,10 +92,10 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
92
92
|
):
|
|
93
93
|
yield chunk
|
|
94
94
|
|
|
95
|
-
await self.run_sleeptime_agents(
|
|
95
|
+
await self.run_sleeptime_agents()
|
|
96
96
|
|
|
97
97
|
@trace_method
|
|
98
|
-
async def run_sleeptime_agents(self
|
|
98
|
+
async def run_sleeptime_agents(self):
|
|
99
99
|
# Get response messages
|
|
100
100
|
last_response_messages = self.response_messages
|
|
101
101
|
|
|
@@ -117,7 +117,6 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
117
117
|
sleeptime_agent_id,
|
|
118
118
|
last_response_messages,
|
|
119
119
|
last_processed_message_id,
|
|
120
|
-
use_assistant_message,
|
|
121
120
|
)
|
|
122
121
|
self.run_ids.append(sleeptime_run_id)
|
|
123
122
|
except Exception as e:
|
|
@@ -131,7 +130,6 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
131
130
|
sleeptime_agent_id: str,
|
|
132
131
|
response_messages: list[Message],
|
|
133
132
|
last_processed_message_id: str,
|
|
134
|
-
use_assistant_message: bool = True,
|
|
135
133
|
) -> str:
|
|
136
134
|
run = Run(
|
|
137
135
|
agent_id=sleeptime_agent_id,
|
|
@@ -150,7 +148,6 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
150
148
|
response_messages=response_messages,
|
|
151
149
|
last_processed_message_id=last_processed_message_id,
|
|
152
150
|
run_id=run.id,
|
|
153
|
-
use_assistant_message=use_assistant_message,
|
|
154
151
|
),
|
|
155
152
|
label=f"participant_agent_step_{sleeptime_agent_id}",
|
|
156
153
|
)
|
|
@@ -164,7 +161,6 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
164
161
|
response_messages: list[Message],
|
|
165
162
|
last_processed_message_id: str,
|
|
166
163
|
run_id: str,
|
|
167
|
-
use_assistant_message: bool = True,
|
|
168
164
|
) -> LettaResponse:
|
|
169
165
|
try:
|
|
170
166
|
# Update run status
|
|
@@ -200,7 +196,7 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
200
196
|
|
|
201
197
|
# Load sleeptime agent
|
|
202
198
|
sleeptime_agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=sleeptime_agent_id, actor=self.actor)
|
|
203
|
-
sleeptime_agent =
|
|
199
|
+
sleeptime_agent = LettaAgentV3(
|
|
204
200
|
agent_state=sleeptime_agent_state,
|
|
205
201
|
actor=self.actor,
|
|
206
202
|
)
|
|
@@ -209,7 +205,6 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
|
209
205
|
result = await sleeptime_agent.step(
|
|
210
206
|
input_messages=sleeptime_agent_messages,
|
|
211
207
|
run_id=run_id,
|
|
212
|
-
use_assistant_message=use_assistant_message,
|
|
213
208
|
)
|
|
214
209
|
|
|
215
210
|
# Update run status
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from letta.agents.letta_agent_v3 import LettaAgentV3
|
|
6
|
+
from letta.constants import DEFAULT_MAX_STEPS
|
|
7
|
+
from letta.groups.helpers import stringify_message
|
|
8
|
+
from letta.otel.tracing import trace_method
|
|
9
|
+
from letta.schemas.agent import AgentState
|
|
10
|
+
from letta.schemas.enums import JobStatus, RunStatus
|
|
11
|
+
from letta.schemas.group import Group, ManagerType
|
|
12
|
+
from letta.schemas.job import JobUpdate
|
|
13
|
+
from letta.schemas.letta_message import MessageType
|
|
14
|
+
from letta.schemas.letta_message_content import TextContent
|
|
15
|
+
from letta.schemas.letta_response import LettaResponse
|
|
16
|
+
from letta.schemas.message import Message, MessageCreate
|
|
17
|
+
from letta.schemas.run import Run, RunUpdate
|
|
18
|
+
from letta.schemas.user import User
|
|
19
|
+
from letta.services.group_manager import GroupManager
|
|
20
|
+
from letta.utils import safe_create_task
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SleeptimeMultiAgentV4(LettaAgentV3):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
agent_state: AgentState,
|
|
27
|
+
actor: User,
|
|
28
|
+
group: Group,
|
|
29
|
+
):
|
|
30
|
+
super().__init__(agent_state, actor)
|
|
31
|
+
assert group.manager_type == ManagerType.sleeptime, f"Expected group type to be 'sleeptime', got {group.manager_type}"
|
|
32
|
+
self.group = group
|
|
33
|
+
self.run_ids = []
|
|
34
|
+
|
|
35
|
+
# Additional manager classes
|
|
36
|
+
self.group_manager = GroupManager()
|
|
37
|
+
|
|
38
|
+
@trace_method
|
|
39
|
+
async def step(
|
|
40
|
+
self,
|
|
41
|
+
input_messages: list[MessageCreate],
|
|
42
|
+
max_steps: int = DEFAULT_MAX_STEPS,
|
|
43
|
+
run_id: str | None = None,
|
|
44
|
+
use_assistant_message: bool = True,
|
|
45
|
+
include_return_message_types: list[MessageType] | None = None,
|
|
46
|
+
request_start_timestamp_ns: int | None = None,
|
|
47
|
+
) -> LettaResponse:
|
|
48
|
+
self.run_ids = []
|
|
49
|
+
|
|
50
|
+
for i in range(len(input_messages)):
|
|
51
|
+
input_messages[i].group_id = self.group.id
|
|
52
|
+
|
|
53
|
+
response = await super().step(
|
|
54
|
+
input_messages=input_messages,
|
|
55
|
+
max_steps=max_steps,
|
|
56
|
+
run_id=run_id,
|
|
57
|
+
use_assistant_message=use_assistant_message,
|
|
58
|
+
include_return_message_types=include_return_message_types,
|
|
59
|
+
request_start_timestamp_ns=request_start_timestamp_ns,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await self.run_sleeptime_agents()
|
|
63
|
+
response.usage.run_ids = self.run_ids
|
|
64
|
+
return response
|
|
65
|
+
|
|
66
|
+
@trace_method
|
|
67
|
+
async def stream(
|
|
68
|
+
self,
|
|
69
|
+
input_messages: list[MessageCreate],
|
|
70
|
+
max_steps: int = DEFAULT_MAX_STEPS,
|
|
71
|
+
stream_tokens: bool = True,
|
|
72
|
+
run_id: str | None = None,
|
|
73
|
+
use_assistant_message: bool = True,
|
|
74
|
+
request_start_timestamp_ns: int | None = None,
|
|
75
|
+
include_return_message_types: list[MessageType] | None = None,
|
|
76
|
+
) -> AsyncGenerator[str, None]:
|
|
77
|
+
self.run_ids = []
|
|
78
|
+
|
|
79
|
+
for i in range(len(input_messages)):
|
|
80
|
+
input_messages[i].group_id = self.group.id
|
|
81
|
+
|
|
82
|
+
# Perform foreground agent step
|
|
83
|
+
async for chunk in super().stream(
|
|
84
|
+
input_messages=input_messages,
|
|
85
|
+
max_steps=max_steps,
|
|
86
|
+
stream_tokens=stream_tokens,
|
|
87
|
+
run_id=run_id,
|
|
88
|
+
use_assistant_message=use_assistant_message,
|
|
89
|
+
include_return_message_types=include_return_message_types,
|
|
90
|
+
request_start_timestamp_ns=request_start_timestamp_ns,
|
|
91
|
+
):
|
|
92
|
+
yield chunk
|
|
93
|
+
|
|
94
|
+
await self.run_sleeptime_agents()
|
|
95
|
+
|
|
96
|
+
@trace_method
|
|
97
|
+
async def run_sleeptime_agents(self):
|
|
98
|
+
# Get response messages
|
|
99
|
+
last_response_messages = self.response_messages
|
|
100
|
+
|
|
101
|
+
# Update turns counter
|
|
102
|
+
turns_counter = None
|
|
103
|
+
if self.group.sleeptime_agent_frequency is not None and self.group.sleeptime_agent_frequency > 0:
|
|
104
|
+
turns_counter = await self.group_manager.bump_turns_counter_async(group_id=self.group.id, actor=self.actor)
|
|
105
|
+
|
|
106
|
+
# Perform participant steps
|
|
107
|
+
if self.group.sleeptime_agent_frequency is None or (
|
|
108
|
+
turns_counter is not None and turns_counter % self.group.sleeptime_agent_frequency == 0
|
|
109
|
+
):
|
|
110
|
+
last_processed_message_id = await self.group_manager.get_last_processed_message_id_and_update_async(
|
|
111
|
+
group_id=self.group.id, last_processed_message_id=last_response_messages[-1].id, actor=self.actor
|
|
112
|
+
)
|
|
113
|
+
for sleeptime_agent_id in self.group.agent_ids:
|
|
114
|
+
try:
|
|
115
|
+
sleeptime_run_id = await self._issue_background_task(
|
|
116
|
+
sleeptime_agent_id,
|
|
117
|
+
last_response_messages,
|
|
118
|
+
last_processed_message_id,
|
|
119
|
+
)
|
|
120
|
+
self.run_ids.append(sleeptime_run_id)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
# Individual task failures
|
|
123
|
+
print(f"Sleeptime agent processing failed: {e!s}")
|
|
124
|
+
raise e
|
|
125
|
+
|
|
126
|
+
@trace_method
|
|
127
|
+
async def _issue_background_task(
|
|
128
|
+
self,
|
|
129
|
+
sleeptime_agent_id: str,
|
|
130
|
+
response_messages: list[Message],
|
|
131
|
+
last_processed_message_id: str,
|
|
132
|
+
) -> str:
|
|
133
|
+
run = Run(
|
|
134
|
+
agent_id=sleeptime_agent_id,
|
|
135
|
+
status=RunStatus.created,
|
|
136
|
+
metadata={
|
|
137
|
+
"run_type": "sleeptime_agent_send_message_async", # is this right?
|
|
138
|
+
"agent_id": sleeptime_agent_id,
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
run = await self.run_manager.create_run(pydantic_run=run, actor=self.actor)
|
|
142
|
+
|
|
143
|
+
safe_create_task(
|
|
144
|
+
self._participant_agent_step(
|
|
145
|
+
foreground_agent_id=self.agent_state.id,
|
|
146
|
+
sleeptime_agent_id=sleeptime_agent_id,
|
|
147
|
+
response_messages=response_messages,
|
|
148
|
+
last_processed_message_id=last_processed_message_id,
|
|
149
|
+
run_id=run.id,
|
|
150
|
+
),
|
|
151
|
+
label=f"participant_agent_step_{sleeptime_agent_id}",
|
|
152
|
+
)
|
|
153
|
+
return run.id
|
|
154
|
+
|
|
155
|
+
@trace_method
|
|
156
|
+
async def _participant_agent_step(
|
|
157
|
+
self,
|
|
158
|
+
foreground_agent_id: str,
|
|
159
|
+
sleeptime_agent_id: str,
|
|
160
|
+
response_messages: list[Message],
|
|
161
|
+
last_processed_message_id: str,
|
|
162
|
+
run_id: str,
|
|
163
|
+
) -> LettaResponse:
|
|
164
|
+
try:
|
|
165
|
+
# Update run status
|
|
166
|
+
run_update = RunUpdate(status=RunStatus.running)
|
|
167
|
+
await self.run_manager.update_run_by_id_async(run_id=run_id, update=run_update, actor=self.actor)
|
|
168
|
+
|
|
169
|
+
# Create conversation transcript
|
|
170
|
+
prior_messages = []
|
|
171
|
+
if self.group.sleeptime_agent_frequency:
|
|
172
|
+
try:
|
|
173
|
+
prior_messages = await self.message_manager.list_messages(
|
|
174
|
+
agent_id=foreground_agent_id,
|
|
175
|
+
actor=self.actor,
|
|
176
|
+
after=last_processed_message_id,
|
|
177
|
+
before=response_messages[0].id,
|
|
178
|
+
)
|
|
179
|
+
except Exception:
|
|
180
|
+
pass # continue with just latest messages
|
|
181
|
+
|
|
182
|
+
transcript_summary = [stringify_message(message) for message in prior_messages + response_messages]
|
|
183
|
+
transcript_summary = [summary for summary in transcript_summary if summary is not None]
|
|
184
|
+
message_text = "\n".join(transcript_summary)
|
|
185
|
+
|
|
186
|
+
sleeptime_agent_messages = [
|
|
187
|
+
MessageCreate(
|
|
188
|
+
role="user",
|
|
189
|
+
content=[TextContent(text=message_text)],
|
|
190
|
+
id=Message.generate_id(),
|
|
191
|
+
agent_id=sleeptime_agent_id,
|
|
192
|
+
group_id=self.group.id,
|
|
193
|
+
)
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
# Load sleeptime agent
|
|
197
|
+
sleeptime_agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=sleeptime_agent_id, actor=self.actor)
|
|
198
|
+
sleeptime_agent = LettaAgentV3(
|
|
199
|
+
agent_state=sleeptime_agent_state,
|
|
200
|
+
actor=self.actor,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Perform sleeptime agent step
|
|
204
|
+
result = await sleeptime_agent.step(
|
|
205
|
+
input_messages=sleeptime_agent_messages,
|
|
206
|
+
run_id=run_id,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Update run status
|
|
210
|
+
run_update = RunUpdate(
|
|
211
|
+
status=RunStatus.completed,
|
|
212
|
+
completed_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
|
213
|
+
metadata={
|
|
214
|
+
"result": result.model_dump(mode="json"),
|
|
215
|
+
"agent_id": sleeptime_agent_state.id,
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
await self.run_manager.update_run_by_id_async(run_id=run_id, update=run_update, actor=self.actor)
|
|
219
|
+
return result
|
|
220
|
+
except Exception as e:
|
|
221
|
+
run_update = RunUpdate(
|
|
222
|
+
status=RunStatus.failed,
|
|
223
|
+
completed_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
|
224
|
+
metadata={"error": str(e)},
|
|
225
|
+
)
|
|
226
|
+
await self.run_manager.update_run_by_id_async(run_id=run_id, update=run_update, actor=self.actor)
|
|
227
|
+
raise
|
letta/helpers/converters.py
CHANGED
|
@@ -8,6 +8,7 @@ from sqlalchemy import Dialect
|
|
|
8
8
|
from letta.functions.mcp_client.types import StdioServerConfig
|
|
9
9
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
10
10
|
from letta.schemas.enums import ProviderType, ToolRuleType
|
|
11
|
+
from letta.schemas.letta_message import ApprovalReturn, MessageReturnType
|
|
11
12
|
from letta.schemas.letta_message_content import (
|
|
12
13
|
ImageContent,
|
|
13
14
|
ImageSourceType,
|
|
@@ -180,6 +181,7 @@ def deserialize_tool_calls(data: Optional[List[Dict]]) -> List[OpenAIToolCall]:
|
|
|
180
181
|
|
|
181
182
|
calls = []
|
|
182
183
|
for item in data:
|
|
184
|
+
item.pop("requires_approval", None) # legacy field
|
|
183
185
|
func_data = item.pop("function", None)
|
|
184
186
|
tool_call_function = OpenAIFunction(**func_data)
|
|
185
187
|
calls.append(OpenAIToolCall(function=tool_call_function, **item))
|
|
@@ -222,6 +224,49 @@ def deserialize_tool_returns(data: Optional[List[Dict]]) -> List[ToolReturn]:
|
|
|
222
224
|
return tool_returns
|
|
223
225
|
|
|
224
226
|
|
|
227
|
+
# --------------------------
|
|
228
|
+
# Approvals Serialization
|
|
229
|
+
# --------------------------
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def serialize_approvals(approvals: Optional[List[Union[ApprovalReturn, ToolReturn, dict]]]) -> List[Dict]:
|
|
233
|
+
"""Convert a list of ToolReturn objects into JSON-serializable format."""
|
|
234
|
+
if not approvals:
|
|
235
|
+
return []
|
|
236
|
+
|
|
237
|
+
serialized_approvals = []
|
|
238
|
+
for approval in approvals:
|
|
239
|
+
if isinstance(approval, ApprovalReturn):
|
|
240
|
+
serialized_approvals.append(approval.model_dump(mode="json"))
|
|
241
|
+
elif isinstance(approval, ToolReturn):
|
|
242
|
+
serialized_approvals.append(approval.model_dump(mode="json"))
|
|
243
|
+
elif isinstance(approval, dict):
|
|
244
|
+
serialized_approvals.append(approval) # Already a dictionary, leave it as-is
|
|
245
|
+
else:
|
|
246
|
+
raise TypeError(f"Unexpected approval type: {type(approval)}")
|
|
247
|
+
|
|
248
|
+
return serialized_approvals
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def deserialize_approvals(data: Optional[List[Dict]]) -> List[Union[ApprovalReturn, ToolReturn]]:
|
|
252
|
+
"""Convert a JSON list back into ApprovalReturn and ToolReturn objects."""
|
|
253
|
+
if not data:
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
approvals = []
|
|
257
|
+
for item in data:
|
|
258
|
+
if "type" in item and item.get("type") == MessageReturnType.approval:
|
|
259
|
+
approval_return = ApprovalReturn(**item)
|
|
260
|
+
approvals.append(approval_return)
|
|
261
|
+
elif "status" in item:
|
|
262
|
+
tool_return = ToolReturn(**item)
|
|
263
|
+
approvals.append(tool_return)
|
|
264
|
+
else:
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
return approvals
|
|
268
|
+
|
|
269
|
+
|
|
225
270
|
# ----------------------------
|
|
226
271
|
# MessageContent Serialization
|
|
227
272
|
# ----------------------------
|
|
@@ -459,14 +504,43 @@ def deserialize_response_format(data: Optional[Dict]) -> Optional[ResponseFormat
|
|
|
459
504
|
|
|
460
505
|
|
|
461
506
|
def serialize_mcp_stdio_config(config: Union[Optional[StdioServerConfig], Dict]) -> Optional[Dict]:
|
|
462
|
-
"""Convert an StdioServerConfig object into a JSON-serializable dictionary.
|
|
507
|
+
"""Convert an StdioServerConfig object into a JSON-serializable dictionary.
|
|
508
|
+
|
|
509
|
+
Persist required fields for successful deserialization back into a
|
|
510
|
+
StdioServerConfig model (namely `server_name` and `type`). The
|
|
511
|
+
`to_dict()` helper intentionally omits these since they're not needed
|
|
512
|
+
by MCP transport, but our ORM deserializer reconstructs the pydantic
|
|
513
|
+
model and requires them.
|
|
514
|
+
"""
|
|
463
515
|
if config and isinstance(config, StdioServerConfig):
|
|
464
|
-
|
|
516
|
+
data = config.to_dict()
|
|
517
|
+
# Preserve required fields for pydantic reconstruction
|
|
518
|
+
data["server_name"] = config.server_name
|
|
519
|
+
# Store enum as its value; pydantic will coerce on load
|
|
520
|
+
data["type"] = config.type.value if hasattr(config.type, "value") else str(config.type)
|
|
521
|
+
return data
|
|
465
522
|
return config
|
|
466
523
|
|
|
467
524
|
|
|
468
525
|
def deserialize_mcp_stdio_config(data: Optional[Dict]) -> Optional[StdioServerConfig]:
|
|
469
|
-
"""Convert a dictionary back into an StdioServerConfig object.
|
|
526
|
+
"""Convert a dictionary back into an StdioServerConfig object.
|
|
527
|
+
|
|
528
|
+
Backwards-compatibility notes:
|
|
529
|
+
- Older rows may only include `transport`, `command`, `args`, `env`.
|
|
530
|
+
In that case, provide defaults for `server_name` and `type` to
|
|
531
|
+
satisfy the pydantic model requirements.
|
|
532
|
+
- If both `type` and `transport` are present, prefer `type`.
|
|
533
|
+
"""
|
|
470
534
|
if not data:
|
|
471
535
|
return None
|
|
472
|
-
|
|
536
|
+
|
|
537
|
+
payload = dict(data)
|
|
538
|
+
# Map legacy `transport` field to required `type` if missing
|
|
539
|
+
if "type" not in payload and "transport" in payload:
|
|
540
|
+
payload["type"] = payload["transport"]
|
|
541
|
+
|
|
542
|
+
# Ensure required field exists; use a sensible placeholder when unknown
|
|
543
|
+
if "server_name" not in payload:
|
|
544
|
+
payload["server_name"] = payload.get("name", "unknown")
|
|
545
|
+
|
|
546
|
+
return StdioServerConfig(**payload)
|
letta/helpers/crypto_utils.py
CHANGED
|
@@ -47,7 +47,9 @@ class CryptoUtils:
|
|
|
47
47
|
master_key = settings.encryption_key
|
|
48
48
|
|
|
49
49
|
if not master_key:
|
|
50
|
-
raise ValueError(
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"No encryption key configured. Please set the LETTA_ENCRYPTION_KEY environment variable (not fully supported yet for Letta v0.12.1 and below)."
|
|
52
|
+
)
|
|
51
53
|
|
|
52
54
|
# Generate random salt and IV
|
|
53
55
|
salt = os.urandom(cls.SALT_SIZE)
|
|
@@ -91,7 +93,9 @@ class CryptoUtils:
|
|
|
91
93
|
master_key = settings.encryption_key
|
|
92
94
|
|
|
93
95
|
if not master_key:
|
|
94
|
-
raise ValueError(
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"No encryption key configured. Please set the LETTA_ENCRYPTION_KEY environment variable (not fully supported yet for Letta v0.12.1 and below)."
|
|
98
|
+
)
|
|
95
99
|
|
|
96
100
|
try:
|
|
97
101
|
# Decode from base64
|
|
@@ -39,6 +39,7 @@ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
|
39
39
|
from letta.schemas.message import Message
|
|
40
40
|
from letta.schemas.openai.chat_completion_response import FunctionCall, ToolCall
|
|
41
41
|
from letta.server.rest_api.json_parser import JSONParser, PydanticJSONParser
|
|
42
|
+
from letta.server.rest_api.utils import decrement_message_uuid
|
|
42
43
|
|
|
43
44
|
logger = get_logger(__name__)
|
|
44
45
|
|
|
@@ -282,14 +283,12 @@ class SimpleAnthropicStreamingInterface:
|
|
|
282
283
|
call_id = content.id
|
|
283
284
|
# Initialize arguments from the start event's input (often {}) to avoid undefined in UIs
|
|
284
285
|
if name in self.requires_approval_tools:
|
|
285
|
-
if prev_message_type and prev_message_type != "approval_request_message":
|
|
286
|
-
message_index += 1
|
|
287
286
|
tool_call_msg = ApprovalRequestMessage(
|
|
288
|
-
id=self.letta_message_id,
|
|
287
|
+
id=decrement_message_uuid(self.letta_message_id),
|
|
289
288
|
# Do not emit placeholder arguments here to avoid UI duplicates
|
|
290
289
|
tool_call=ToolCallDelta(name=name, tool_call_id=call_id),
|
|
291
290
|
date=datetime.now(timezone.utc).isoformat(),
|
|
292
|
-
otid=Message.generate_otid_from_id(self.letta_message_id,
|
|
291
|
+
otid=Message.generate_otid_from_id(decrement_message_uuid(self.letta_message_id), -1),
|
|
293
292
|
run_id=self.run_id,
|
|
294
293
|
step_id=self.step_id,
|
|
295
294
|
)
|
|
@@ -300,12 +299,13 @@ class SimpleAnthropicStreamingInterface:
|
|
|
300
299
|
id=self.letta_message_id,
|
|
301
300
|
# Do not emit placeholder arguments here to avoid UI duplicates
|
|
302
301
|
tool_call=ToolCallDelta(name=name, tool_call_id=call_id),
|
|
302
|
+
tool_calls=ToolCallDelta(name=name, tool_call_id=call_id),
|
|
303
303
|
date=datetime.now(timezone.utc).isoformat(),
|
|
304
304
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
|
305
305
|
run_id=self.run_id,
|
|
306
306
|
step_id=self.step_id,
|
|
307
307
|
)
|
|
308
|
-
|
|
308
|
+
prev_message_type = tool_call_msg.message_type
|
|
309
309
|
yield tool_call_msg
|
|
310
310
|
|
|
311
311
|
elif isinstance(content, BetaThinkingBlock):
|
|
@@ -345,7 +345,6 @@ class SimpleAnthropicStreamingInterface:
|
|
|
345
345
|
|
|
346
346
|
assistant_msg = AssistantMessage(
|
|
347
347
|
id=self.letta_message_id,
|
|
348
|
-
# content=[TextContent(text=delta.text)],
|
|
349
348
|
content=delta.text,
|
|
350
349
|
date=datetime.now(timezone.utc).isoformat(),
|
|
351
350
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
|
@@ -381,13 +380,11 @@ class SimpleAnthropicStreamingInterface:
|
|
|
381
380
|
call_id = ctx.get("id")
|
|
382
381
|
|
|
383
382
|
if name in self.requires_approval_tools:
|
|
384
|
-
if prev_message_type and prev_message_type != "approval_request_message":
|
|
385
|
-
message_index += 1
|
|
386
383
|
tool_call_msg = ApprovalRequestMessage(
|
|
387
|
-
id=self.letta_message_id,
|
|
384
|
+
id=decrement_message_uuid(self.letta_message_id),
|
|
388
385
|
tool_call=ToolCallDelta(name=name, tool_call_id=call_id, arguments=delta.partial_json),
|
|
389
386
|
date=datetime.now(timezone.utc).isoformat(),
|
|
390
|
-
otid=Message.generate_otid_from_id(self.letta_message_id,
|
|
387
|
+
otid=Message.generate_otid_from_id(decrement_message_uuid(self.letta_message_id), -1),
|
|
391
388
|
run_id=self.run_id,
|
|
392
389
|
step_id=self.step_id,
|
|
393
390
|
)
|
|
@@ -397,12 +394,13 @@ class SimpleAnthropicStreamingInterface:
|
|
|
397
394
|
tool_call_msg = ToolCallMessage(
|
|
398
395
|
id=self.letta_message_id,
|
|
399
396
|
tool_call=ToolCallDelta(name=name, tool_call_id=call_id, arguments=delta.partial_json),
|
|
397
|
+
tool_calls=ToolCallDelta(name=name, tool_call_id=call_id, arguments=delta.partial_json),
|
|
400
398
|
date=datetime.now(timezone.utc).isoformat(),
|
|
401
399
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
|
402
400
|
run_id=self.run_id,
|
|
403
401
|
step_id=self.step_id,
|
|
404
402
|
)
|
|
405
|
-
|
|
403
|
+
prev_message_type = tool_call_msg.message_type
|
|
406
404
|
yield tool_call_msg
|
|
407
405
|
|
|
408
406
|
elif isinstance(delta, BetaThinkingDelta):
|
|
@@ -235,7 +235,7 @@ class AnthropicStreamingInterface:
|
|
|
235
235
|
except Exception as e:
|
|
236
236
|
import traceback
|
|
237
237
|
|
|
238
|
-
logger.
|
|
238
|
+
logger.exception("Error processing stream: %s", e)
|
|
239
239
|
if ttft_span:
|
|
240
240
|
ttft_span.add_event(
|
|
241
241
|
name="stop_reason",
|
|
@@ -454,7 +454,7 @@ class AnthropicStreamingInterface:
|
|
|
454
454
|
message_index += 1
|
|
455
455
|
assistant_msg = AssistantMessage(
|
|
456
456
|
id=self.letta_message_id,
|
|
457
|
-
content=
|
|
457
|
+
content=send_message_diff,
|
|
458
458
|
date=datetime.now(timezone.utc).isoformat(),
|
|
459
459
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
|
460
460
|
run_id=self.run_id,
|
|
@@ -734,7 +734,7 @@ class SimpleAnthropicStreamingInterface:
|
|
|
734
734
|
except Exception as e:
|
|
735
735
|
import traceback
|
|
736
736
|
|
|
737
|
-
logger.
|
|
737
|
+
logger.exception("Error processing stream: %s", e)
|
|
738
738
|
if ttft_span:
|
|
739
739
|
ttft_span.add_event(
|
|
740
740
|
name="stop_reason",
|
|
@@ -836,7 +836,6 @@ class SimpleAnthropicStreamingInterface:
|
|
|
836
836
|
|
|
837
837
|
assistant_msg = AssistantMessage(
|
|
838
838
|
id=self.letta_message_id,
|
|
839
|
-
# content=[TextContent(text=delta.text)],
|
|
840
839
|
content=delta.text,
|
|
841
840
|
date=datetime.now(timezone.utc).isoformat(),
|
|
842
841
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
|
@@ -26,6 +26,7 @@ from letta.schemas.letta_message_content import (
|
|
|
26
26
|
from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
27
27
|
from letta.schemas.message import Message
|
|
28
28
|
from letta.schemas.openai.chat_completion_response import FunctionCall, ToolCall
|
|
29
|
+
from letta.server.rest_api.utils import decrement_message_uuid
|
|
29
30
|
from letta.utils import get_tool_call_id
|
|
30
31
|
|
|
31
32
|
logger = get_logger(__name__)
|
|
@@ -138,7 +139,7 @@ class SimpleGeminiStreamingInterface:
|
|
|
138
139
|
except Exception as e:
|
|
139
140
|
import traceback
|
|
140
141
|
|
|
141
|
-
logger.
|
|
142
|
+
logger.exception("Error processing stream: %s", e)
|
|
142
143
|
if ttft_span:
|
|
143
144
|
ttft_span.add_event(
|
|
144
145
|
name="stop_reason",
|
|
@@ -255,11 +256,9 @@ class SimpleGeminiStreamingInterface:
|
|
|
255
256
|
self.tool_call_args = arguments
|
|
256
257
|
|
|
257
258
|
if self.tool_call_name and self.tool_call_name in self.requires_approval_tools:
|
|
258
|
-
if prev_message_type and prev_message_type != "approval_request_message":
|
|
259
|
-
message_index += 1
|
|
260
259
|
yield ApprovalRequestMessage(
|
|
261
|
-
id=self.letta_message_id,
|
|
262
|
-
otid=Message.generate_otid_from_id(self.letta_message_id,
|
|
260
|
+
id=decrement_message_uuid(self.letta_message_id),
|
|
261
|
+
otid=Message.generate_otid_from_id(decrement_message_uuid(self.letta_message_id), -1),
|
|
263
262
|
date=datetime.now(timezone.utc),
|
|
264
263
|
tool_call=ToolCallDelta(
|
|
265
264
|
name=name,
|
|
@@ -269,7 +268,6 @@ class SimpleGeminiStreamingInterface:
|
|
|
269
268
|
run_id=self.run_id,
|
|
270
269
|
step_id=self.step_id,
|
|
271
270
|
)
|
|
272
|
-
prev_message_type = "approval_request_message"
|
|
273
271
|
else:
|
|
274
272
|
if prev_message_type and prev_message_type != "tool_call_message":
|
|
275
273
|
message_index += 1
|