letta-nightly 0.11.4.dev20250826104242__py3-none-any.whl → 0.11.6.dev20250827050912__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letta/__init__.py +1 -1
- letta/agent.py +9 -3
- letta/agents/base_agent.py +2 -2
- letta/agents/letta_agent.py +56 -45
- letta/agents/voice_agent.py +2 -2
- letta/data_sources/redis_client.py +146 -1
- letta/errors.py +4 -0
- letta/functions/function_sets/files.py +2 -2
- letta/functions/mcp_client/types.py +30 -6
- letta/functions/schema_generator.py +46 -1
- letta/functions/schema_validator.py +17 -2
- letta/functions/types.py +1 -1
- letta/helpers/tool_execution_helper.py +0 -2
- letta/llm_api/anthropic_client.py +27 -5
- letta/llm_api/deepseek_client.py +97 -0
- letta/llm_api/groq_client.py +79 -0
- letta/llm_api/helpers.py +0 -1
- letta/llm_api/llm_api_tools.py +2 -113
- letta/llm_api/llm_client.py +21 -0
- letta/llm_api/llm_client_base.py +11 -9
- letta/llm_api/openai_client.py +3 -0
- letta/llm_api/xai_client.py +85 -0
- letta/prompts/prompt_generator.py +190 -0
- letta/schemas/agent_file.py +17 -2
- letta/schemas/file.py +24 -1
- letta/schemas/job.py +2 -0
- letta/schemas/letta_message.py +2 -0
- letta/schemas/letta_request.py +22 -0
- letta/schemas/message.py +10 -1
- letta/schemas/providers/bedrock.py +1 -0
- letta/schemas/response_format.py +2 -2
- letta/server/generate_openapi_schema.sh +4 -4
- letta/server/rest_api/redis_stream_manager.py +300 -0
- letta/server/rest_api/routers/v1/agents.py +129 -7
- letta/server/rest_api/routers/v1/folders.py +15 -5
- letta/server/rest_api/routers/v1/runs.py +101 -11
- letta/server/rest_api/routers/v1/sources.py +21 -53
- letta/server/rest_api/routers/v1/telemetry.py +14 -4
- letta/server/rest_api/routers/v1/tools.py +2 -2
- letta/server/rest_api/streaming_response.py +3 -24
- letta/server/server.py +0 -1
- letta/services/agent_manager.py +2 -2
- letta/services/agent_serialization_manager.py +129 -32
- letta/services/file_manager.py +111 -6
- letta/services/file_processor/file_processor.py +5 -2
- letta/services/files_agents_manager.py +60 -0
- letta/services/helpers/agent_manager_helper.py +6 -207
- letta/services/helpers/tool_parser_helper.py +6 -3
- letta/services/llm_batch_manager.py +1 -1
- letta/services/mcp/base_client.py +7 -1
- letta/services/mcp/sse_client.py +7 -2
- letta/services/mcp/stdio_client.py +5 -0
- letta/services/mcp/streamable_http_client.py +11 -2
- letta/services/mcp_manager.py +31 -30
- letta/services/source_manager.py +26 -1
- letta/services/summarizer/summarizer.py +21 -10
- letta/services/tool_executor/files_tool_executor.py +13 -9
- letta/services/tool_executor/mcp_tool_executor.py +3 -0
- letta/services/tool_executor/tool_execution_manager.py +13 -0
- letta/services/tool_executor/tool_execution_sandbox.py +0 -1
- letta/services/tool_manager.py +43 -20
- letta/services/tool_sandbox/local_sandbox.py +0 -2
- letta/settings.py +1 -0
- letta/utils.py +37 -0
- {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info}/METADATA +116 -102
- {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info}/RECORD +128 -127
- {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info}/WHEEL +1 -1
- letta_nightly-0.11.6.dev20250827050912.dist-info/entry_points.txt +2 -0
- letta/functions/mcp_client/__init__.py +0 -0
- letta/functions/mcp_client/base_client.py +0 -156
- letta/functions/mcp_client/sse_client.py +0 -51
- letta/functions/mcp_client/stdio_client.py +0 -109
- letta_nightly-0.11.4.dev20250826104242.dist-info/entry_points.txt +0 -3
- {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info/licenses}/LICENSE +0 -0
@@ -1,16 +1,23 @@
|
|
1
|
+
from datetime import timedelta
|
1
2
|
from typing import Annotated, List, Optional
|
2
3
|
|
3
|
-
from fastapi import APIRouter, Depends, Header, HTTPException, Query
|
4
|
+
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
|
4
5
|
from pydantic import Field
|
5
6
|
|
7
|
+
from letta.data_sources.redis_client import NoopAsyncRedisClient, get_redis_client
|
8
|
+
from letta.helpers.datetime_helpers import get_utc_time
|
6
9
|
from letta.orm.errors import NoResultFound
|
7
10
|
from letta.schemas.enums import JobStatus, JobType, MessageRole
|
8
11
|
from letta.schemas.letta_message import LettaMessageUnion
|
12
|
+
from letta.schemas.letta_request import RetrieveStreamRequest
|
9
13
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
10
14
|
from letta.schemas.run import Run
|
11
15
|
from letta.schemas.step import Step
|
16
|
+
from letta.server.rest_api.redis_stream_manager import redis_sse_stream_generator
|
17
|
+
from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode, add_keepalive_to_stream
|
12
18
|
from letta.server.rest_api.utils import get_letta_server
|
13
19
|
from letta.server.server import SyncServer
|
20
|
+
from letta.settings import settings
|
14
21
|
|
15
22
|
router = APIRouter(prefix="/runs", tags=["runs"])
|
16
23
|
|
@@ -19,6 +26,14 @@ router = APIRouter(prefix="/runs", tags=["runs"])
|
|
19
26
|
def list_runs(
|
20
27
|
server: "SyncServer" = Depends(get_letta_server),
|
21
28
|
agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
|
29
|
+
background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
|
30
|
+
after: Optional[str] = Query(None, description="Cursor for pagination"),
|
31
|
+
before: Optional[str] = Query(None, description="Cursor for pagination"),
|
32
|
+
limit: Optional[int] = Query(50, description="Maximum number of runs to return"),
|
33
|
+
ascending: bool = Query(
|
34
|
+
False,
|
35
|
+
description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default)",
|
36
|
+
),
|
22
37
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
23
38
|
):
|
24
39
|
"""
|
@@ -26,18 +41,29 @@ def list_runs(
|
|
26
41
|
"""
|
27
42
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
28
43
|
|
29
|
-
runs = [
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
44
|
+
runs = [
|
45
|
+
Run.from_job(job)
|
46
|
+
for job in server.job_manager.list_jobs(
|
47
|
+
actor=actor,
|
48
|
+
job_type=JobType.RUN,
|
49
|
+
limit=limit,
|
50
|
+
before=before,
|
51
|
+
after=after,
|
52
|
+
ascending=False,
|
53
|
+
)
|
54
|
+
]
|
55
|
+
if agent_ids:
|
56
|
+
runs = [run for run in runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
|
57
|
+
if background is not None:
|
58
|
+
runs = [run for run in runs if "background" in run.metadata and run.metadata["background"] == background]
|
59
|
+
return runs
|
35
60
|
|
36
61
|
|
37
62
|
@router.get("/active", response_model=List[Run], operation_id="list_active_runs")
|
38
63
|
def list_active_runs(
|
39
64
|
server: "SyncServer" = Depends(get_letta_server),
|
40
65
|
agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
|
66
|
+
background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
|
41
67
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
42
68
|
):
|
43
69
|
"""
|
@@ -46,13 +72,15 @@ def list_active_runs(
|
|
46
72
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
47
73
|
|
48
74
|
active_runs = server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running], job_type=JobType.RUN)
|
49
|
-
|
50
75
|
active_runs = [Run.from_job(job) for job in active_runs]
|
51
76
|
|
52
|
-
if
|
53
|
-
|
77
|
+
if agent_ids:
|
78
|
+
active_runs = [run for run in active_runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
|
79
|
+
|
80
|
+
if background is not None:
|
81
|
+
active_runs = [run for run in active_runs if "background" in run.metadata and run.metadata["background"] == background]
|
54
82
|
|
55
|
-
return
|
83
|
+
return active_runs
|
56
84
|
|
57
85
|
|
58
86
|
@router.get("/{run_id}", response_model=Run, operation_id="retrieve_run")
|
@@ -213,3 +241,65 @@ async def delete_run(
|
|
213
241
|
return Run.from_job(job)
|
214
242
|
except NoResultFound:
|
215
243
|
raise HTTPException(status_code=404, detail="Run not found")
|
244
|
+
|
245
|
+
|
246
|
+
@router.post(
|
247
|
+
"/{run_id}/stream",
|
248
|
+
response_model=None,
|
249
|
+
operation_id="retrieve_stream",
|
250
|
+
responses={
|
251
|
+
200: {
|
252
|
+
"description": "Successful response",
|
253
|
+
"content": {
|
254
|
+
"text/event-stream": {"description": "Server-Sent Events stream"},
|
255
|
+
},
|
256
|
+
}
|
257
|
+
},
|
258
|
+
)
|
259
|
+
async def retrieve_stream(
|
260
|
+
run_id: str,
|
261
|
+
request: RetrieveStreamRequest = Body(None),
|
262
|
+
actor_id: Optional[str] = Header(None, alias="user_id"),
|
263
|
+
server: "SyncServer" = Depends(get_letta_server),
|
264
|
+
):
|
265
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
266
|
+
try:
|
267
|
+
job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
|
268
|
+
except NoResultFound:
|
269
|
+
raise HTTPException(status_code=404, detail="Run not found")
|
270
|
+
|
271
|
+
run = Run.from_job(job)
|
272
|
+
|
273
|
+
if "background" not in run.metadata or not run.metadata["background"]:
|
274
|
+
raise HTTPException(status_code=400, detail="Run was not created in background mode, so it cannot be retrieved.")
|
275
|
+
|
276
|
+
if run.created_at < get_utc_time() - timedelta(hours=3):
|
277
|
+
raise HTTPException(status_code=410, detail="Run was created more than 3 hours ago, and is now expired.")
|
278
|
+
|
279
|
+
redis_client = await get_redis_client()
|
280
|
+
|
281
|
+
if isinstance(redis_client, NoopAsyncRedisClient):
|
282
|
+
raise HTTPException(
|
283
|
+
status_code=503,
|
284
|
+
detail=(
|
285
|
+
"Background streaming requires Redis to be running. "
|
286
|
+
"Please ensure Redis is properly configured. "
|
287
|
+
f"LETTA_REDIS_HOST: {settings.redis_host}, LETTA_REDIS_PORT: {settings.redis_port}"
|
288
|
+
),
|
289
|
+
)
|
290
|
+
|
291
|
+
stream = redis_sse_stream_generator(
|
292
|
+
redis_client=redis_client,
|
293
|
+
run_id=run_id,
|
294
|
+
starting_after=request.starting_after,
|
295
|
+
poll_interval=request.poll_interval,
|
296
|
+
batch_size=request.batch_size,
|
297
|
+
)
|
298
|
+
|
299
|
+
if request.include_pings and settings.enable_keepalive:
|
300
|
+
stream = add_keepalive_to_stream(stream, keepalive_interval=settings.keepalive_interval)
|
301
|
+
|
302
|
+
return StreamingResponseWithStatusCode(
|
303
|
+
stream,
|
304
|
+
media_type="text/event-stream",
|
305
|
+
)
|
@@ -2,18 +2,17 @@ import asyncio
|
|
2
2
|
import mimetypes
|
3
3
|
import os
|
4
4
|
import tempfile
|
5
|
-
from datetime import datetime, timedelta, timezone
|
6
5
|
from pathlib import Path
|
7
6
|
from typing import List, Optional
|
8
7
|
|
9
8
|
from fastapi import APIRouter, Depends, Header, HTTPException, Query, UploadFile
|
10
9
|
from starlette import status
|
10
|
+
from starlette.responses import Response
|
11
11
|
|
12
12
|
import letta.constants as constants
|
13
13
|
from letta.helpers.pinecone_utils import (
|
14
14
|
delete_file_records_from_pinecone_index,
|
15
15
|
delete_source_records_from_pinecone_index,
|
16
|
-
list_pinecone_index_for_files,
|
17
16
|
should_use_pinecone,
|
18
17
|
)
|
19
18
|
from letta.log import get_logger
|
@@ -35,14 +34,13 @@ from letta.services.file_processor.file_types import get_allowed_media_types, ge
|
|
35
34
|
from letta.services.file_processor.parser.markitdown_parser import MarkitdownFileParser
|
36
35
|
from letta.services.file_processor.parser.mistral_parser import MistralFileParser
|
37
36
|
from letta.settings import settings
|
38
|
-
from letta.utils import safe_create_task, sanitize_filename
|
37
|
+
from letta.utils import safe_create_file_processing_task, safe_create_task, sanitize_filename
|
39
38
|
|
40
39
|
logger = get_logger(__name__)
|
41
40
|
|
42
41
|
# Register all supported file types with Python's mimetypes module
|
43
42
|
register_mime_types()
|
44
43
|
|
45
|
-
|
46
44
|
router = APIRouter(prefix="/sources", tags=["sources"])
|
47
45
|
|
48
46
|
|
@@ -139,8 +137,11 @@ async def create_source(
|
|
139
137
|
# TODO: need to asyncify this
|
140
138
|
if not source_create.embedding_config:
|
141
139
|
if not source_create.embedding:
|
142
|
-
|
143
|
-
|
140
|
+
if settings.default_embedding_handle is None:
|
141
|
+
# TODO: modify error type
|
142
|
+
raise ValueError("Must specify either embedding or embedding_config in request")
|
143
|
+
else:
|
144
|
+
source_create.embedding = settings.default_embedding_handle
|
144
145
|
source_create.embedding_config = await server.get_embedding_config_from_handle_async(
|
145
146
|
handle=source_create.embedding,
|
146
147
|
embedding_chunk_size=source_create.embedding_chunk_size or constants.DEFAULT_EMBEDDING_CHUNK_SIZE,
|
@@ -258,7 +259,9 @@ async def upload_file_to_source(
|
|
258
259
|
|
259
260
|
# Store original filename and handle duplicate logic
|
260
261
|
# Use custom name if provided, otherwise use the uploaded file's name
|
261
|
-
|
262
|
+
# If custom name is provided, use it directly (it's just metadata, not a filesystem path)
|
263
|
+
# Otherwise, sanitize the uploaded filename for security
|
264
|
+
original_filename = name if name else sanitize_filename(file.filename) # Basic sanitization only
|
262
265
|
|
263
266
|
# Check if duplicate exists
|
264
267
|
existing_file = await server.file_manager.get_file_by_original_name_and_source(
|
@@ -307,8 +310,11 @@ async def upload_file_to_source(
|
|
307
310
|
|
308
311
|
# Use cloud processing for all files (simple files always, complex files with Mistral key)
|
309
312
|
logger.info("Running experimental cloud based file processing...")
|
310
|
-
|
313
|
+
safe_create_file_processing_task(
|
311
314
|
load_file_to_source_cloud(server, agent_states, content, source_id, actor, source.embedding_config, file_metadata),
|
315
|
+
file_metadata=file_metadata,
|
316
|
+
server=server,
|
317
|
+
actor=actor,
|
312
318
|
logger=logger,
|
313
319
|
label="file_processor.process",
|
314
320
|
)
|
@@ -358,6 +364,10 @@ async def list_source_files(
|
|
358
364
|
limit: int = Query(1000, description="Number of files to return"),
|
359
365
|
after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"),
|
360
366
|
include_content: bool = Query(False, description="Whether to include full file content"),
|
367
|
+
check_status_updates: bool = Query(
|
368
|
+
True,
|
369
|
+
description="Whether to check and update file processing status (from the vector db service). If False, will not fetch and update the status, which may lead to performance gains.",
|
370
|
+
),
|
361
371
|
server: "SyncServer" = Depends(get_letta_server),
|
362
372
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
363
373
|
):
|
@@ -372,6 +382,7 @@ async def list_source_files(
|
|
372
382
|
actor=actor,
|
373
383
|
include_content=include_content,
|
374
384
|
strip_directory_prefix=True, # TODO: Reconsider this. This is purely for aesthetics.
|
385
|
+
check_status_updates=check_status_updates,
|
375
386
|
)
|
376
387
|
|
377
388
|
|
@@ -400,51 +411,8 @@ async def get_file_metadata(
|
|
400
411
|
if file_metadata.source_id != source_id:
|
401
412
|
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found in source {source_id}.")
|
402
413
|
|
403
|
-
# Check
|
404
|
-
|
405
|
-
if file_metadata.created_at:
|
406
|
-
# Handle timezone differences between PostgreSQL (timezone-aware) and SQLite (timezone-naive)
|
407
|
-
if settings.letta_pg_uri_no_default:
|
408
|
-
# PostgreSQL: both datetimes are timezone-aware
|
409
|
-
timeout_threshold = datetime.now(timezone.utc) - timedelta(minutes=settings.file_processing_timeout_minutes)
|
410
|
-
file_created_at = file_metadata.created_at
|
411
|
-
else:
|
412
|
-
# SQLite: both datetimes should be timezone-naive
|
413
|
-
timeout_threshold = datetime.utcnow() - timedelta(minutes=settings.file_processing_timeout_minutes)
|
414
|
-
file_created_at = file_metadata.created_at
|
415
|
-
|
416
|
-
if file_created_at < timeout_threshold:
|
417
|
-
# Move file to error status with timeout message
|
418
|
-
timeout_message = settings.file_processing_timeout_error_message.format(settings.file_processing_timeout_minutes)
|
419
|
-
try:
|
420
|
-
file_metadata = await server.file_manager.update_file_status(
|
421
|
-
file_id=file_metadata.id, actor=actor, processing_status=FileProcessingStatus.ERROR, error_message=timeout_message
|
422
|
-
)
|
423
|
-
except ValueError as e:
|
424
|
-
# state transition was blocked - log it but don't fail the request
|
425
|
-
logger.warning(f"Could not update file to timeout error state: {str(e)}")
|
426
|
-
# continue with existing file_metadata
|
427
|
-
|
428
|
-
if should_use_pinecone() and file_metadata.processing_status == FileProcessingStatus.EMBEDDING:
|
429
|
-
ids = await list_pinecone_index_for_files(file_id=file_id, actor=actor)
|
430
|
-
logger.info(
|
431
|
-
f"Embedded chunks {len(ids)}/{file_metadata.total_chunks} for {file_id} ({file_metadata.file_name}) in organization {actor.organization_id}"
|
432
|
-
)
|
433
|
-
|
434
|
-
if len(ids) != file_metadata.chunks_embedded or len(ids) == file_metadata.total_chunks:
|
435
|
-
if len(ids) != file_metadata.total_chunks:
|
436
|
-
file_status = file_metadata.processing_status
|
437
|
-
else:
|
438
|
-
file_status = FileProcessingStatus.COMPLETED
|
439
|
-
try:
|
440
|
-
file_metadata = await server.file_manager.update_file_status(
|
441
|
-
file_id=file_metadata.id, actor=actor, chunks_embedded=len(ids), processing_status=file_status
|
442
|
-
)
|
443
|
-
except ValueError as e:
|
444
|
-
# state transition was blocked - this is a race condition
|
445
|
-
# log it but don't fail the request since we're just reading metadata
|
446
|
-
logger.warning(f"Race condition detected in get_file_metadata: {str(e)}")
|
447
|
-
# return the current file state without updating
|
414
|
+
# Check and update file status (timeout check and pinecone embedding sync)
|
415
|
+
file_metadata = await server.file_manager.check_and_update_file_status(file_metadata, actor)
|
448
416
|
|
449
417
|
return file_metadata
|
450
418
|
|
@@ -1,18 +1,28 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
1
3
|
from fastapi import APIRouter, Depends, Header
|
2
4
|
|
3
5
|
from letta.schemas.provider_trace import ProviderTrace
|
4
6
|
from letta.server.rest_api.utils import get_letta_server
|
5
7
|
from letta.server.server import SyncServer
|
8
|
+
from letta.settings import settings
|
6
9
|
|
7
10
|
router = APIRouter(prefix="/telemetry", tags=["telemetry"])
|
8
11
|
|
9
12
|
|
10
|
-
@router.get("/{step_id}", response_model=ProviderTrace, operation_id="retrieve_provider_trace")
|
13
|
+
@router.get("/{step_id}", response_model=Optional[ProviderTrace], operation_id="retrieve_provider_trace")
|
11
14
|
async def retrieve_provider_trace_by_step_id(
|
12
15
|
step_id: str,
|
13
16
|
server: SyncServer = Depends(get_letta_server),
|
14
17
|
actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
15
18
|
):
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
provider_trace = None
|
20
|
+
if settings.track_provider_trace:
|
21
|
+
try:
|
22
|
+
provider_trace = await server.telemetry_manager.get_provider_trace_by_step_id_async(
|
23
|
+
step_id=step_id, actor=await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
24
|
+
)
|
25
|
+
except:
|
26
|
+
pass
|
27
|
+
|
28
|
+
return provider_trace
|
@@ -547,7 +547,7 @@ async def add_mcp_server_to_config(
|
|
547
547
|
server_name=request.server_name,
|
548
548
|
server_type=request.type,
|
549
549
|
server_url=request.server_url,
|
550
|
-
token=request.resolve_token()
|
550
|
+
token=request.resolve_token(),
|
551
551
|
custom_headers=request.custom_headers,
|
552
552
|
)
|
553
553
|
elif isinstance(request, StreamableHTTPServerConfig):
|
@@ -555,7 +555,7 @@ async def add_mcp_server_to_config(
|
|
555
555
|
server_name=request.server_name,
|
556
556
|
server_type=request.type,
|
557
557
|
server_url=request.server_url,
|
558
|
-
token=request.resolve_token()
|
558
|
+
token=request.resolve_token(),
|
559
559
|
custom_headers=request.custom_headers,
|
560
560
|
)
|
561
561
|
|
@@ -10,6 +10,7 @@ import anyio
|
|
10
10
|
from fastapi.responses import StreamingResponse
|
11
11
|
from starlette.types import Send
|
12
12
|
|
13
|
+
from letta.errors import LettaUnexpectedStreamCancellationError
|
13
14
|
from letta.log import get_logger
|
14
15
|
from letta.schemas.enums import JobStatus
|
15
16
|
from letta.schemas.letta_ping import LettaPing
|
@@ -288,33 +289,11 @@ class StreamingResponseWithStatusCode(StreamingResponse):
|
|
288
289
|
|
289
290
|
# Handle client timeouts (should throw error to inform user)
|
290
291
|
except asyncio.CancelledError as exc:
|
291
|
-
logger.warning("Stream was
|
292
|
+
logger.warning("Stream was terminated due to unexpected cancellation from server")
|
292
293
|
# Handle unexpected cancellation with error
|
293
294
|
more_body = False
|
294
|
-
error_resp = {"error": {"message": "Request was unexpectedly cancelled (likely due to client timeout or disconnection)"}}
|
295
|
-
error_event = f"event: error\ndata: {json.dumps(error_resp)}\n\n".encode(self.charset)
|
296
|
-
if not self.response_started:
|
297
|
-
await send(
|
298
|
-
{
|
299
|
-
"type": "http.response.start",
|
300
|
-
"status": 408, # Request Timeout
|
301
|
-
"headers": self.raw_headers,
|
302
|
-
}
|
303
|
-
)
|
304
|
-
raise
|
305
|
-
if self._client_connected:
|
306
|
-
try:
|
307
|
-
await send(
|
308
|
-
{
|
309
|
-
"type": "http.response.body",
|
310
|
-
"body": error_event,
|
311
|
-
"more_body": more_body,
|
312
|
-
}
|
313
|
-
)
|
314
|
-
except anyio.ClosedResourceError:
|
315
|
-
self._client_connected = False
|
316
295
|
capture_sentry_exception(exc)
|
317
|
-
|
296
|
+
raise LettaUnexpectedStreamCancellationError("Stream was terminated due to unexpected cancellation from server")
|
318
297
|
|
319
298
|
except Exception as exc:
|
320
299
|
logger.exception("Unhandled Streaming Error")
|
letta/server/server.py
CHANGED
@@ -2068,7 +2068,6 @@ class SyncServer(Server):
|
|
2068
2068
|
raise ValueError(f"No client was created for MCP server: {mcp_server_name}")
|
2069
2069
|
|
2070
2070
|
tools = await self.mcp_clients[mcp_server_name].list_tools()
|
2071
|
-
|
2072
2071
|
# Add health information to each tool
|
2073
2072
|
for tool in tools:
|
2074
2073
|
if tool.inputSchema:
|
letta/services/agent_manager.py
CHANGED
@@ -42,6 +42,7 @@ from letta.orm.sandbox_config import AgentEnvironmentVariable
|
|
42
42
|
from letta.orm.sandbox_config import AgentEnvironmentVariable as AgentEnvironmentVariableModel
|
43
43
|
from letta.orm.sqlalchemy_base import AccessType
|
44
44
|
from letta.otel.tracing import trace_method
|
45
|
+
from letta.prompts.prompt_generator import PromptGenerator
|
45
46
|
from letta.schemas.agent import AgentState as PydanticAgentState
|
46
47
|
from letta.schemas.agent import AgentType, CreateAgent, UpdateAgent, get_prompt_template_for_agent_type
|
47
48
|
from letta.schemas.block import DEFAULT_BLOCKS
|
@@ -89,7 +90,6 @@ from letta.services.helpers.agent_manager_helper import (
|
|
89
90
|
check_supports_structured_output,
|
90
91
|
compile_system_message,
|
91
92
|
derive_system_message,
|
92
|
-
get_system_message_from_compiled_memory,
|
93
93
|
initialize_message_sequence,
|
94
94
|
initialize_message_sequence_async,
|
95
95
|
package_initial_message_sequence,
|
@@ -1783,7 +1783,7 @@ class AgentManager:
|
|
1783
1783
|
|
1784
1784
|
# update memory (TODO: potentially update recall/archival stats separately)
|
1785
1785
|
|
1786
|
-
new_system_message_str = get_system_message_from_compiled_memory(
|
1786
|
+
new_system_message_str = PromptGenerator.get_system_message_from_compiled_memory(
|
1787
1787
|
system_prompt=agent_state.system,
|
1788
1788
|
memory_with_sources=curr_memory_str,
|
1789
1789
|
in_context_memory_last_edit=memory_edit_timestamp,
|