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.
- letta/__init__.py +5 -3
- letta/agent.py +3 -2
- letta/agents/base_agent.py +4 -1
- letta/agents/voice_agent.py +1 -0
- letta/constants.py +4 -2
- letta/functions/schema_generator.py +2 -1
- letta/groups/dynamic_multi_agent.py +1 -0
- letta/helpers/converters.py +13 -5
- letta/helpers/json_helpers.py +6 -1
- letta/llm_api/anthropic.py +2 -2
- letta/llm_api/aws_bedrock.py +24 -94
- letta/llm_api/deepseek.py +1 -1
- letta/llm_api/google_ai_client.py +0 -38
- letta/llm_api/google_constants.py +6 -3
- letta/llm_api/helpers.py +1 -1
- letta/llm_api/llm_api_tools.py +4 -7
- letta/llm_api/mistral.py +12 -37
- letta/llm_api/openai.py +17 -17
- letta/llm_api/sample_response_jsons/aws_bedrock.json +38 -0
- letta/llm_api/sample_response_jsons/lmstudio_embedding_list.json +15 -0
- letta/llm_api/sample_response_jsons/lmstudio_model_list.json +15 -0
- letta/local_llm/constants.py +2 -23
- letta/local_llm/json_parser.py +11 -1
- letta/local_llm/llm_chat_completion_wrappers/airoboros.py +9 -9
- letta/local_llm/llm_chat_completion_wrappers/chatml.py +7 -8
- letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +6 -6
- letta/local_llm/llm_chat_completion_wrappers/dolphin.py +3 -3
- letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +1 -1
- letta/local_llm/ollama/api.py +2 -2
- letta/orm/__init__.py +1 -0
- letta/orm/agent.py +33 -2
- letta/orm/files_agents.py +13 -10
- letta/orm/mixins.py +8 -0
- letta/orm/prompt.py +13 -0
- letta/orm/sqlite_functions.py +61 -17
- letta/otel/db_pool_monitoring.py +13 -12
- letta/schemas/agent.py +69 -4
- letta/schemas/agent_file.py +2 -0
- letta/schemas/block.py +11 -0
- letta/schemas/embedding_config.py +15 -3
- letta/schemas/enums.py +2 -0
- letta/schemas/file.py +1 -1
- letta/schemas/folder.py +74 -0
- letta/schemas/memory.py +12 -6
- letta/schemas/prompt.py +9 -0
- letta/schemas/providers/__init__.py +47 -0
- letta/schemas/providers/anthropic.py +78 -0
- letta/schemas/providers/azure.py +80 -0
- letta/schemas/providers/base.py +201 -0
- letta/schemas/providers/bedrock.py +78 -0
- letta/schemas/providers/cerebras.py +79 -0
- letta/schemas/providers/cohere.py +18 -0
- letta/schemas/providers/deepseek.py +63 -0
- letta/schemas/providers/google_gemini.py +102 -0
- letta/schemas/providers/google_vertex.py +54 -0
- letta/schemas/providers/groq.py +35 -0
- letta/schemas/providers/letta.py +39 -0
- letta/schemas/providers/lmstudio.py +97 -0
- letta/schemas/providers/mistral.py +41 -0
- letta/schemas/providers/ollama.py +151 -0
- letta/schemas/providers/openai.py +241 -0
- letta/schemas/providers/together.py +85 -0
- letta/schemas/providers/vllm.py +57 -0
- letta/schemas/providers/xai.py +66 -0
- letta/server/db.py +0 -5
- letta/server/rest_api/app.py +4 -3
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +152 -4
- letta/server/rest_api/routers/v1/folders.py +490 -0
- letta/server/rest_api/routers/v1/providers.py +2 -2
- letta/server/rest_api/routers/v1/sources.py +21 -26
- letta/server/rest_api/routers/v1/tools.py +90 -15
- letta/server/server.py +50 -95
- letta/services/agent_manager.py +420 -81
- letta/services/agent_serialization_manager.py +707 -0
- letta/services/block_manager.py +132 -11
- letta/services/file_manager.py +104 -29
- letta/services/file_processor/embedder/pinecone_embedder.py +8 -2
- letta/services/file_processor/file_processor.py +75 -24
- letta/services/file_processor/parser/markitdown_parser.py +95 -0
- letta/services/files_agents_manager.py +57 -17
- letta/services/group_manager.py +7 -0
- letta/services/helpers/agent_manager_helper.py +25 -15
- letta/services/provider_manager.py +2 -2
- letta/services/source_manager.py +35 -16
- letta/services/tool_executor/files_tool_executor.py +12 -5
- letta/services/tool_manager.py +12 -0
- letta/services/tool_sandbox/e2b_sandbox.py +52 -48
- letta/settings.py +9 -6
- letta/streaming_utils.py +2 -1
- letta/utils.py +34 -1
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/METADATA +9 -8
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/RECORD +96 -68
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/WHEEL +0 -0
- {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
|
-
|
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
|
-
|
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
|
-
|
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(
|