letta-nightly 0.7.20.dev20250520104253__py3-none-any.whl → 0.7.21.dev20250521233415__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 (67) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +290 -3
  3. letta/agents/base_agent.py +0 -55
  4. letta/agents/helpers.py +5 -0
  5. letta/agents/letta_agent.py +314 -64
  6. letta/agents/letta_agent_batch.py +102 -55
  7. letta/agents/voice_agent.py +5 -5
  8. letta/client/client.py +9 -18
  9. letta/constants.py +55 -1
  10. letta/functions/function_sets/builtin.py +27 -0
  11. letta/functions/mcp_client/stdio_client.py +1 -1
  12. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  13. letta/interfaces/anthropic_streaming_interface.py +10 -1
  14. letta/interfaces/openai_streaming_interface.py +9 -2
  15. letta/llm_api/anthropic.py +21 -2
  16. letta/llm_api/anthropic_client.py +33 -6
  17. letta/llm_api/google_ai_client.py +136 -423
  18. letta/llm_api/google_vertex_client.py +173 -22
  19. letta/llm_api/llm_api_tools.py +27 -0
  20. letta/llm_api/llm_client.py +1 -1
  21. letta/llm_api/llm_client_base.py +32 -21
  22. letta/llm_api/openai.py +57 -0
  23. letta/llm_api/openai_client.py +7 -11
  24. letta/memory.py +0 -1
  25. letta/orm/__init__.py +1 -0
  26. letta/orm/enums.py +1 -0
  27. letta/orm/provider_trace.py +26 -0
  28. letta/orm/step.py +1 -0
  29. letta/schemas/provider_trace.py +43 -0
  30. letta/schemas/providers.py +210 -65
  31. letta/schemas/step.py +1 -0
  32. letta/schemas/tool.py +4 -0
  33. letta/server/db.py +37 -19
  34. letta/server/rest_api/routers/v1/__init__.py +2 -0
  35. letta/server/rest_api/routers/v1/agents.py +57 -34
  36. letta/server/rest_api/routers/v1/blocks.py +3 -3
  37. letta/server/rest_api/routers/v1/identities.py +24 -26
  38. letta/server/rest_api/routers/v1/jobs.py +3 -3
  39. letta/server/rest_api/routers/v1/llms.py +13 -8
  40. letta/server/rest_api/routers/v1/sandbox_configs.py +6 -6
  41. letta/server/rest_api/routers/v1/tags.py +3 -3
  42. letta/server/rest_api/routers/v1/telemetry.py +18 -0
  43. letta/server/rest_api/routers/v1/tools.py +6 -6
  44. letta/server/rest_api/streaming_response.py +105 -0
  45. letta/server/rest_api/utils.py +4 -0
  46. letta/server/server.py +140 -0
  47. letta/services/agent_manager.py +251 -18
  48. letta/services/block_manager.py +52 -37
  49. letta/services/helpers/noop_helper.py +10 -0
  50. letta/services/identity_manager.py +43 -38
  51. letta/services/job_manager.py +29 -0
  52. letta/services/message_manager.py +111 -0
  53. letta/services/sandbox_config_manager.py +36 -0
  54. letta/services/step_manager.py +146 -0
  55. letta/services/telemetry_manager.py +58 -0
  56. letta/services/tool_executor/tool_execution_manager.py +49 -5
  57. letta/services/tool_executor/tool_execution_sandbox.py +47 -0
  58. letta/services/tool_executor/tool_executor.py +236 -7
  59. letta/services/tool_manager.py +160 -1
  60. letta/services/tool_sandbox/e2b_sandbox.py +65 -3
  61. letta/settings.py +10 -2
  62. letta/tracing.py +5 -5
  63. {letta_nightly-0.7.20.dev20250520104253.dist-info → letta_nightly-0.7.21.dev20250521233415.dist-info}/METADATA +3 -2
  64. {letta_nightly-0.7.20.dev20250520104253.dist-info → letta_nightly-0.7.21.dev20250521233415.dist-info}/RECORD +67 -60
  65. {letta_nightly-0.7.20.dev20250520104253.dist-info → letta_nightly-0.7.21.dev20250521233415.dist-info}/LICENSE +0 -0
  66. {letta_nightly-0.7.20.dev20250520104253.dist-info → letta_nightly-0.7.21.dev20250521233415.dist-info}/WHEEL +0 -0
  67. {letta_nightly-0.7.20.dev20250520104253.dist-info → letta_nightly-0.7.21.dev20250521233415.dist-info}/entry_points.txt +0 -0
@@ -33,6 +33,7 @@ from letta.schemas.user import User
33
33
  from letta.serialize_schemas.pydantic_agent_schema import AgentSchema
34
34
  from letta.server.rest_api.utils import get_letta_server
35
35
  from letta.server.server import SyncServer
36
+ from letta.services.telemetry_manager import NoopTelemetryManager
36
37
  from letta.settings import settings
37
38
 
38
39
  # These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
@@ -106,14 +107,15 @@ async def list_agents(
106
107
 
107
108
 
108
109
  @router.get("/count", response_model=int, operation_id="count_agents")
109
- def count_agents(
110
+ async def count_agents(
110
111
  server: SyncServer = Depends(get_letta_server),
111
112
  actor_id: Optional[str] = Header(None, alias="user_id"),
112
113
  ):
113
114
  """
114
115
  Get the count of all agents associated with a given user.
115
116
  """
116
- return server.agent_manager.size(actor=server.user_manager.get_user_or_default(user_id=actor_id))
117
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
118
+ return await server.agent_manager.size_async(actor=actor)
117
119
 
118
120
 
119
121
  class IndentedORJSONResponse(Response):
@@ -124,7 +126,7 @@ class IndentedORJSONResponse(Response):
124
126
 
125
127
 
126
128
  @router.get("/{agent_id}/export", response_class=IndentedORJSONResponse, operation_id="export_agent_serialized")
127
- def export_agent_serialized(
129
+ async def export_agent_serialized(
128
130
  agent_id: str,
129
131
  server: "SyncServer" = Depends(get_letta_server),
130
132
  actor_id: Optional[str] = Header(None, alias="user_id"),
@@ -135,7 +137,7 @@ def export_agent_serialized(
135
137
  """
136
138
  Export the serialized JSON representation of an agent, formatted with indentation.
137
139
  """
138
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
140
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
139
141
 
140
142
  try:
141
143
  agent = server.agent_manager.serialize(agent_id=agent_id, actor=actor)
@@ -200,7 +202,7 @@ async def import_agent_serialized(
200
202
 
201
203
 
202
204
  @router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window")
203
- def retrieve_agent_context_window(
205
+ async def retrieve_agent_context_window(
204
206
  agent_id: str,
205
207
  server: "SyncServer" = Depends(get_letta_server),
206
208
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -208,9 +210,12 @@ def retrieve_agent_context_window(
208
210
  """
209
211
  Retrieve the context window of a specific agent.
210
212
  """
211
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
212
-
213
- return server.get_agent_context_window(agent_id=agent_id, actor=actor)
213
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
214
+ try:
215
+ return await server.get_agent_context_window_async(agent_id=agent_id, actor=actor)
216
+ except Exception as e:
217
+ traceback.print_exc()
218
+ raise e
214
219
 
215
220
 
216
221
  class CreateAgentRequest(CreateAgent):
@@ -341,7 +346,7 @@ async def retrieve_agent(
341
346
  """
342
347
  Get the state of the agent.
343
348
  """
344
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
349
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
345
350
 
346
351
  try:
347
352
  return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
@@ -367,7 +372,7 @@ def delete_agent(
367
372
 
368
373
 
369
374
  @router.get("/{agent_id}/sources", response_model=List[Source], operation_id="list_agent_sources")
370
- def list_agent_sources(
375
+ async def list_agent_sources(
371
376
  agent_id: str,
372
377
  server: "SyncServer" = Depends(get_letta_server),
373
378
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -375,8 +380,8 @@ def list_agent_sources(
375
380
  """
376
381
  Get the sources associated with an agent.
377
382
  """
378
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
379
- return server.agent_manager.list_attached_sources(agent_id=agent_id, actor=actor)
383
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
384
+ return await server.agent_manager.list_attached_sources_async(agent_id=agent_id, actor=actor)
380
385
 
381
386
 
382
387
  # TODO: remove? can also get with agent blocks
@@ -424,14 +429,14 @@ async def list_blocks(
424
429
  """
425
430
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
426
431
  try:
427
- agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor)
432
+ agent = await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, include_relationships=["memory"], actor=actor)
428
433
  return agent.memory.blocks
429
434
  except NoResultFound as e:
430
435
  raise HTTPException(status_code=404, detail=str(e))
431
436
 
432
437
 
433
438
  @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
434
- def modify_block(
439
+ async def modify_block(
435
440
  agent_id: str,
436
441
  block_label: str,
437
442
  block_update: BlockUpdate = Body(...),
@@ -441,10 +446,11 @@ def modify_block(
441
446
  """
442
447
  Updates a core memory block of an agent.
443
448
  """
444
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
449
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
445
450
 
446
- block = server.agent_manager.get_block_with_label(agent_id=agent_id, block_label=block_label, actor=actor)
447
- block = server.block_manager.update_block(block.id, block_update=block_update, actor=actor)
451
+ block = await server.agent_manager.modify_block_by_label_async(
452
+ agent_id=agent_id, block_label=block_label, block_update=block_update, actor=actor
453
+ )
448
454
 
449
455
  # This should also trigger a system prompt change in the agent
450
456
  server.agent_manager.rebuild_system_prompt(agent_id=agent_id, actor=actor, force=True, update_timestamp=False)
@@ -481,7 +487,7 @@ def detach_block(
481
487
 
482
488
 
483
489
  @router.get("/{agent_id}/archival-memory", response_model=List[Passage], operation_id="list_passages")
484
- def list_passages(
490
+ async def list_passages(
485
491
  agent_id: str,
486
492
  server: "SyncServer" = Depends(get_letta_server),
487
493
  after: Optional[str] = Query(None, description="Unique ID of the memory to start the query range at."),
@@ -496,11 +502,11 @@ def list_passages(
496
502
  """
497
503
  Retrieve the memories in an agent's archival memory store (paginated query).
498
504
  """
499
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
505
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
500
506
 
501
- return server.get_agent_archival(
502
- user_id=actor.id,
507
+ return await server.get_agent_archival_async(
503
508
  agent_id=agent_id,
509
+ actor=actor,
504
510
  after=after,
505
511
  before=before,
506
512
  query_text=search,
@@ -564,7 +570,7 @@ AgentMessagesResponse = Annotated[
564
570
 
565
571
 
566
572
  @router.get("/{agent_id}/messages", response_model=AgentMessagesResponse, operation_id="list_messages")
567
- def list_messages(
573
+ async def list_messages(
568
574
  agent_id: str,
569
575
  server: "SyncServer" = Depends(get_letta_server),
570
576
  after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."),
@@ -579,10 +585,9 @@ def list_messages(
579
585
  """
580
586
  Retrieve message history for an agent.
581
587
  """
582
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
588
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
583
589
 
584
- return server.get_agent_recall(
585
- user_id=actor.id,
590
+ return await server.get_agent_recall_async(
586
591
  agent_id=agent_id,
587
592
  after=after,
588
593
  before=before,
@@ -593,6 +598,7 @@ def list_messages(
593
598
  use_assistant_message=use_assistant_message,
594
599
  assistant_message_tool_name=assistant_message_tool_name,
595
600
  assistant_message_tool_kwarg=assistant_message_tool_kwarg,
601
+ actor=actor,
596
602
  )
597
603
 
598
604
 
@@ -634,7 +640,7 @@ async def send_message(
634
640
  agent_eligible = not agent.enable_sleeptime and not agent.multi_agent_group and agent.agent_type != AgentType.sleeptime_agent
635
641
  experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
636
642
  feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
637
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "google_vertex", "google_ai"]
643
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
638
644
 
639
645
  if agent_eligible and feature_enabled and model_compatible:
640
646
  experimental_agent = LettaAgent(
@@ -644,6 +650,8 @@ async def send_message(
644
650
  block_manager=server.block_manager,
645
651
  passage_manager=server.passage_manager,
646
652
  actor=actor,
653
+ step_manager=server.step_manager,
654
+ telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
647
655
  )
648
656
 
649
657
  result = await experimental_agent.step(request.messages, max_steps=10, use_assistant_message=request.use_assistant_message)
@@ -692,7 +700,8 @@ async def send_message_streaming(
692
700
  agent_eligible = not agent.enable_sleeptime and not agent.multi_agent_group and agent.agent_type != AgentType.sleeptime_agent
693
701
  experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
694
702
  feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
695
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
703
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
704
+ model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
696
705
 
697
706
  if agent_eligible and feature_enabled and model_compatible and request.stream_tokens:
698
707
  experimental_agent = LettaAgent(
@@ -702,14 +711,28 @@ async def send_message_streaming(
702
711
  block_manager=server.block_manager,
703
712
  passage_manager=server.passage_manager,
704
713
  actor=actor,
714
+ step_manager=server.step_manager,
715
+ telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
705
716
  )
706
-
707
- result = StreamingResponse(
708
- experimental_agent.step_stream(
709
- request.messages, max_steps=10, use_assistant_message=request.use_assistant_message, stream_tokens=request.stream_tokens
710
- ),
711
- media_type="text/event-stream",
712
- )
717
+ from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode
718
+
719
+ if request.stream_tokens and model_compatible_token_streaming:
720
+ result = StreamingResponseWithStatusCode(
721
+ experimental_agent.step_stream(
722
+ input_messages=request.messages,
723
+ max_steps=10,
724
+ use_assistant_message=request.use_assistant_message,
725
+ request_start_timestamp_ns=request_start_timestamp_ns,
726
+ ),
727
+ media_type="text/event-stream",
728
+ )
729
+ else:
730
+ result = StreamingResponseWithStatusCode(
731
+ experimental_agent.step_stream_no_tokens(
732
+ request.messages, max_steps=10, use_assistant_message=request.use_assistant_message
733
+ ),
734
+ media_type="text/event-stream",
735
+ )
713
736
  else:
714
737
  result = await server.send_message_to_agent(
715
738
  agent_id=agent_id,
@@ -99,7 +99,7 @@ def retrieve_block(
99
99
 
100
100
 
101
101
  @router.get("/{block_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_block")
102
- def list_agents_for_block(
102
+ async def list_agents_for_block(
103
103
  block_id: str,
104
104
  server: SyncServer = Depends(get_letta_server),
105
105
  actor_id: Optional[str] = Header(None, alias="user_id"),
@@ -108,9 +108,9 @@ def list_agents_for_block(
108
108
  Retrieves all agents associated with the specified block.
109
109
  Raises a 404 if the block does not exist.
110
110
  """
111
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
111
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
112
112
  try:
113
- agents = server.block_manager.get_agents_for_block(block_id=block_id, actor=actor)
113
+ agents = await server.block_manager.get_agents_for_block_async(block_id=block_id, actor=actor)
114
114
  return agents
115
115
  except NoResultFound:
116
116
  raise HTTPException(status_code=404, detail=f"Block with id={block_id} not found")
@@ -13,7 +13,7 @@ router = APIRouter(prefix="/identities", tags=["identities"])
13
13
 
14
14
 
15
15
  @router.get("/", tags=["identities"], response_model=List[Identity], operation_id="list_identities")
16
- def list_identities(
16
+ async def list_identities(
17
17
  name: Optional[str] = Query(None),
18
18
  project_id: Optional[str] = Query(None),
19
19
  identifier_key: Optional[str] = Query(None),
@@ -28,9 +28,9 @@ def list_identities(
28
28
  Get a list of all identities in the database
29
29
  """
30
30
  try:
31
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
31
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
32
32
 
33
- identities = server.identity_manager.list_identities(
33
+ identities = await server.identity_manager.list_identities_async(
34
34
  name=name,
35
35
  project_id=project_id,
36
36
  identifier_key=identifier_key,
@@ -50,7 +50,7 @@ def list_identities(
50
50
 
51
51
 
52
52
  @router.get("/count", tags=["identities"], response_model=int, operation_id="count_identities")
53
- def count_identities(
53
+ async def count_identities(
54
54
  server: "SyncServer" = Depends(get_letta_server),
55
55
  actor_id: Optional[str] = Header(None, alias="user_id"),
56
56
  ):
@@ -58,7 +58,8 @@ def count_identities(
58
58
  Get count of all identities for a user
59
59
  """
60
60
  try:
61
- return server.identity_manager.size(actor=server.user_manager.get_user_or_default(user_id=actor_id))
61
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
62
+ return await server.identity_manager.size_async(actor=actor)
62
63
  except NoResultFound:
63
64
  return 0
64
65
  except HTTPException:
@@ -68,28 +69,28 @@ def count_identities(
68
69
 
69
70
 
70
71
  @router.get("/{identity_id}", tags=["identities"], response_model=Identity, operation_id="retrieve_identity")
71
- def retrieve_identity(
72
+ async def retrieve_identity(
72
73
  identity_id: str,
73
74
  server: "SyncServer" = Depends(get_letta_server),
74
75
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
75
76
  ):
76
77
  try:
77
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
78
- return server.identity_manager.get_identity(identity_id=identity_id, actor=actor)
78
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
79
+ return await server.identity_manager.get_identity_async(identity_id=identity_id, actor=actor)
79
80
  except NoResultFound as e:
80
81
  raise HTTPException(status_code=404, detail=str(e))
81
82
 
82
83
 
83
84
  @router.post("/", tags=["identities"], response_model=Identity, operation_id="create_identity")
84
- def create_identity(
85
+ async def create_identity(
85
86
  identity: IdentityCreate = Body(...),
86
87
  server: "SyncServer" = Depends(get_letta_server),
87
88
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
88
89
  x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
89
90
  ):
90
91
  try:
91
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
92
- return server.identity_manager.create_identity(identity=identity, actor=actor)
92
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
93
+ return await server.identity_manager.create_identity_async(identity=identity, actor=actor)
93
94
  except HTTPException:
94
95
  raise
95
96
  except UniqueConstraintViolationError:
@@ -105,15 +106,15 @@ def create_identity(
105
106
 
106
107
 
107
108
  @router.put("/", tags=["identities"], response_model=Identity, operation_id="upsert_identity")
108
- def upsert_identity(
109
+ async def upsert_identity(
109
110
  identity: IdentityUpsert = Body(...),
110
111
  server: "SyncServer" = Depends(get_letta_server),
111
112
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
112
113
  x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
113
114
  ):
114
115
  try:
115
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
116
- return server.identity_manager.upsert_identity(identity=identity, actor=actor)
116
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
117
+ return await server.identity_manager.upsert_identity_async(identity=identity, actor=actor)
117
118
  except HTTPException:
118
119
  raise
119
120
  except NoResultFound as e:
@@ -123,36 +124,33 @@ def upsert_identity(
123
124
 
124
125
 
125
126
  @router.patch("/{identity_id}", tags=["identities"], response_model=Identity, operation_id="update_identity")
126
- def modify_identity(
127
+ async def modify_identity(
127
128
  identity_id: str,
128
129
  identity: IdentityUpdate = Body(...),
129
130
  server: "SyncServer" = Depends(get_letta_server),
130
131
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
131
132
  ):
132
133
  try:
133
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
134
- return server.identity_manager.update_identity(identity_id=identity_id, identity=identity, actor=actor)
134
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
135
+ return await server.identity_manager.update_identity_async(identity_id=identity_id, identity=identity, actor=actor)
135
136
  except HTTPException:
136
137
  raise
137
138
  except NoResultFound as e:
138
139
  raise HTTPException(status_code=404, detail=str(e))
139
140
  except Exception as e:
140
- import traceback
141
-
142
- print(traceback.format_exc())
143
141
  raise HTTPException(status_code=500, detail=f"{e}")
144
142
 
145
143
 
146
144
  @router.put("/{identity_id}/properties", tags=["identities"], operation_id="upsert_identity_properties")
147
- def upsert_identity_properties(
145
+ async def upsert_identity_properties(
148
146
  identity_id: str,
149
147
  properties: List[IdentityProperty] = Body(...),
150
148
  server: "SyncServer" = Depends(get_letta_server),
151
149
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
152
150
  ):
153
151
  try:
154
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
155
- return server.identity_manager.upsert_identity_properties(identity_id=identity_id, properties=properties, actor=actor)
152
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
153
+ return await server.identity_manager.upsert_identity_properties_async(identity_id=identity_id, properties=properties, actor=actor)
156
154
  except HTTPException:
157
155
  raise
158
156
  except NoResultFound as e:
@@ -162,7 +160,7 @@ def upsert_identity_properties(
162
160
 
163
161
 
164
162
  @router.delete("/{identity_id}", tags=["identities"], operation_id="delete_identity")
165
- def delete_identity(
163
+ async def delete_identity(
166
164
  identity_id: str,
167
165
  server: "SyncServer" = Depends(get_letta_server),
168
166
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -171,8 +169,8 @@ def delete_identity(
171
169
  Delete an identity by its identifier key
172
170
  """
173
171
  try:
174
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
175
- server.identity_manager.delete_identity(identity_id=identity_id, actor=actor)
172
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
173
+ await server.identity_manager.delete_identity_async(identity_id=identity_id, actor=actor)
176
174
  except HTTPException:
177
175
  raise
178
176
  except NoResultFound as e:
@@ -33,16 +33,16 @@ def list_jobs(
33
33
 
34
34
 
35
35
  @router.get("/active", response_model=List[Job], operation_id="list_active_jobs")
36
- def list_active_jobs(
36
+ async def list_active_jobs(
37
37
  server: "SyncServer" = Depends(get_letta_server),
38
38
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
39
39
  ):
40
40
  """
41
41
  List all active jobs.
42
42
  """
43
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
43
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
44
44
 
45
- return server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running])
45
+ return await server.job_manager.list_jobs_async(actor=actor, statuses=[JobStatus.created, JobStatus.running])
46
46
 
47
47
 
48
48
  @router.get("/{job_id}", response_model=Job, operation_id="retrieve_job")
@@ -14,30 +14,35 @@ router = APIRouter(prefix="/models", tags=["models", "llms"])
14
14
 
15
15
 
16
16
  @router.get("/", response_model=List[LLMConfig], operation_id="list_models")
17
- def list_llm_models(
17
+ async def list_llm_models(
18
18
  provider_category: Optional[List[ProviderCategory]] = Query(None),
19
19
  provider_name: Optional[str] = Query(None),
20
20
  provider_type: Optional[ProviderType] = Query(None),
21
21
  server: "SyncServer" = Depends(get_letta_server),
22
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
22
+ actor_id: Optional[str] = Header(None, alias="user_id"),
23
+ # Extract user_id from header, default to None if not present
23
24
  ):
25
+ """List available LLM models using the asynchronous implementation for improved performance"""
24
26
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
25
- models = server.list_llm_models(
27
+
28
+ models = await server.list_llm_models_async(
26
29
  provider_category=provider_category,
27
30
  provider_name=provider_name,
28
31
  provider_type=provider_type,
29
32
  actor=actor,
30
33
  )
31
- # print(models)
34
+
32
35
  return models
33
36
 
34
37
 
35
38
  @router.get("/embedding", response_model=List[EmbeddingConfig], operation_id="list_embedding_models")
36
- def list_embedding_models(
39
+ async def list_embedding_models(
37
40
  server: "SyncServer" = Depends(get_letta_server),
38
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
41
+ actor_id: Optional[str] = Header(None, alias="user_id"),
42
+ # Extract user_id from header, default to None if not present
39
43
  ):
44
+ """List available embedding models using the asynchronous implementation for improved performance"""
40
45
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
41
- models = server.list_embedding_models(actor=actor)
42
- # print(models)
46
+ models = await server.list_embedding_models_async(actor=actor)
47
+
43
48
  return models
@@ -100,15 +100,15 @@ def delete_sandbox_config(
100
100
 
101
101
 
102
102
  @router.get("/", response_model=List[PydanticSandboxConfig])
103
- def list_sandbox_configs(
103
+ async def list_sandbox_configs(
104
104
  limit: int = Query(1000, description="Number of results to return"),
105
105
  after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"),
106
106
  sandbox_type: Optional[SandboxType] = Query(None, description="Filter for this specific sandbox type"),
107
107
  server: SyncServer = Depends(get_letta_server),
108
108
  actor_id: str = Depends(get_user_id),
109
109
  ):
110
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
111
- return server.sandbox_config_manager.list_sandbox_configs(actor, limit=limit, after=after, sandbox_type=sandbox_type)
110
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
111
+ return await server.sandbox_config_manager.list_sandbox_configs_async(actor, limit=limit, after=after, sandbox_type=sandbox_type)
112
112
 
113
113
 
114
114
  @router.post("/local/recreate-venv", response_model=PydanticSandboxConfig)
@@ -190,12 +190,12 @@ def delete_sandbox_env_var(
190
190
 
191
191
 
192
192
  @router.get("/{sandbox_config_id}/environment-variable", response_model=List[PydanticEnvVar])
193
- def list_sandbox_env_vars(
193
+ async def list_sandbox_env_vars(
194
194
  sandbox_config_id: str,
195
195
  limit: int = Query(1000, description="Number of results to return"),
196
196
  after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"),
197
197
  server: SyncServer = Depends(get_letta_server),
198
198
  actor_id: str = Depends(get_user_id),
199
199
  ):
200
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
201
- return server.sandbox_config_manager.list_sandbox_env_vars(sandbox_config_id, actor, limit=limit, after=after)
200
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
201
+ return await server.sandbox_config_manager.list_sandbox_env_vars_async(sandbox_config_id, actor, limit=limit, after=after)
@@ -12,7 +12,7 @@ router = APIRouter(prefix="/tags", tags=["tag", "admin"])
12
12
 
13
13
 
14
14
  @router.get("/", tags=["admin"], response_model=List[str], operation_id="list_tags")
15
- def list_tags(
15
+ async def list_tags(
16
16
  after: Optional[str] = Query(None),
17
17
  limit: Optional[int] = Query(50),
18
18
  server: "SyncServer" = Depends(get_letta_server),
@@ -22,6 +22,6 @@ def list_tags(
22
22
  """
23
23
  Get a list of all tags in the database
24
24
  """
25
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
26
- tags = server.agent_manager.list_tags(actor=actor, after=after, limit=limit, query_text=query_text)
25
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
26
+ tags = await server.agent_manager.list_tags_async(actor=actor, after=after, limit=limit, query_text=query_text)
27
27
  return tags
@@ -0,0 +1,18 @@
1
+ from fastapi import APIRouter, Depends, Header
2
+
3
+ from letta.schemas.provider_trace import ProviderTrace
4
+ from letta.server.rest_api.utils import get_letta_server
5
+ from letta.server.server import SyncServer
6
+
7
+ router = APIRouter(prefix="/telemetry", tags=["telemetry"])
8
+
9
+
10
+ @router.get("/{step_id}", response_model=ProviderTrace, operation_id="retrieve_provider_trace")
11
+ async def retrieve_provider_trace_by_step_id(
12
+ step_id: str,
13
+ server: SyncServer = Depends(get_letta_server),
14
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
15
+ ):
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
+ )
@@ -59,7 +59,7 @@ def count_tools(
59
59
 
60
60
 
61
61
  @router.get("/{tool_id}", response_model=Tool, operation_id="retrieve_tool")
62
- def retrieve_tool(
62
+ async def retrieve_tool(
63
63
  tool_id: str,
64
64
  server: SyncServer = Depends(get_letta_server),
65
65
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -67,8 +67,8 @@ def retrieve_tool(
67
67
  """
68
68
  Get a tool by ID
69
69
  """
70
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
71
- tool = server.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor)
70
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
71
+ tool = await server.tool_manager.get_tool_by_id_async(tool_id=tool_id, actor=actor)
72
72
  if tool is None:
73
73
  # return 404 error
74
74
  raise HTTPException(status_code=404, detail=f"Tool with id {tool_id} not found.")
@@ -196,15 +196,15 @@ def modify_tool(
196
196
 
197
197
 
198
198
  @router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
199
- def upsert_base_tools(
199
+ async def upsert_base_tools(
200
200
  server: SyncServer = Depends(get_letta_server),
201
201
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
202
202
  ):
203
203
  """
204
204
  Upsert base tools
205
205
  """
206
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
207
- return server.tool_manager.upsert_base_tools(actor=actor)
206
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
207
+ return await server.tool_manager.upsert_base_tools_async(actor=actor)
208
208
 
209
209
 
210
210
  @router.post("/run", response_model=ToolReturnMessage, operation_id="run_tool_from_source")