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.
Files changed (74) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +9 -3
  3. letta/agents/base_agent.py +2 -2
  4. letta/agents/letta_agent.py +56 -45
  5. letta/agents/voice_agent.py +2 -2
  6. letta/data_sources/redis_client.py +146 -1
  7. letta/errors.py +4 -0
  8. letta/functions/function_sets/files.py +2 -2
  9. letta/functions/mcp_client/types.py +30 -6
  10. letta/functions/schema_generator.py +46 -1
  11. letta/functions/schema_validator.py +17 -2
  12. letta/functions/types.py +1 -1
  13. letta/helpers/tool_execution_helper.py +0 -2
  14. letta/llm_api/anthropic_client.py +27 -5
  15. letta/llm_api/deepseek_client.py +97 -0
  16. letta/llm_api/groq_client.py +79 -0
  17. letta/llm_api/helpers.py +0 -1
  18. letta/llm_api/llm_api_tools.py +2 -113
  19. letta/llm_api/llm_client.py +21 -0
  20. letta/llm_api/llm_client_base.py +11 -9
  21. letta/llm_api/openai_client.py +3 -0
  22. letta/llm_api/xai_client.py +85 -0
  23. letta/prompts/prompt_generator.py +190 -0
  24. letta/schemas/agent_file.py +17 -2
  25. letta/schemas/file.py +24 -1
  26. letta/schemas/job.py +2 -0
  27. letta/schemas/letta_message.py +2 -0
  28. letta/schemas/letta_request.py +22 -0
  29. letta/schemas/message.py +10 -1
  30. letta/schemas/providers/bedrock.py +1 -0
  31. letta/schemas/response_format.py +2 -2
  32. letta/server/generate_openapi_schema.sh +4 -4
  33. letta/server/rest_api/redis_stream_manager.py +300 -0
  34. letta/server/rest_api/routers/v1/agents.py +129 -7
  35. letta/server/rest_api/routers/v1/folders.py +15 -5
  36. letta/server/rest_api/routers/v1/runs.py +101 -11
  37. letta/server/rest_api/routers/v1/sources.py +21 -53
  38. letta/server/rest_api/routers/v1/telemetry.py +14 -4
  39. letta/server/rest_api/routers/v1/tools.py +2 -2
  40. letta/server/rest_api/streaming_response.py +3 -24
  41. letta/server/server.py +0 -1
  42. letta/services/agent_manager.py +2 -2
  43. letta/services/agent_serialization_manager.py +129 -32
  44. letta/services/file_manager.py +111 -6
  45. letta/services/file_processor/file_processor.py +5 -2
  46. letta/services/files_agents_manager.py +60 -0
  47. letta/services/helpers/agent_manager_helper.py +6 -207
  48. letta/services/helpers/tool_parser_helper.py +6 -3
  49. letta/services/llm_batch_manager.py +1 -1
  50. letta/services/mcp/base_client.py +7 -1
  51. letta/services/mcp/sse_client.py +7 -2
  52. letta/services/mcp/stdio_client.py +5 -0
  53. letta/services/mcp/streamable_http_client.py +11 -2
  54. letta/services/mcp_manager.py +31 -30
  55. letta/services/source_manager.py +26 -1
  56. letta/services/summarizer/summarizer.py +21 -10
  57. letta/services/tool_executor/files_tool_executor.py +13 -9
  58. letta/services/tool_executor/mcp_tool_executor.py +3 -0
  59. letta/services/tool_executor/tool_execution_manager.py +13 -0
  60. letta/services/tool_executor/tool_execution_sandbox.py +0 -1
  61. letta/services/tool_manager.py +43 -20
  62. letta/services/tool_sandbox/local_sandbox.py +0 -2
  63. letta/settings.py +1 -0
  64. letta/utils.py +37 -0
  65. {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info}/METADATA +116 -102
  66. {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info}/RECORD +128 -127
  67. {letta_nightly-0.11.4.dev20250826104242.dist-info → letta_nightly-0.11.6.dev20250827050912.dist-info}/WHEEL +1 -1
  68. letta_nightly-0.11.6.dev20250827050912.dist-info/entry_points.txt +2 -0
  69. letta/functions/mcp_client/__init__.py +0 -0
  70. letta/functions/mcp_client/base_client.py +0 -156
  71. letta/functions/mcp_client/sse_client.py +0 -51
  72. letta/functions/mcp_client/stdio_client.py +0 -109
  73. letta_nightly-0.11.4.dev20250826104242.dist-info/entry_points.txt +0 -3
  74. {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 = [Run.from_job(job) for job in server.job_manager.list_jobs(actor=actor, job_type=JobType.RUN)]
30
-
31
- if not agent_ids:
32
- return runs
33
-
34
- return [run for run in runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
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 not agent_ids:
53
- return active_runs
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 [run for run in active_runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
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
- # TODO: modify error type
143
- raise ValueError("Must specify either embedding or embedding_config in request")
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
- original_filename = sanitize_filename(name if name else file.filename) # Basic sanitization only
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
- safe_create_task(
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 for timeout if status is not terminal
404
- if not file_metadata.processing_status.is_terminal_state():
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
- return await server.telemetry_manager.get_provider_trace_by_step_id_async(
17
- step_id=step_id, actor=await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
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() if not request.custom_headers else None,
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() if not request.custom_headers else None,
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 cancelled due to client timeout or unexpected disconnection")
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
- return
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:
@@ -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,