letta-nightly 0.11.7.dev20250910104051__py3-none-any.whl → 0.11.7.dev20250911104039__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/adapters/letta_llm_request_adapter.py +4 -2
- letta/adapters/letta_llm_stream_adapter.py +4 -2
- letta/agents/agent_loop.py +23 -0
- letta/agents/letta_agent_v2.py +5 -4
- letta/functions/helpers.py +3 -2
- letta/groups/sleeptime_multi_agent_v2.py +4 -2
- letta/groups/sleeptime_multi_agent_v3.py +4 -2
- letta/interfaces/anthropic_streaming_interface.py +10 -6
- letta/llm_api/google_vertex_client.py +1 -1
- letta/orm/agent.py +4 -1
- letta/orm/block.py +1 -0
- letta/orm/blocks_agents.py +1 -0
- letta/orm/sources_agents.py +2 -1
- letta/orm/tools_agents.py +5 -2
- letta/schemas/message.py +19 -2
- letta/server/rest_api/interface.py +34 -2
- letta/server/rest_api/json_parser.py +2 -0
- letta/server/rest_api/redis_stream_manager.py +2 -1
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +4 -2
- letta/server/rest_api/routers/v1/agents.py +47 -180
- letta/server/rest_api/routers/v1/folders.py +2 -2
- letta/server/rest_api/routers/v1/sources.py +2 -2
- letta/server/rest_api/streaming_response.py +2 -1
- letta/server/server.py +7 -5
- letta/services/agent_serialization_manager.py +4 -3
- letta/services/mcp_manager.py +2 -2
- letta/services/summarizer/summarizer.py +2 -1
- letta/services/tool_executor/multi_agent_tool_executor.py +17 -14
- letta/services/tool_sandbox/local_sandbox.py +2 -2
- letta/services/tool_sandbox/modal_version_manager.py +2 -1
- letta/streaming_utils.py +29 -4
- letta/utils.py +72 -3
- {letta_nightly-0.11.7.dev20250910104051.dist-info → letta_nightly-0.11.7.dev20250911104039.dist-info}/METADATA +1 -1
- {letta_nightly-0.11.7.dev20250910104051.dist-info → letta_nightly-0.11.7.dev20250911104039.dist-info}/RECORD +37 -36
- {letta_nightly-0.11.7.dev20250910104051.dist-info → letta_nightly-0.11.7.dev20250911104039.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20250910104051.dist-info → letta_nightly-0.11.7.dev20250911104039.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.7.dev20250910104051.dist-info → letta_nightly-0.11.7.dev20250911104039.dist-info}/licenses/LICENSE +0 -0
@@ -12,7 +12,9 @@ from pydantic import BaseModel, Field
|
|
12
12
|
from sqlalchemy.exc import IntegrityError, OperationalError
|
13
13
|
from starlette.responses import Response, StreamingResponse
|
14
14
|
|
15
|
+
from letta.agents.agent_loop import AgentLoop
|
15
16
|
from letta.agents.letta_agent import LettaAgent
|
17
|
+
from letta.agents.letta_agent_v2 import LettaAgentV2
|
16
18
|
from letta.constants import AGENT_ID_PATTERN, DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, REDIS_RUN_ID_PREFIX
|
17
19
|
from letta.data_sources.redis_client import NoopAsyncRedisClient, get_redis_client
|
18
20
|
from letta.errors import (
|
@@ -58,7 +60,7 @@ from letta.server.server import SyncServer
|
|
58
60
|
from letta.services.summarizer.enums import SummarizationMode
|
59
61
|
from letta.services.telemetry_manager import NoopTelemetryManager
|
60
62
|
from letta.settings import settings
|
61
|
-
from letta.utils import safe_create_task, truncate_file_visible_content
|
63
|
+
from letta.utils import safe_create_shielded_task, safe_create_task, truncate_file_visible_content
|
62
64
|
|
63
65
|
# These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
|
64
66
|
|
@@ -1144,7 +1146,9 @@ async def send_message(
|
|
1144
1146
|
|
1145
1147
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
1146
1148
|
# TODO: This is redundant, remove soon
|
1147
|
-
agent = await server.agent_manager.get_agent_by_id_async(
|
1149
|
+
agent = await server.agent_manager.get_agent_by_id_async(
|
1150
|
+
agent_id, actor, include_relationships=["memory", "multi_agent_group", "sources", "tool_exec_environment_variables", "tools"]
|
1151
|
+
)
|
1148
1152
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
1149
1153
|
model_compatible = agent.llm_config.model_endpoint_type in [
|
1150
1154
|
"anthropic",
|
@@ -1190,42 +1194,11 @@ async def send_message(
|
|
1190
1194
|
|
1191
1195
|
try:
|
1192
1196
|
if agent_eligible and model_compatible:
|
1193
|
-
|
1194
|
-
agent_loop = SleeptimeMultiAgentV2(
|
1195
|
-
agent_id=agent_id,
|
1196
|
-
message_manager=server.message_manager,
|
1197
|
-
agent_manager=server.agent_manager,
|
1198
|
-
block_manager=server.block_manager,
|
1199
|
-
passage_manager=server.passage_manager,
|
1200
|
-
group_manager=server.group_manager,
|
1201
|
-
job_manager=server.job_manager,
|
1202
|
-
actor=actor,
|
1203
|
-
group=agent.multi_agent_group,
|
1204
|
-
current_run_id=run.id if run else None,
|
1205
|
-
)
|
1206
|
-
else:
|
1207
|
-
agent_loop = LettaAgent(
|
1208
|
-
agent_id=agent_id,
|
1209
|
-
message_manager=server.message_manager,
|
1210
|
-
agent_manager=server.agent_manager,
|
1211
|
-
block_manager=server.block_manager,
|
1212
|
-
job_manager=server.job_manager,
|
1213
|
-
passage_manager=server.passage_manager,
|
1214
|
-
actor=actor,
|
1215
|
-
step_manager=server.step_manager,
|
1216
|
-
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1217
|
-
current_run_id=run.id if run else None,
|
1218
|
-
# summarizer settings to be added here
|
1219
|
-
summarizer_mode=(
|
1220
|
-
SummarizationMode.STATIC_MESSAGE_BUFFER
|
1221
|
-
if agent.agent_type == AgentType.voice_convo_agent
|
1222
|
-
else SummarizationMode.PARTIAL_EVICT_MESSAGE_BUFFER
|
1223
|
-
),
|
1224
|
-
)
|
1225
|
-
|
1197
|
+
agent_loop = AgentLoop.load(agent_state=agent, actor=actor)
|
1226
1198
|
result = await agent_loop.step(
|
1227
1199
|
request.messages,
|
1228
1200
|
max_steps=request.max_steps,
|
1201
|
+
run_id=run.id if run else None,
|
1229
1202
|
use_assistant_message=request.use_assistant_message,
|
1230
1203
|
request_start_timestamp_ns=request_start_timestamp_ns,
|
1231
1204
|
include_return_message_types=request.include_return_message_types,
|
@@ -1299,7 +1272,9 @@ async def send_message_streaming(
|
|
1299
1272
|
|
1300
1273
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
1301
1274
|
# TODO: This is redundant, remove soon
|
1302
|
-
agent = await server.agent_manager.get_agent_by_id_async(
|
1275
|
+
agent = await server.agent_manager.get_agent_by_id_async(
|
1276
|
+
agent_id, actor, include_relationships=["memory", "multi_agent_group", "sources", "tool_exec_environment_variables", "tools"]
|
1277
|
+
)
|
1303
1278
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
1304
1279
|
model_compatible = agent.llm_config.model_endpoint_type in [
|
1305
1280
|
"anthropic",
|
@@ -1344,57 +1319,16 @@ async def send_message_streaming(
|
|
1344
1319
|
|
1345
1320
|
try:
|
1346
1321
|
if agent_eligible and model_compatible:
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
step_manager=server.step_manager,
|
1358
|
-
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1359
|
-
group=agent.multi_agent_group,
|
1360
|
-
current_run_id=run.id if run else None,
|
1361
|
-
)
|
1362
|
-
else:
|
1363
|
-
agent_loop = LettaAgent(
|
1364
|
-
agent_id=agent_id,
|
1365
|
-
message_manager=server.message_manager,
|
1366
|
-
agent_manager=server.agent_manager,
|
1367
|
-
block_manager=server.block_manager,
|
1368
|
-
job_manager=server.job_manager,
|
1369
|
-
passage_manager=server.passage_manager,
|
1370
|
-
actor=actor,
|
1371
|
-
step_manager=server.step_manager,
|
1372
|
-
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1373
|
-
current_run_id=run.id if run else None,
|
1374
|
-
# summarizer settings to be added here
|
1375
|
-
summarizer_mode=(
|
1376
|
-
SummarizationMode.STATIC_MESSAGE_BUFFER
|
1377
|
-
if agent.agent_type == AgentType.voice_convo_agent
|
1378
|
-
else SummarizationMode.PARTIAL_EVICT_MESSAGE_BUFFER
|
1379
|
-
),
|
1380
|
-
)
|
1381
|
-
|
1382
|
-
if request.stream_tokens and model_compatible_token_streaming:
|
1383
|
-
raw_stream = agent_loop.step_stream(
|
1384
|
-
input_messages=request.messages,
|
1385
|
-
max_steps=request.max_steps,
|
1386
|
-
use_assistant_message=request.use_assistant_message,
|
1387
|
-
request_start_timestamp_ns=request_start_timestamp_ns,
|
1388
|
-
include_return_message_types=request.include_return_message_types,
|
1389
|
-
)
|
1390
|
-
else:
|
1391
|
-
raw_stream = agent_loop.step_stream_no_tokens(
|
1392
|
-
request.messages,
|
1393
|
-
max_steps=request.max_steps,
|
1394
|
-
use_assistant_message=request.use_assistant_message,
|
1395
|
-
request_start_timestamp_ns=request_start_timestamp_ns,
|
1396
|
-
include_return_message_types=request.include_return_message_types,
|
1397
|
-
)
|
1322
|
+
agent_loop = AgentLoop.load(agent_state=agent, actor=actor)
|
1323
|
+
raw_stream = agent_loop.stream(
|
1324
|
+
input_messages=request.messages,
|
1325
|
+
max_steps=request.max_steps,
|
1326
|
+
stream_tokens=request.stream_tokens and model_compatible_token_streaming,
|
1327
|
+
run_id=run.id if run else None,
|
1328
|
+
use_assistant_message=request.use_assistant_message,
|
1329
|
+
request_start_timestamp_ns=request_start_timestamp_ns,
|
1330
|
+
include_return_message_types=request.include_return_message_types,
|
1331
|
+
)
|
1398
1332
|
|
1399
1333
|
from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode, add_keepalive_to_stream
|
1400
1334
|
|
@@ -1409,12 +1343,13 @@ async def send_message_streaming(
|
|
1409
1343
|
),
|
1410
1344
|
)
|
1411
1345
|
|
1412
|
-
|
1346
|
+
safe_create_task(
|
1413
1347
|
create_background_stream_processor(
|
1414
1348
|
stream_generator=raw_stream,
|
1415
1349
|
redis_client=redis_client,
|
1416
1350
|
run_id=run.id,
|
1417
|
-
)
|
1351
|
+
),
|
1352
|
+
label=f"background_stream_processor_{run.id}",
|
1418
1353
|
)
|
1419
1354
|
|
1420
1355
|
raw_stream = redis_sse_stream_generator(
|
@@ -1568,7 +1503,9 @@ async def _process_message_background(
|
|
1568
1503
|
"""Background task to process the message and update job status."""
|
1569
1504
|
request_start_timestamp_ns = get_utc_timestamp_ns()
|
1570
1505
|
try:
|
1571
|
-
agent = await server.agent_manager.get_agent_by_id_async(
|
1506
|
+
agent = await server.agent_manager.get_agent_by_id_async(
|
1507
|
+
agent_id, actor, include_relationships=["memory", "multi_agent_group", "sources", "tool_exec_environment_variables", "tools"]
|
1508
|
+
)
|
1572
1509
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
1573
1510
|
model_compatible = agent.llm_config.model_endpoint_type in [
|
1574
1511
|
"anthropic",
|
@@ -1584,37 +1521,7 @@ async def _process_message_background(
|
|
1584
1521
|
"deepseek",
|
1585
1522
|
]
|
1586
1523
|
if agent_eligible and model_compatible:
|
1587
|
-
|
1588
|
-
agent_loop = SleeptimeMultiAgentV2(
|
1589
|
-
agent_id=agent_id,
|
1590
|
-
message_manager=server.message_manager,
|
1591
|
-
agent_manager=server.agent_manager,
|
1592
|
-
block_manager=server.block_manager,
|
1593
|
-
passage_manager=server.passage_manager,
|
1594
|
-
group_manager=server.group_manager,
|
1595
|
-
job_manager=server.job_manager,
|
1596
|
-
actor=actor,
|
1597
|
-
group=agent.multi_agent_group,
|
1598
|
-
)
|
1599
|
-
else:
|
1600
|
-
agent_loop = LettaAgent(
|
1601
|
-
agent_id=agent_id,
|
1602
|
-
message_manager=server.message_manager,
|
1603
|
-
agent_manager=server.agent_manager,
|
1604
|
-
block_manager=server.block_manager,
|
1605
|
-
job_manager=server.job_manager,
|
1606
|
-
passage_manager=server.passage_manager,
|
1607
|
-
actor=actor,
|
1608
|
-
step_manager=server.step_manager,
|
1609
|
-
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1610
|
-
# summarizer settings to be added here
|
1611
|
-
summarizer_mode=(
|
1612
|
-
SummarizationMode.STATIC_MESSAGE_BUFFER
|
1613
|
-
if agent.agent_type == AgentType.voice_convo_agent
|
1614
|
-
else SummarizationMode.PARTIAL_EVICT_MESSAGE_BUFFER
|
1615
|
-
),
|
1616
|
-
)
|
1617
|
-
|
1524
|
+
agent_loop = AgentLoop.load(agent_state=agent, actor=actor)
|
1618
1525
|
result = await agent_loop.step(
|
1619
1526
|
messages,
|
1620
1527
|
max_steps=max_steps,
|
@@ -1702,8 +1609,8 @@ async def send_message_async(
|
|
1702
1609
|
)
|
1703
1610
|
run = await server.job_manager.create_job_async(pydantic_job=run, actor=actor)
|
1704
1611
|
|
1705
|
-
# Create asyncio task for background processing
|
1706
|
-
task =
|
1612
|
+
# Create asyncio task for background processing (shielded to prevent cancellation)
|
1613
|
+
task = safe_create_shielded_task(
|
1707
1614
|
_process_message_background(
|
1708
1615
|
run_id=run.id,
|
1709
1616
|
server=server,
|
@@ -1715,28 +1622,20 @@ async def send_message_async(
|
|
1715
1622
|
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
1716
1623
|
max_steps=request.max_steps,
|
1717
1624
|
include_return_message_types=request.include_return_message_types,
|
1718
|
-
)
|
1625
|
+
),
|
1626
|
+
label=f"process_message_background_{run.id}",
|
1719
1627
|
)
|
1720
1628
|
|
1721
1629
|
def handle_task_completion(t):
|
1722
1630
|
try:
|
1723
1631
|
t.result()
|
1724
1632
|
except asyncio.CancelledError:
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
job_id=run.id,
|
1729
|
-
job_update=JobUpdate(
|
1730
|
-
status=JobStatus.failed,
|
1731
|
-
completed_at=datetime.now(timezone.utc),
|
1732
|
-
metadata={"error": "Task was cancelled"},
|
1733
|
-
),
|
1734
|
-
actor=actor,
|
1735
|
-
)
|
1736
|
-
)
|
1633
|
+
# Note: With shielded tasks, cancellation attempts don't actually stop the task
|
1634
|
+
logger.info(f"Cancellation attempted on shielded background task for run {run.id}, but task continues running")
|
1635
|
+
# Don't mark as failed since the shielded task is still running
|
1737
1636
|
except Exception as e:
|
1738
1637
|
logger.error(f"Unhandled exception in background task for run {run.id}: {e}")
|
1739
|
-
|
1638
|
+
safe_create_task(
|
1740
1639
|
server.job_manager.update_job_by_id_async(
|
1741
1640
|
job_id=run.id,
|
1742
1641
|
job_update=JobUpdate(
|
@@ -1745,7 +1644,8 @@ async def send_message_async(
|
|
1745
1644
|
metadata={"error": str(e)},
|
1746
1645
|
),
|
1747
1646
|
actor=actor,
|
1748
|
-
)
|
1647
|
+
),
|
1648
|
+
label=f"update_failed_job_{run.id}",
|
1749
1649
|
)
|
1750
1650
|
|
1751
1651
|
task.add_done_callback(handle_task_completion)
|
@@ -1816,38 +1716,10 @@ async def preview_raw_payload(
|
|
1816
1716
|
]
|
1817
1717
|
|
1818
1718
|
if agent_eligible and model_compatible:
|
1819
|
-
|
1820
|
-
|
1821
|
-
raise HTTPException(
|
1822
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
1823
|
-
detail="Payload inspection is not supported for agents with sleeptime enabled.",
|
1824
|
-
)
|
1825
|
-
else:
|
1826
|
-
agent_loop = LettaAgent(
|
1827
|
-
agent_id=agent_id,
|
1828
|
-
message_manager=server.message_manager,
|
1829
|
-
agent_manager=server.agent_manager,
|
1830
|
-
block_manager=server.block_manager,
|
1831
|
-
job_manager=server.job_manager,
|
1832
|
-
passage_manager=server.passage_manager,
|
1833
|
-
actor=actor,
|
1834
|
-
step_manager=server.step_manager,
|
1835
|
-
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1836
|
-
summarizer_mode=(
|
1837
|
-
SummarizationMode.STATIC_MESSAGE_BUFFER
|
1838
|
-
if agent.agent_type == AgentType.voice_convo_agent
|
1839
|
-
else SummarizationMode.PARTIAL_EVICT_MESSAGE_BUFFER
|
1840
|
-
),
|
1841
|
-
)
|
1842
|
-
|
1843
|
-
# TODO: Support step_streaming
|
1844
|
-
return await agent_loop.step(
|
1719
|
+
agent_loop = AgentLoop.load(agent_state=agent, actor=actor)
|
1720
|
+
return await agent_loop.build_request(
|
1845
1721
|
input_messages=request.messages,
|
1846
|
-
use_assistant_message=request.use_assistant_message,
|
1847
|
-
include_return_message_types=request.include_return_message_types,
|
1848
|
-
dry_run=True,
|
1849
1722
|
)
|
1850
|
-
|
1851
1723
|
else:
|
1852
1724
|
raise HTTPException(
|
1853
1725
|
status_code=status.HTTP_403_FORBIDDEN,
|
@@ -1888,19 +1760,14 @@ async def summarize_agent_conversation(
|
|
1888
1760
|
]
|
1889
1761
|
|
1890
1762
|
if agent_eligible and model_compatible:
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
actor=actor,
|
1899
|
-
step_manager=server.step_manager,
|
1900
|
-
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1901
|
-
message_buffer_min=max_message_length,
|
1763
|
+
agent_loop = LettaAgentV2(agent_state=agent, actor=actor)
|
1764
|
+
in_context_messages = await server.message_manager.get_messages_by_ids_async(message_ids=agent.message_ids, actor=actor)
|
1765
|
+
await agent_loop.summarize_conversation_history(
|
1766
|
+
in_context_messages=in_context_messages,
|
1767
|
+
new_letta_messages=[],
|
1768
|
+
total_tokens=None,
|
1769
|
+
force=True,
|
1902
1770
|
)
|
1903
|
-
await agent.summarize_conversation_history()
|
1904
1771
|
# Summarization completed, return 204 No Content
|
1905
1772
|
else:
|
1906
1773
|
raise HTTPException(
|
@@ -327,7 +327,7 @@ async def upload_file_to_folder(
|
|
327
327
|
logger=logger,
|
328
328
|
label="file_processor.process",
|
329
329
|
)
|
330
|
-
safe_create_task(sleeptime_document_ingest_async(server, folder_id, actor),
|
330
|
+
safe_create_task(sleeptime_document_ingest_async(server, folder_id, actor), label="sleeptime_document_ingest_async")
|
331
331
|
|
332
332
|
return file_metadata
|
333
333
|
|
@@ -467,7 +467,7 @@ async def delete_file_from_folder(
|
|
467
467
|
logger.info(f"Deleting file {file_id} from pinecone index")
|
468
468
|
await delete_file_records_from_pinecone_index(file_id=file_id, actor=actor)
|
469
469
|
|
470
|
-
|
470
|
+
safe_create_task(sleeptime_document_ingest_async(server, folder_id, actor, clear_history=True), label="document_ingest_after_delete")
|
471
471
|
if deleted_file is None:
|
472
472
|
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found.")
|
473
473
|
|
@@ -325,7 +325,7 @@ async def upload_file_to_source(
|
|
325
325
|
logger=logger,
|
326
326
|
label="file_processor.process",
|
327
327
|
)
|
328
|
-
safe_create_task(sleeptime_document_ingest_async(server, source_id, actor),
|
328
|
+
safe_create_task(sleeptime_document_ingest_async(server, source_id, actor), label="sleeptime_document_ingest_async")
|
329
329
|
|
330
330
|
return file_metadata
|
331
331
|
|
@@ -452,7 +452,7 @@ async def delete_file_from_source(
|
|
452
452
|
logger.info(f"Deleting file {file_id} from pinecone index")
|
453
453
|
await delete_file_records_from_pinecone_index(file_id=file_id, actor=actor)
|
454
454
|
|
455
|
-
|
455
|
+
safe_create_task(sleeptime_document_ingest_async(server, source_id, actor, clear_history=True), label="document_ingest_after_delete")
|
456
456
|
if deleted_file is None:
|
457
457
|
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found.")
|
458
458
|
|
@@ -19,6 +19,7 @@ from letta.schemas.user import User
|
|
19
19
|
from letta.server.rest_api.utils import capture_sentry_exception
|
20
20
|
from letta.services.job_manager import JobManager
|
21
21
|
from letta.settings import settings
|
22
|
+
from letta.utils import safe_create_task
|
22
23
|
|
23
24
|
logger = get_logger(__name__)
|
24
25
|
|
@@ -64,7 +65,7 @@ async def add_keepalive_to_stream(
|
|
64
65
|
await queue.put(("end", None))
|
65
66
|
|
66
67
|
# Start the stream reader task
|
67
|
-
reader_task =
|
68
|
+
reader_task = safe_create_task(stream_reader(), label="stream_reader")
|
68
69
|
|
69
70
|
try:
|
70
71
|
while True:
|
letta/server/server.py
CHANGED
@@ -109,7 +109,7 @@ from letta.services.tool_manager import ToolManager
|
|
109
109
|
from letta.services.user_manager import UserManager
|
110
110
|
from letta.settings import DatabaseChoice, model_settings, settings, tool_settings
|
111
111
|
from letta.streaming_interface import AgentChunkStreamingInterface
|
112
|
-
from letta.utils import get_friendly_error_msg, get_persona_text, make_key
|
112
|
+
from letta.utils import get_friendly_error_msg, get_persona_text, make_key, safe_create_task
|
113
113
|
|
114
114
|
config = LettaConfig.load()
|
115
115
|
logger = get_logger(__name__)
|
@@ -2248,7 +2248,7 @@ class SyncServer(Server):
|
|
2248
2248
|
|
2249
2249
|
# Offload the synchronous message_func to a separate thread
|
2250
2250
|
streaming_interface.stream_start()
|
2251
|
-
task =
|
2251
|
+
task = safe_create_task(
|
2252
2252
|
asyncio.to_thread(
|
2253
2253
|
self.send_messages,
|
2254
2254
|
actor=actor,
|
@@ -2256,7 +2256,8 @@ class SyncServer(Server):
|
|
2256
2256
|
input_messages=input_messages,
|
2257
2257
|
interface=streaming_interface,
|
2258
2258
|
metadata=metadata,
|
2259
|
-
)
|
2259
|
+
),
|
2260
|
+
label="send_messages_thread",
|
2260
2261
|
)
|
2261
2262
|
|
2262
2263
|
if stream_steps:
|
@@ -2363,13 +2364,14 @@ class SyncServer(Server):
|
|
2363
2364
|
streaming_interface.metadata = metadata
|
2364
2365
|
|
2365
2366
|
streaming_interface.stream_start()
|
2366
|
-
task =
|
2367
|
+
task = safe_create_task(
|
2367
2368
|
asyncio.to_thread(
|
2368
2369
|
letta_multi_agent.step,
|
2369
2370
|
input_messages=input_messages,
|
2370
2371
|
chaining=self.chaining,
|
2371
2372
|
max_chaining_steps=self.max_chaining_steps,
|
2372
|
-
)
|
2373
|
+
),
|
2374
|
+
label="multi_agent_step_thread",
|
2373
2375
|
)
|
2374
2376
|
|
2375
2377
|
if stream_steps:
|
@@ -53,7 +53,7 @@ from letta.services.message_manager import MessageManager
|
|
53
53
|
from letta.services.source_manager import SourceManager
|
54
54
|
from letta.services.tool_manager import ToolManager
|
55
55
|
from letta.settings import settings
|
56
|
-
from letta.utils import get_latest_alembic_revision
|
56
|
+
from letta.utils import get_latest_alembic_revision, safe_create_task
|
57
57
|
|
58
58
|
logger = get_logger(__name__)
|
59
59
|
|
@@ -622,10 +622,11 @@ class AgentSerializationManager:
|
|
622
622
|
|
623
623
|
# Create background task for file processing
|
624
624
|
# TODO: This can be moved to celery or RQ or something
|
625
|
-
task =
|
625
|
+
task = safe_create_task(
|
626
626
|
self._process_file_async(
|
627
627
|
file_metadata=file_metadata, source_id=source_db_id, file_processor=file_processor, actor=actor
|
628
|
-
)
|
628
|
+
),
|
629
|
+
label=f"process_file_{file_metadata.file_name}",
|
629
630
|
)
|
630
631
|
background_tasks.append(task)
|
631
632
|
logger.info(f"Started background processing for file {file_metadata.file_name} (ID: {file_db_id})")
|
letta/services/mcp_manager.py
CHANGED
@@ -43,7 +43,7 @@ from letta.services.mcp.stdio_client import AsyncStdioMCPClient
|
|
43
43
|
from letta.services.mcp.streamable_http_client import AsyncStreamableHTTPMCPClient
|
44
44
|
from letta.services.tool_manager import ToolManager
|
45
45
|
from letta.settings import tool_settings
|
46
|
-
from letta.utils import enforce_types, printd
|
46
|
+
from letta.utils import enforce_types, printd, safe_create_task
|
47
47
|
|
48
48
|
logger = get_logger(__name__)
|
49
49
|
|
@@ -869,7 +869,7 @@ class MCPManager:
|
|
869
869
|
|
870
870
|
# Run connect_to_server in background to avoid blocking
|
871
871
|
# This will trigger the OAuth flow and the redirect_handler will save the authorization URL to database
|
872
|
-
connect_task =
|
872
|
+
connect_task = safe_create_task(temp_client.connect_to_server(), label="mcp_oauth_connect")
|
873
873
|
|
874
874
|
# Give the OAuth flow time to trigger and save the URL
|
875
875
|
await asyncio.sleep(1.0)
|
@@ -20,6 +20,7 @@ from letta.services.message_manager import MessageManager
|
|
20
20
|
from letta.services.summarizer.enums import SummarizationMode
|
21
21
|
from letta.system import package_summarize_message_no_counts
|
22
22
|
from letta.templates.template_helper import render_template
|
23
|
+
from letta.utils import safe_create_task
|
23
24
|
|
24
25
|
logger = get_logger(__name__)
|
25
26
|
|
@@ -100,7 +101,7 @@ class Summarizer:
|
|
100
101
|
return in_context_messages, False
|
101
102
|
|
102
103
|
def fire_and_forget(self, coro):
|
103
|
-
task =
|
104
|
+
task = safe_create_task(coro, label="summarizer_background_task")
|
104
105
|
|
105
106
|
def callback(t):
|
106
107
|
try:
|
@@ -13,6 +13,7 @@ from letta.schemas.tool_execution_result import ToolExecutionResult
|
|
13
13
|
from letta.schemas.user import User
|
14
14
|
from letta.services.tool_executor.tool_executor_base import ToolExecutor
|
15
15
|
from letta.settings import settings
|
16
|
+
from letta.utils import safe_create_task
|
16
17
|
|
17
18
|
logger = get_logger(__name__)
|
18
19
|
|
@@ -55,7 +56,8 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
|
|
55
56
|
f"{message}"
|
56
57
|
)
|
57
58
|
|
58
|
-
|
59
|
+
other_agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=other_agent_id, actor=self.actor)
|
60
|
+
return str(await self._process_agent(agent_state=other_agent_state, message=augmented_message))
|
59
61
|
|
60
62
|
async def send_message_to_agents_matching_tags_async(
|
61
63
|
self, agent_state: AgentState, message: str, match_all: List[str], match_some: List[str]
|
@@ -75,22 +77,20 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
|
|
75
77
|
)
|
76
78
|
|
77
79
|
tasks = [
|
78
|
-
|
80
|
+
safe_create_task(
|
81
|
+
self._process_agent(agent_state=agent_state, message=augmented_message), label=f"process_agent_{agent_state.id}"
|
82
|
+
)
|
83
|
+
for agent_state in matching_agents
|
79
84
|
]
|
80
85
|
results = await asyncio.gather(*tasks)
|
81
86
|
return str(results)
|
82
87
|
|
83
|
-
async def _process_agent(self,
|
84
|
-
from letta.agents.
|
88
|
+
async def _process_agent(self, agent_state: AgentState, message: str) -> Dict[str, Any]:
|
89
|
+
from letta.agents.letta_agent_v2 import LettaAgentV2
|
85
90
|
|
86
91
|
try:
|
87
|
-
letta_agent =
|
88
|
-
|
89
|
-
message_manager=self.message_manager,
|
90
|
-
agent_manager=self.agent_manager,
|
91
|
-
block_manager=self.block_manager,
|
92
|
-
job_manager=self.job_manager,
|
93
|
-
passage_manager=self.passage_manager,
|
92
|
+
letta_agent = LettaAgentV2(
|
93
|
+
agent_state=agent_state,
|
94
94
|
actor=self.actor,
|
95
95
|
)
|
96
96
|
|
@@ -100,13 +100,13 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
|
|
100
100
|
send_message_content = [message.content for message in messages if isinstance(message, AssistantMessage)]
|
101
101
|
|
102
102
|
return {
|
103
|
-
"agent_id":
|
103
|
+
"agent_id": agent_state.id,
|
104
104
|
"response": send_message_content if send_message_content else ["<no response>"],
|
105
105
|
}
|
106
106
|
|
107
107
|
except Exception as e:
|
108
108
|
return {
|
109
|
-
"agent_id":
|
109
|
+
"agent_id": agent_state.id,
|
110
110
|
"error": str(e),
|
111
111
|
"type": type(e).__name__,
|
112
112
|
}
|
@@ -123,7 +123,10 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
|
|
123
123
|
f"{message}"
|
124
124
|
)
|
125
125
|
|
126
|
-
|
126
|
+
other_agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=other_agent_id, actor=self.actor)
|
127
|
+
task = safe_create_task(
|
128
|
+
self._process_agent(agent_state=other_agent_state, message=prefixed), label=f"send_message_to_{other_agent_id}"
|
129
|
+
)
|
127
130
|
|
128
131
|
task.add_done_callback(lambda t: (logger.error(f"Async send_message task failed: {t.exception()}") if t.exception() else None))
|
129
132
|
|
@@ -23,7 +23,7 @@ from letta.services.helpers.tool_execution_helper import (
|
|
23
23
|
from letta.services.helpers.tool_parser_helper import parse_stdout_best_effort
|
24
24
|
from letta.services.tool_sandbox.base import AsyncToolSandboxBase
|
25
25
|
from letta.settings import tool_settings
|
26
|
-
from letta.utils import get_friendly_error_msg, parse_stderr_error_msg
|
26
|
+
from letta.utils import get_friendly_error_msg, parse_stderr_error_msg, safe_create_task
|
27
27
|
|
28
28
|
logger = get_logger(__name__)
|
29
29
|
|
@@ -89,7 +89,7 @@ class AsyncToolSandboxLocal(AsyncToolSandboxBase):
|
|
89
89
|
venv_preparation_task = None
|
90
90
|
if use_venv:
|
91
91
|
venv_path = str(os.path.join(sandbox_dir, local_configs.venv_name))
|
92
|
-
venv_preparation_task =
|
92
|
+
venv_preparation_task = safe_create_task(self._prepare_venv(local_configs, venv_path, env), label="prepare_venv")
|
93
93
|
|
94
94
|
# Generate and write execution script (always with markers, since we rely on stdout)
|
95
95
|
code = await self.generate_execution_script(agent_state=agent_state, wrap_print_with_markers=True)
|
@@ -16,6 +16,7 @@ from letta.log import get_logger
|
|
16
16
|
from letta.schemas.tool import ToolUpdate
|
17
17
|
from letta.services.tool_manager import ToolManager
|
18
18
|
from letta.services.tool_sandbox.modal_constants import CACHE_TTL_SECONDS, DEFAULT_CONFIG_KEY, MODAL_DEPLOYMENTS_KEY
|
19
|
+
from letta.utils import safe_create_task
|
19
20
|
|
20
21
|
logger = get_logger(__name__)
|
21
22
|
|
@@ -197,7 +198,7 @@ class ModalVersionManager:
|
|
197
198
|
if deployment_key in self._deployments_in_progress:
|
198
199
|
self._deployments_in_progress[deployment_key].set()
|
199
200
|
# Clean up after a short delay to allow waiters to wake up
|
200
|
-
|
201
|
+
safe_create_task(self._cleanup_deployment_marker(deployment_key), label=f"cleanup_deployment_{deployment_key}")
|
201
202
|
|
202
203
|
async def _cleanup_deployment_marker(self, deployment_key: str):
|
203
204
|
"""Clean up deployment marker after a delay."""
|