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.

Files changed (159) hide show
  1. letta/__init__.py +2 -3
  2. letta/adapters/letta_llm_adapter.py +1 -0
  3. letta/adapters/simple_llm_request_adapter.py +8 -5
  4. letta/adapters/simple_llm_stream_adapter.py +22 -6
  5. letta/agents/agent_loop.py +10 -3
  6. letta/agents/base_agent.py +4 -1
  7. letta/agents/helpers.py +41 -9
  8. letta/agents/letta_agent.py +11 -10
  9. letta/agents/letta_agent_v2.py +47 -37
  10. letta/agents/letta_agent_v3.py +395 -300
  11. letta/agents/voice_agent.py +8 -6
  12. letta/agents/voice_sleeptime_agent.py +3 -3
  13. letta/constants.py +30 -7
  14. letta/errors.py +20 -0
  15. letta/functions/function_sets/base.py +55 -3
  16. letta/functions/mcp_client/types.py +33 -57
  17. letta/functions/schema_generator.py +135 -23
  18. letta/groups/sleeptime_multi_agent_v3.py +6 -11
  19. letta/groups/sleeptime_multi_agent_v4.py +227 -0
  20. letta/helpers/converters.py +78 -4
  21. letta/helpers/crypto_utils.py +6 -2
  22. letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
  23. letta/interfaces/anthropic_streaming_interface.py +3 -4
  24. letta/interfaces/gemini_streaming_interface.py +4 -6
  25. letta/interfaces/openai_streaming_interface.py +63 -28
  26. letta/llm_api/anthropic_client.py +7 -4
  27. letta/llm_api/deepseek_client.py +6 -4
  28. letta/llm_api/google_ai_client.py +3 -12
  29. letta/llm_api/google_vertex_client.py +1 -1
  30. letta/llm_api/helpers.py +90 -61
  31. letta/llm_api/llm_api_tools.py +4 -1
  32. letta/llm_api/openai.py +12 -12
  33. letta/llm_api/openai_client.py +53 -16
  34. letta/local_llm/constants.py +4 -3
  35. letta/local_llm/json_parser.py +5 -2
  36. letta/local_llm/utils.py +2 -3
  37. letta/log.py +171 -7
  38. letta/orm/agent.py +43 -9
  39. letta/orm/archive.py +4 -0
  40. letta/orm/custom_columns.py +15 -0
  41. letta/orm/identity.py +11 -11
  42. letta/orm/mcp_server.py +9 -0
  43. letta/orm/message.py +6 -1
  44. letta/orm/run_metrics.py +7 -2
  45. letta/orm/sqlalchemy_base.py +2 -2
  46. letta/orm/tool.py +3 -0
  47. letta/otel/tracing.py +2 -0
  48. letta/prompts/prompt_generator.py +7 -2
  49. letta/schemas/agent.py +41 -10
  50. letta/schemas/agent_file.py +3 -0
  51. letta/schemas/archive.py +4 -2
  52. letta/schemas/block.py +2 -1
  53. letta/schemas/enums.py +36 -3
  54. letta/schemas/file.py +3 -3
  55. letta/schemas/folder.py +2 -1
  56. letta/schemas/group.py +2 -1
  57. letta/schemas/identity.py +18 -9
  58. letta/schemas/job.py +3 -1
  59. letta/schemas/letta_message.py +71 -12
  60. letta/schemas/letta_request.py +7 -3
  61. letta/schemas/letta_stop_reason.py +0 -25
  62. letta/schemas/llm_config.py +8 -2
  63. letta/schemas/mcp.py +80 -83
  64. letta/schemas/mcp_server.py +349 -0
  65. letta/schemas/memory.py +20 -8
  66. letta/schemas/message.py +212 -67
  67. letta/schemas/providers/anthropic.py +13 -6
  68. letta/schemas/providers/azure.py +6 -4
  69. letta/schemas/providers/base.py +8 -4
  70. letta/schemas/providers/bedrock.py +6 -2
  71. letta/schemas/providers/cerebras.py +7 -3
  72. letta/schemas/providers/deepseek.py +2 -1
  73. letta/schemas/providers/google_gemini.py +15 -6
  74. letta/schemas/providers/groq.py +2 -1
  75. letta/schemas/providers/lmstudio.py +9 -6
  76. letta/schemas/providers/mistral.py +2 -1
  77. letta/schemas/providers/openai.py +7 -2
  78. letta/schemas/providers/together.py +9 -3
  79. letta/schemas/providers/xai.py +7 -3
  80. letta/schemas/run.py +7 -2
  81. letta/schemas/run_metrics.py +2 -1
  82. letta/schemas/sandbox_config.py +2 -2
  83. letta/schemas/secret.py +3 -158
  84. letta/schemas/source.py +2 -2
  85. letta/schemas/step.py +2 -2
  86. letta/schemas/tool.py +24 -1
  87. letta/schemas/usage.py +0 -1
  88. letta/server/rest_api/app.py +123 -7
  89. letta/server/rest_api/dependencies.py +3 -0
  90. letta/server/rest_api/interface.py +7 -4
  91. letta/server/rest_api/redis_stream_manager.py +16 -1
  92. letta/server/rest_api/routers/v1/__init__.py +7 -0
  93. letta/server/rest_api/routers/v1/agents.py +332 -322
  94. letta/server/rest_api/routers/v1/archives.py +127 -40
  95. letta/server/rest_api/routers/v1/blocks.py +54 -6
  96. letta/server/rest_api/routers/v1/chat_completions.py +146 -0
  97. letta/server/rest_api/routers/v1/folders.py +27 -35
  98. letta/server/rest_api/routers/v1/groups.py +23 -35
  99. letta/server/rest_api/routers/v1/identities.py +24 -10
  100. letta/server/rest_api/routers/v1/internal_runs.py +107 -0
  101. letta/server/rest_api/routers/v1/internal_templates.py +162 -179
  102. letta/server/rest_api/routers/v1/jobs.py +15 -27
  103. letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
  104. letta/server/rest_api/routers/v1/messages.py +23 -34
  105. letta/server/rest_api/routers/v1/organizations.py +6 -27
  106. letta/server/rest_api/routers/v1/providers.py +35 -62
  107. letta/server/rest_api/routers/v1/runs.py +30 -43
  108. letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
  109. letta/server/rest_api/routers/v1/sources.py +26 -42
  110. letta/server/rest_api/routers/v1/steps.py +16 -29
  111. letta/server/rest_api/routers/v1/tools.py +17 -13
  112. letta/server/rest_api/routers/v1/users.py +5 -17
  113. letta/server/rest_api/routers/v1/voice.py +18 -27
  114. letta/server/rest_api/streaming_response.py +5 -2
  115. letta/server/rest_api/utils.py +187 -25
  116. letta/server/server.py +27 -22
  117. letta/server/ws_api/server.py +5 -4
  118. letta/services/agent_manager.py +148 -26
  119. letta/services/agent_serialization_manager.py +6 -1
  120. letta/services/archive_manager.py +168 -15
  121. letta/services/block_manager.py +14 -4
  122. letta/services/file_manager.py +33 -29
  123. letta/services/group_manager.py +10 -0
  124. letta/services/helpers/agent_manager_helper.py +65 -11
  125. letta/services/identity_manager.py +105 -4
  126. letta/services/job_manager.py +11 -1
  127. letta/services/mcp/base_client.py +2 -2
  128. letta/services/mcp/oauth_utils.py +33 -8
  129. letta/services/mcp_manager.py +174 -78
  130. letta/services/mcp_server_manager.py +1331 -0
  131. letta/services/message_manager.py +109 -4
  132. letta/services/organization_manager.py +4 -4
  133. letta/services/passage_manager.py +9 -25
  134. letta/services/provider_manager.py +91 -15
  135. letta/services/run_manager.py +72 -15
  136. letta/services/sandbox_config_manager.py +45 -3
  137. letta/services/source_manager.py +15 -8
  138. letta/services/step_manager.py +24 -1
  139. letta/services/streaming_service.py +581 -0
  140. letta/services/summarizer/summarizer.py +1 -1
  141. letta/services/tool_executor/core_tool_executor.py +111 -0
  142. letta/services/tool_executor/files_tool_executor.py +5 -3
  143. letta/services/tool_executor/sandbox_tool_executor.py +2 -2
  144. letta/services/tool_executor/tool_execution_manager.py +1 -1
  145. letta/services/tool_manager.py +10 -3
  146. letta/services/tool_sandbox/base.py +61 -1
  147. letta/services/tool_sandbox/local_sandbox.py +1 -3
  148. letta/services/user_manager.py +2 -2
  149. letta/settings.py +49 -5
  150. letta/system.py +14 -5
  151. letta/utils.py +73 -1
  152. letta/validators.py +105 -0
  153. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/METADATA +4 -2
  154. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/RECORD +157 -151
  155. letta/schemas/letta_ping.py +0 -28
  156. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  157. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/WHEEL +0 -0
  158. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/entry_points.txt +0 -0
  159. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/licenses/LICENSE +0 -0
@@ -11,6 +11,7 @@ from typing import Optional
11
11
 
12
12
  import uvicorn
13
13
  from fastapi import FastAPI, Request
14
+ from fastapi.exceptions import RequestValidationError
14
15
  from fastapi.responses import JSONResponse
15
16
  from marshmallow import ValidationError
16
17
  from sqlalchemy.exc import IntegrityError, OperationalError
@@ -26,29 +27,33 @@ from letta.errors import (
26
27
  AgentNotFoundForExportError,
27
28
  BedrockPermissionError,
28
29
  LettaAgentNotFoundError,
30
+ LettaExpiredError,
29
31
  LettaInvalidArgumentError,
30
32
  LettaInvalidMCPSchemaError,
31
33
  LettaMCPConnectionError,
32
34
  LettaMCPTimeoutError,
35
+ LettaServiceUnavailableError,
33
36
  LettaToolCreateError,
34
37
  LettaToolNameConflictError,
38
+ LettaUnsupportedFileUploadError,
35
39
  LettaUserNotFoundError,
36
40
  LLMAuthenticationError,
37
41
  LLMError,
38
42
  LLMRateLimitError,
39
43
  LLMTimeoutError,
44
+ PendingApprovalError,
40
45
  )
41
46
  from letta.helpers.pinecone_utils import get_pinecone_indices, should_use_pinecone, upsert_pinecone_indices
42
47
  from letta.jobs.scheduler import start_scheduler_with_leader_election
43
48
  from letta.log import get_logger
44
49
  from letta.orm.errors import DatabaseTimeoutError, ForeignKeyConstraintViolationError, NoResultFound, UniqueConstraintViolationError
45
- from letta.schemas.letta_message import create_letta_message_union_schema
50
+ from letta.otel.tracing import get_trace_id
51
+ from letta.schemas.letta_message import create_letta_message_union_schema, create_letta_ping_schema
46
52
  from letta.schemas.letta_message_content import (
47
53
  create_letta_assistant_message_content_union_schema,
48
54
  create_letta_message_content_union_schema,
49
55
  create_letta_user_message_content_union_schema,
50
56
  )
51
- from letta.schemas.letta_ping import create_letta_ping_schema
52
57
  from letta.server.constants import REST_DEFAULT_PORT
53
58
  from letta.server.db import db_registry
54
59
 
@@ -63,6 +68,7 @@ from letta.server.rest_api.static_files import mount_static_files
63
68
  from letta.server.rest_api.utils import SENTRY_ENABLED
64
69
  from letta.server.server import SyncServer
65
70
  from letta.settings import settings, telemetry_settings
71
+ from letta.validators import PATH_VALIDATORS, PRIMITIVE_ID_PATTERNS
66
72
 
67
73
  if SENTRY_ENABLED:
68
74
  import sentry_sdk
@@ -197,7 +203,49 @@ def create_application() -> "FastAPI":
197
203
  "continuous_profiling_auto_start": True,
198
204
  },
199
205
  )
200
- logger.info("Sentry enabled.")
206
+
207
+ if telemetry_settings.enable_datadog:
208
+ try:
209
+ dd_env = settings.environment or "development"
210
+ print(f"▶ Initializing Datadog profiling (env={dd_env})")
211
+
212
+ # Configure environment variables before importing ddtrace (must be set in environment before importing ddtrace)
213
+ os.environ.setdefault("DD_ENV", dd_env)
214
+ os.environ.setdefault("DD_SERVICE", telemetry_settings.datadog_service_name)
215
+ os.environ.setdefault("DD_VERSION", letta_version)
216
+ os.environ.setdefault("DD_AGENT_HOST", telemetry_settings.datadog_agent_host)
217
+ os.environ.setdefault("DD_TRACE_AGENT_PORT", str(telemetry_settings.datadog_agent_port))
218
+ os.environ.setdefault("DD_PROFILING_ENABLED", "true")
219
+ os.environ.setdefault("DD_PROFILING_MEMORY_ENABLED", str(telemetry_settings.datadog_profiling_memory_enabled).lower())
220
+ os.environ.setdefault("DD_PROFILING_HEAP_ENABLED", str(telemetry_settings.datadog_profiling_heap_enabled).lower())
221
+
222
+ from ddtrace.profiling import Profiler
223
+
224
+ # Initialize and start profiler
225
+ profiler = Profiler(
226
+ env=dd_env,
227
+ service=telemetry_settings.datadog_service_name,
228
+ version=letta_version,
229
+ )
230
+ profiler.start()
231
+
232
+ # Log Git metadata for source code integration
233
+ git_info = ""
234
+ if telemetry_settings.datadog_git_commit_sha:
235
+ git_info = f", commit={telemetry_settings.datadog_git_commit_sha[:8]}"
236
+ if telemetry_settings.datadog_git_repository_url:
237
+ git_info += f", repo={telemetry_settings.datadog_git_repository_url}"
238
+
239
+ logger.info(
240
+ f"Datadog profiling enabled: env={dd_env}, "
241
+ f"service={telemetry_settings.datadog_service_name}, "
242
+ f"agent={telemetry_settings.datadog_agent_host}:{telemetry_settings.datadog_agent_port}{git_info}"
243
+ )
244
+ except Exception as e:
245
+ logger.error(f"Failed to initialize Datadog profiling: {e}", exc_info=True)
246
+ if SENTRY_ENABLED:
247
+ sentry_sdk.capture_exception(e)
248
+ # Don't fail application startup if Datadog initialization fails
201
249
 
202
250
  debug_mode = "--debug" in sys.argv
203
251
  app = FastAPI(
@@ -228,10 +276,67 @@ def create_application() -> "FastAPI":
228
276
  },
229
277
  )
230
278
 
279
+ # Reasoning for this handler is the default path validation logic returns a pretty gnarly error message
280
+ # because of the uuid4 pattern. This handler rewrites the error message to be more user-friendly and less intimidating.
281
+ @app.exception_handler(RequestValidationError)
282
+ async def custom_request_validation_handler(request: Request, exc: RequestValidationError):
283
+ """Generalize path `_id` validation messages and include example IDs.
284
+
285
+ - Rewrites string pattern/length mismatches to "primitive-{uuid4}"
286
+ - Preserves stringified `detail` and includes `trace_id`
287
+ - Adds top-level `examples` from `PATH_VALIDATORS` for offending params
288
+ """
289
+ errors = exc.errors()
290
+ examples_set: set[str] = set()
291
+ content = {"trace_id": get_trace_id() or ""}
292
+ for err in errors:
293
+ fastapi_error_loc = err.get("loc", [])
294
+ # only rewrite path param validation errors (should expand in future)
295
+ if len(fastapi_error_loc) != 2 or fastapi_error_loc[0] != "path":
296
+ continue
297
+
298
+ # re-write the error message
299
+ parameter_name = fastapi_error_loc[1]
300
+ err_type = err.get("type")
301
+ if (
302
+ err_type in {"string_pattern_mismatch", "string_too_short", "string_too_long"}
303
+ and isinstance(parameter_name, str)
304
+ and parameter_name.endswith("_id")
305
+ ):
306
+ primitive = parameter_name[:-3]
307
+ validator = PATH_VALIDATORS.get(primitive)
308
+ if validator:
309
+ # simplify default error message
310
+ err["msg"] = f"String should match pattern '{primitive}-{{uuid4}}'"
311
+
312
+ # rewrite as string_pattern_mismatch even if the input length is too short or too long (more intuitive for user)
313
+ if err_type in {"string_too_short", "string_too_long"}:
314
+ # FYI: the pattern is the same as the pattern inthe validator object but for some reason the validator object
315
+ # doesn't let you access it directly (unless you call into pydantic layer)
316
+ err["ctx"] = {"pattern": PRIMITIVE_ID_PATTERNS[primitive].pattern}
317
+ err["type"] = "string_pattern_mismatch"
318
+
319
+ # collect examples for top-level examples field (prevents duplicates and allows for multiple examples for multiple primitives)
320
+ # e.g. if there are 2 malformed agent ids, the examples field will contain 2 examples for the agent primitive
321
+ # e.g. if there is a malformed agent id and malformed folder id, the examples field will contain both examples, for both the agent and folder primitives
322
+ try:
323
+ exs = getattr(validator, "examples", None)
324
+ if exs:
325
+ for ex in exs:
326
+ examples_set.add(ex)
327
+ else:
328
+ examples_set.add(f"{primitive}-123e4567-e89b-42d3-8456-426614174000")
329
+ except Exception:
330
+ examples_set.add(f"{primitive}-123e4567-e89b-42d3-8456-426614174000")
331
+
332
+ # Preserve current API contract: stringified list of errors
333
+ content["detail"] = repr(errors)
334
+ if examples_set:
335
+ content["examples"] = sorted(examples_set)
336
+ return JSONResponse(status_code=422, content=content)
337
+
231
338
  async def error_handler_with_code(request: Request, exc: Exception, code: int, detail: str | None = None):
232
339
  logger.error(f"{type(exc).__name__}", exc_info=exc)
233
- if SENTRY_ENABLED:
234
- sentry_sdk.capture_exception(exc)
235
340
 
236
341
  if not detail:
237
342
  detail = str(exc)
@@ -246,6 +351,8 @@ def create_application() -> "FastAPI":
246
351
  _error_handler_404_user = partial(_error_handler_404, detail="User not found")
247
352
  _error_handler_408 = partial(error_handler_with_code, code=408)
248
353
  _error_handler_409 = partial(error_handler_with_code, code=409)
354
+ _error_handler_410 = partial(error_handler_with_code, code=410)
355
+ _error_handler_415 = partial(error_handler_with_code, code=415)
249
356
  _error_handler_422 = partial(error_handler_with_code, code=422)
250
357
  _error_handler_500 = partial(error_handler_with_code, code=500)
251
358
  _error_handler_503 = partial(error_handler_with_code, code=503)
@@ -263,6 +370,9 @@ def create_application() -> "FastAPI":
263
370
  app.add_exception_handler(LettaUserNotFoundError, _error_handler_404_user)
264
371
  app.add_exception_handler(AgentNotFoundForExportError, _error_handler_404)
265
372
 
373
+ # 410 Expired errors
374
+ app.add_exception_handler(LettaExpiredError, _error_handler_410)
375
+
266
376
  # 408 Timeout errors
267
377
  app.add_exception_handler(LettaMCPTimeoutError, _error_handler_408)
268
378
  app.add_exception_handler(LettaInvalidMCPSchemaError, _error_handler_400)
@@ -271,6 +381,10 @@ def create_application() -> "FastAPI":
271
381
  app.add_exception_handler(ForeignKeyConstraintViolationError, _error_handler_409)
272
382
  app.add_exception_handler(UniqueConstraintViolationError, _error_handler_409)
273
383
  app.add_exception_handler(IntegrityError, _error_handler_409)
384
+ app.add_exception_handler(PendingApprovalError, _error_handler_409)
385
+
386
+ # 415 Unsupported Media Type errors
387
+ app.add_exception_handler(LettaUnsupportedFileUploadError, _error_handler_415)
274
388
 
275
389
  # 422 Validation errors
276
390
  app.add_exception_handler(ValidationError, _error_handler_422)
@@ -281,6 +395,7 @@ def create_application() -> "FastAPI":
281
395
 
282
396
  # 503 Service Unavailable errors
283
397
  app.add_exception_handler(OperationalError, _error_handler_503)
398
+ app.add_exception_handler(LettaServiceUnavailableError, _error_handler_503)
284
399
 
285
400
  @app.exception_handler(IncompatibleAgentType)
286
401
  async def handle_incompatible_agent_type(request: Request, exc: IncompatibleAgentType):
@@ -311,8 +426,6 @@ def create_application() -> "FastAPI":
311
426
  @app.exception_handler(BedrockPermissionError)
312
427
  async def bedrock_permission_error_handler(request, exc: BedrockPermissionError):
313
428
  logger.error("Bedrock permission denied.")
314
- if SENTRY_ENABLED:
315
- sentry_sdk.capture_exception(exc)
316
429
 
317
430
  return JSONResponse(
318
431
  status_code=403,
@@ -440,6 +553,9 @@ def create_application() -> "FastAPI":
440
553
  logger.warning(f"Failed to setup SQLAlchemy instrumentation: {e}")
441
554
  # Don't fail startup if instrumentation fails
442
555
 
556
+ # Ensure our validation handler overrides tracing's handler when tracing is enabled
557
+ app.add_exception_handler(RequestValidationError, custom_request_validation_handler)
558
+
443
559
  for route in v1_routes:
444
560
  app.include_router(route, prefix=API_PREFIX)
445
561
  # this gives undocumented routes for "latest" and bare api calls.
@@ -20,6 +20,7 @@ class HeaderParams(BaseModel):
20
20
  actor_id: Optional[str] = None
21
21
  user_agent: Optional[str] = None
22
22
  project_id: Optional[str] = None
23
+ sdk_version: Optional[str] = None
23
24
  experimental_params: Optional[ExperimentalParams] = None
24
25
 
25
26
 
@@ -27,6 +28,7 @@ def get_headers(
27
28
  actor_id: Optional[str] = Header(None, alias="user_id"),
28
29
  user_agent: Optional[str] = Header(None, alias="User-Agent"),
29
30
  project_id: Optional[str] = Header(None, alias="X-Project-Id"),
31
+ sdk_version: Optional[str] = Header(None, alias="X-Stainless-Package-Version"),
30
32
  message_async: Optional[str] = Header(None, alias="X-Experimental-Message-Async"),
31
33
  letta_v1_agent: Optional[str] = Header(None, alias="X-Experimental-Letta-V1-Agent"),
32
34
  ) -> HeaderParams:
@@ -35,6 +37,7 @@ def get_headers(
35
37
  actor_id=actor_id,
36
38
  user_agent=user_agent,
37
39
  project_id=project_id,
40
+ sdk_version=sdk_version,
38
41
  experimental_params=ExperimentalParams(
39
42
  message_async=(message_async == "true") if message_async else None,
40
43
  letta_v1_agent=(letta_v1_agent == "true") if letta_v1_agent else None,
@@ -1,7 +1,10 @@
1
1
  import asyncio
2
2
  import json
3
3
  import queue
4
- import warnings
4
+
5
+ from letta.log import get_logger
6
+
7
+ logger = get_logger(__name__)
5
8
  from collections import deque
6
9
  from datetime import datetime
7
10
  from typing import AsyncGenerator, Literal, Optional, Union
@@ -503,7 +506,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
503
506
  data: {"function_return": "None", "status": "success", "date": "2024-02-29T06:07:50.847262+00:00"}
504
507
  """
505
508
  if not chunk.choices or len(chunk.choices) == 0:
506
- warnings.warn(f"No choices in chunk: {chunk}")
509
+ logger.warning(f"No choices in chunk: {chunk}")
507
510
  return None
508
511
 
509
512
  choice = chunk.choices[0]
@@ -1028,7 +1031,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
1028
1031
  # created=1713216662
1029
1032
  # model='gpt-4o-mini-2024-07-18'
1030
1033
  # object='chat.completion.chunk'
1031
- warnings.warn(f"Couldn't find delta in chunk: {chunk}")
1034
+ logger.warning(f"Couldn't find delta in chunk: {chunk}")
1032
1035
  return None
1033
1036
 
1034
1037
  return processed_chunk
@@ -1255,7 +1258,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
1255
1258
  try:
1256
1259
  func_args = parse_json(function_call.function.arguments)
1257
1260
  except:
1258
- warnings.warn(f"Failed to parse function arguments: {function_call.function.arguments}")
1261
+ logger.warning(f"Failed to parse function arguments: {function_call.function.arguments}")
1259
1262
  func_args = {}
1260
1263
 
1261
1264
  if (
@@ -213,6 +213,7 @@ async def create_background_stream_processor(
213
213
  run_manager: Optional run manager for updating run status
214
214
  actor: Optional actor for run status updates
215
215
  """
216
+ stop_reason = None
216
217
  if writer is None:
217
218
  writer = RedisSSEStreamWriter(redis_client)
218
219
  await writer.start()
@@ -232,6 +233,15 @@ async def create_background_stream_processor(
232
233
  if is_done:
233
234
  break
234
235
 
236
+ try:
237
+ # sorry for this
238
+ maybe_json_chunk = chunk.split("data: ")[1]
239
+ maybe_stop_reason = json.loads(maybe_json_chunk) if maybe_json_chunk and maybe_json_chunk[0] == "{" else None
240
+ if maybe_stop_reason and maybe_stop_reason.get("message_type") == "stop_reason":
241
+ stop_reason = maybe_stop_reason.get("stop_reason")
242
+ except:
243
+ pass
244
+
235
245
  except Exception as e:
236
246
  logger.error(f"Error processing stream for run {run_id}: {e}")
237
247
  # Write error chunk
@@ -251,9 +261,14 @@ async def create_background_stream_processor(
251
261
  if should_stop_writer:
252
262
  await writer.stop()
253
263
  if run_manager and actor:
264
+ if stop_reason == "cancelled":
265
+ run_status = RunStatus.cancelled
266
+ else:
267
+ run_status = RunStatus.completed
268
+
254
269
  await run_manager.update_run_by_id_async(
255
270
  run_id=run_id,
256
- update=RunUpdate(status=RunStatus.completed, stop_reason=StopReasonType.end_turn.value),
271
+ update=RunUpdate(status=run_status, stop_reason=stop_reason or StopReasonType.end_turn.value),
257
272
  actor=actor,
258
273
  )
259
274
 
@@ -1,14 +1,17 @@
1
1
  from letta.server.rest_api.routers.v1.agents import router as agents_router
2
2
  from letta.server.rest_api.routers.v1.archives import router as archives_router
3
3
  from letta.server.rest_api.routers.v1.blocks import router as blocks_router
4
+ from letta.server.rest_api.routers.v1.chat_completions import router as chat_completions_router, router as openai_chat_completions_router
4
5
  from letta.server.rest_api.routers.v1.embeddings import router as embeddings_router
5
6
  from letta.server.rest_api.routers.v1.folders import router as folders_router
6
7
  from letta.server.rest_api.routers.v1.groups import router as groups_router
7
8
  from letta.server.rest_api.routers.v1.health import router as health_router
8
9
  from letta.server.rest_api.routers.v1.identities import router as identities_router
10
+ from letta.server.rest_api.routers.v1.internal_runs import router as internal_runs_router
9
11
  from letta.server.rest_api.routers.v1.internal_templates import router as internal_templates_router
10
12
  from letta.server.rest_api.routers.v1.jobs import router as jobs_router
11
13
  from letta.server.rest_api.routers.v1.llms import router as llm_router
14
+ from letta.server.rest_api.routers.v1.mcp_servers import router as mcp_servers_router
12
15
  from letta.server.rest_api.routers.v1.messages import router as messages_router
13
16
  from letta.server.rest_api.routers.v1.providers import router as providers_router
14
17
  from letta.server.rest_api.routers.v1.runs import router as runs_router
@@ -26,10 +29,13 @@ ROUTERS = [
26
29
  sources_router,
27
30
  folders_router,
28
31
  agents_router,
32
+ chat_completions_router,
29
33
  groups_router,
30
34
  identities_router,
35
+ internal_runs_router,
31
36
  internal_templates_router,
32
37
  llm_router,
38
+ mcp_servers_router,
33
39
  blocks_router,
34
40
  jobs_router,
35
41
  health_router,
@@ -42,4 +48,5 @@ ROUTERS = [
42
48
  messages_router,
43
49
  voice_router,
44
50
  embeddings_router,
51
+ openai_chat_completions_router,
45
52
  ]