letta-nightly 0.8.17.dev20250723104501__py3-none-any.whl → 0.9.0.dev20250724104456__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 (96) hide show
  1. letta/__init__.py +5 -3
  2. letta/agent.py +3 -2
  3. letta/agents/base_agent.py +4 -1
  4. letta/agents/voice_agent.py +1 -0
  5. letta/constants.py +4 -2
  6. letta/functions/schema_generator.py +2 -1
  7. letta/groups/dynamic_multi_agent.py +1 -0
  8. letta/helpers/converters.py +13 -5
  9. letta/helpers/json_helpers.py +6 -1
  10. letta/llm_api/anthropic.py +2 -2
  11. letta/llm_api/aws_bedrock.py +24 -94
  12. letta/llm_api/deepseek.py +1 -1
  13. letta/llm_api/google_ai_client.py +0 -38
  14. letta/llm_api/google_constants.py +6 -3
  15. letta/llm_api/helpers.py +1 -1
  16. letta/llm_api/llm_api_tools.py +4 -7
  17. letta/llm_api/mistral.py +12 -37
  18. letta/llm_api/openai.py +17 -17
  19. letta/llm_api/sample_response_jsons/aws_bedrock.json +38 -0
  20. letta/llm_api/sample_response_jsons/lmstudio_embedding_list.json +15 -0
  21. letta/llm_api/sample_response_jsons/lmstudio_model_list.json +15 -0
  22. letta/local_llm/constants.py +2 -23
  23. letta/local_llm/json_parser.py +11 -1
  24. letta/local_llm/llm_chat_completion_wrappers/airoboros.py +9 -9
  25. letta/local_llm/llm_chat_completion_wrappers/chatml.py +7 -8
  26. letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +6 -6
  27. letta/local_llm/llm_chat_completion_wrappers/dolphin.py +3 -3
  28. letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +1 -1
  29. letta/local_llm/ollama/api.py +2 -2
  30. letta/orm/__init__.py +1 -0
  31. letta/orm/agent.py +33 -2
  32. letta/orm/files_agents.py +13 -10
  33. letta/orm/mixins.py +8 -0
  34. letta/orm/prompt.py +13 -0
  35. letta/orm/sqlite_functions.py +61 -17
  36. letta/otel/db_pool_monitoring.py +13 -12
  37. letta/schemas/agent.py +69 -4
  38. letta/schemas/agent_file.py +2 -0
  39. letta/schemas/block.py +11 -0
  40. letta/schemas/embedding_config.py +15 -3
  41. letta/schemas/enums.py +2 -0
  42. letta/schemas/file.py +1 -1
  43. letta/schemas/folder.py +74 -0
  44. letta/schemas/memory.py +12 -6
  45. letta/schemas/prompt.py +9 -0
  46. letta/schemas/providers/__init__.py +47 -0
  47. letta/schemas/providers/anthropic.py +78 -0
  48. letta/schemas/providers/azure.py +80 -0
  49. letta/schemas/providers/base.py +201 -0
  50. letta/schemas/providers/bedrock.py +78 -0
  51. letta/schemas/providers/cerebras.py +79 -0
  52. letta/schemas/providers/cohere.py +18 -0
  53. letta/schemas/providers/deepseek.py +63 -0
  54. letta/schemas/providers/google_gemini.py +102 -0
  55. letta/schemas/providers/google_vertex.py +54 -0
  56. letta/schemas/providers/groq.py +35 -0
  57. letta/schemas/providers/letta.py +39 -0
  58. letta/schemas/providers/lmstudio.py +97 -0
  59. letta/schemas/providers/mistral.py +41 -0
  60. letta/schemas/providers/ollama.py +151 -0
  61. letta/schemas/providers/openai.py +241 -0
  62. letta/schemas/providers/together.py +85 -0
  63. letta/schemas/providers/vllm.py +57 -0
  64. letta/schemas/providers/xai.py +66 -0
  65. letta/server/db.py +0 -5
  66. letta/server/rest_api/app.py +4 -3
  67. letta/server/rest_api/routers/v1/__init__.py +2 -0
  68. letta/server/rest_api/routers/v1/agents.py +152 -4
  69. letta/server/rest_api/routers/v1/folders.py +490 -0
  70. letta/server/rest_api/routers/v1/providers.py +2 -2
  71. letta/server/rest_api/routers/v1/sources.py +21 -26
  72. letta/server/rest_api/routers/v1/tools.py +90 -15
  73. letta/server/server.py +50 -95
  74. letta/services/agent_manager.py +420 -81
  75. letta/services/agent_serialization_manager.py +707 -0
  76. letta/services/block_manager.py +132 -11
  77. letta/services/file_manager.py +104 -29
  78. letta/services/file_processor/embedder/pinecone_embedder.py +8 -2
  79. letta/services/file_processor/file_processor.py +75 -24
  80. letta/services/file_processor/parser/markitdown_parser.py +95 -0
  81. letta/services/files_agents_manager.py +57 -17
  82. letta/services/group_manager.py +7 -0
  83. letta/services/helpers/agent_manager_helper.py +25 -15
  84. letta/services/provider_manager.py +2 -2
  85. letta/services/source_manager.py +35 -16
  86. letta/services/tool_executor/files_tool_executor.py +12 -5
  87. letta/services/tool_manager.py +12 -0
  88. letta/services/tool_sandbox/e2b_sandbox.py +52 -48
  89. letta/settings.py +9 -6
  90. letta/streaming_utils.py +2 -1
  91. letta/utils.py +34 -1
  92. {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/METADATA +9 -8
  93. {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/RECORD +96 -68
  94. {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/LICENSE +0 -0
  95. {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/WHEEL +0 -0
  96. {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/entry_points.txt +0 -0
@@ -293,7 +293,9 @@ async def attach_tool(
293
293
  Attach a tool to an agent.
294
294
  """
295
295
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
296
- return await server.agent_manager.attach_tool_async(agent_id=agent_id, tool_id=tool_id, actor=actor)
296
+ await server.agent_manager.attach_tool_async(agent_id=agent_id, tool_id=tool_id, actor=actor)
297
+ # TODO: Unfortunately we need this to preserve our current API behavior
298
+ return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
297
299
 
298
300
 
299
301
  @router.patch("/{agent_id}/tools/detach/{tool_id}", response_model=AgentState, operation_id="detach_tool")
@@ -307,7 +309,9 @@ async def detach_tool(
307
309
  Detach a tool from an agent.
308
310
  """
309
311
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
310
- return await server.agent_manager.detach_tool_async(agent_id=agent_id, tool_id=tool_id, actor=actor)
312
+ await server.agent_manager.detach_tool_async(agent_id=agent_id, tool_id=tool_id, actor=actor)
313
+ # TODO: Unfortunately we need this to preserve our current API behavior
314
+ return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
311
315
 
312
316
 
313
317
  @router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent")
@@ -327,7 +331,8 @@ async def attach_source(
327
331
  agent_state = await server.agent_manager.attach_missing_files_tools_async(agent_state=agent_state, actor=actor)
328
332
 
329
333
  files = await server.file_manager.list_files(source_id, actor, include_content=True)
330
- await server.insert_files_into_context_window(agent_state=agent_state, file_metadata_with_content=files, actor=actor)
334
+ if files:
335
+ await server.agent_manager.insert_files_into_context_window(agent_state=agent_state, file_metadata_with_content=files, actor=actor)
331
336
 
332
337
  if agent_state.enable_sleeptime:
333
338
  source = await server.source_manager.get_source_by_id(source_id=source_id)
@@ -338,6 +343,35 @@ async def attach_source(
338
343
  return agent_state
339
344
 
340
345
 
346
+ @router.patch("/{agent_id}/folders/attach/{folder_id}", response_model=AgentState, operation_id="attach_folder_to_agent")
347
+ async def attach_folder_to_agent(
348
+ agent_id: str,
349
+ folder_id: str,
350
+ server: "SyncServer" = Depends(get_letta_server),
351
+ actor_id: str | None = Header(None, alias="user_id"),
352
+ ):
353
+ """
354
+ Attach a folder to an agent.
355
+ """
356
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
357
+ agent_state = await server.agent_manager.attach_source_async(agent_id=agent_id, source_id=folder_id, actor=actor)
358
+
359
+ # Check if the agent is missing any files tools
360
+ agent_state = await server.agent_manager.attach_missing_files_tools_async(agent_state=agent_state, actor=actor)
361
+
362
+ files = await server.file_manager.list_files(folder_id, actor, include_content=True)
363
+ if files:
364
+ await server.agent_manager.insert_files_into_context_window(agent_state=agent_state, file_metadata_with_content=files, actor=actor)
365
+
366
+ if agent_state.enable_sleeptime:
367
+ source = await server.source_manager.get_source_by_id(source_id=folder_id)
368
+ safe_create_task(
369
+ server.sleeptime_document_ingest_async(agent_state, source, actor), logger=logger, label="sleeptime_document_ingest_async"
370
+ )
371
+
372
+ return agent_state
373
+
374
+
341
375
  @router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent")
342
376
  async def detach_source(
343
377
  agent_id: str,
@@ -368,6 +402,36 @@ async def detach_source(
368
402
  return agent_state
369
403
 
370
404
 
405
+ @router.patch("/{agent_id}/folders/detach/{folder_id}", response_model=AgentState, operation_id="detach_folder_from_agent")
406
+ async def detach_folder_from_agent(
407
+ agent_id: str,
408
+ folder_id: str,
409
+ server: "SyncServer" = Depends(get_letta_server),
410
+ actor_id: str | None = Header(None, alias="user_id"),
411
+ ):
412
+ """
413
+ Detach a folder from an agent.
414
+ """
415
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
416
+ agent_state = await server.agent_manager.detach_source_async(agent_id=agent_id, source_id=folder_id, actor=actor)
417
+
418
+ if not agent_state.sources:
419
+ agent_state = await server.agent_manager.detach_all_files_tools_async(agent_state=agent_state, actor=actor)
420
+
421
+ files = await server.file_manager.list_files(folder_id, actor)
422
+ file_ids = [f.id for f in files]
423
+ await server.remove_files_from_context_window(agent_state=agent_state, file_ids=file_ids, actor=actor)
424
+
425
+ if agent_state.enable_sleeptime:
426
+ try:
427
+ source = await server.source_manager.get_source_by_id(source_id=folder_id)
428
+ block = await server.agent_manager.get_block_with_label_async(agent_id=agent_state.id, block_label=source.name, actor=actor)
429
+ await server.block_manager.delete_block_async(block.id, actor)
430
+ except:
431
+ pass
432
+ return agent_state
433
+
434
+
371
435
  @router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="close_all_open_files")
372
436
  async def close_all_open_files(
373
437
  agent_id: str,
@@ -382,7 +446,78 @@ async def close_all_open_files(
382
446
  """
383
447
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
384
448
 
385
- return server.file_agent_manager.close_all_other_files(agent_id=agent_id, keep_file_names=[], actor=actor)
449
+ return await server.file_agent_manager.close_all_other_files(agent_id=agent_id, keep_file_names=[], actor=actor)
450
+
451
+
452
+ @router.patch("/{agent_id}/files/{file_id}/open", response_model=List[str], operation_id="open_file")
453
+ async def open_file(
454
+ agent_id: str,
455
+ file_id: str,
456
+ server: "SyncServer" = Depends(get_letta_server),
457
+ actor_id: Optional[str] = Header(None, alias="user_id"),
458
+ ):
459
+ """
460
+ Opens a specific file for a given agent.
461
+
462
+ This endpoint marks a specific file as open in the agent's file state.
463
+ The file will be included in the agent's working memory view.
464
+ Returns a list of file names that were closed due to LRU eviction.
465
+ """
466
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
467
+
468
+ # Get the agent to access files configuration
469
+ try:
470
+ per_file_view_window_char_limit, max_files_open = await server.agent_manager.get_agent_files_config_async(
471
+ agent_id=agent_id, actor=actor
472
+ )
473
+ except ValueError:
474
+ raise HTTPException(status_code=404, detail=f"Agent with id={agent_id} not found")
475
+
476
+ # Get file metadata
477
+ file_metadata = await server.file_manager.get_file_by_id(file_id=file_id, actor=actor, include_content=True)
478
+ if not file_metadata:
479
+ raise HTTPException(status_code=404, detail=f"File with id={file_id} not found")
480
+
481
+ # Use enforce_max_open_files_and_open for efficient LRU handling
482
+ closed_files, was_already_open = await server.file_agent_manager.enforce_max_open_files_and_open(
483
+ agent_id=agent_id,
484
+ file_id=file_id,
485
+ file_name=file_metadata.file_name,
486
+ source_id=file_metadata.source_id,
487
+ actor=actor,
488
+ visible_content=file_metadata.content[:per_file_view_window_char_limit] if file_metadata.content else "",
489
+ max_files_open=max_files_open,
490
+ )
491
+
492
+ return closed_files
493
+
494
+
495
+ @router.patch("/{agent_id}/files/{file_id}/close", response_model=None, operation_id="close_file")
496
+ async def close_file(
497
+ agent_id: str,
498
+ file_id: str,
499
+ server: "SyncServer" = Depends(get_letta_server),
500
+ actor_id: Optional[str] = Header(None, alias="user_id"),
501
+ ):
502
+ """
503
+ Closes a specific file for a given agent.
504
+
505
+ This endpoint marks a specific file as closed in the agent's file state.
506
+ The file will be removed from the agent's working memory view.
507
+ """
508
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
509
+
510
+ # Use update_file_agent_by_id to close the file
511
+ try:
512
+ await server.file_agent_manager.update_file_agent_by_id(
513
+ agent_id=agent_id,
514
+ file_id=file_id,
515
+ actor=actor,
516
+ is_open=False,
517
+ )
518
+ return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"File id={file_id} successfully closed"})
519
+ except NoResultFound:
520
+ raise HTTPException(status_code=404, detail=f"File association for file_id={file_id} and agent_id={agent_id} not found")
386
521
 
387
522
 
388
523
  @router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent")
@@ -440,6 +575,19 @@ async def list_agent_sources(
440
575
  return await server.agent_manager.list_attached_sources_async(agent_id=agent_id, actor=actor)
441
576
 
442
577
 
578
+ @router.get("/{agent_id}/folders", response_model=list[Source], operation_id="list_agent_folders")
579
+ async def list_agent_folders(
580
+ agent_id: str,
581
+ server: "SyncServer" = Depends(get_letta_server),
582
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
583
+ ):
584
+ """
585
+ Get the folders associated with an agent.
586
+ """
587
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
588
+ return await server.agent_manager.list_attached_sources_async(agent_id=agent_id, actor=actor)
589
+
590
+
443
591
  # TODO: remove? can also get with agent blocks
444
592
  @router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory")
445
593
  async def retrieve_agent_memory(