Skip to content

Commit 8682930

Browse files
authored
feat: folders fixes (#2730)
2 parents cd11132 + 4b626e7 commit 8682930

File tree

5 files changed

+83
-10
lines changed

5 files changed

+83
-10
lines changed

letta/schemas/folder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class BaseFolder(LettaBase):
1212
Shared attributes across all folder schemas.
1313
"""
1414

15-
__id_prefix__ = "folder"
15+
__id_prefix__ = "source" # TODO: change to "folder"
1616

1717
# Core folder fields
1818
name: str = Field(..., description="The name of the folder.")

letta/server/rest_api/routers/v1/agents.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,35 @@ async def attach_source(
343343
return agent_state
344344

345345

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+
346375
@router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent")
347376
async def detach_source(
348377
agent_id: str,
@@ -373,6 +402,36 @@ async def detach_source(
373402
return agent_state
374403

375404

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+
376435
@router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="close_all_open_files")
377436
async def close_all_open_files(
378437
agent_id: str,
@@ -516,6 +575,19 @@ async def list_agent_sources(
516575
return await server.agent_manager.list_attached_sources_async(agent_id=agent_id, actor=actor)
517576

518577

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+
519591
# TODO: remove? can also get with agent blocks
520592
@router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory")
521593
async def retrieve_agent_memory(

letta/server/rest_api/routers/v1/folders.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
from letta.schemas.embedding_config import EmbeddingConfig
2121
from letta.schemas.enums import DuplicateFileHandling, FileProcessingStatus
2222
from letta.schemas.file import FileMetadata
23-
from letta.schemas.folder import Folder, FolderCreate, FolderUpdate
23+
from letta.schemas.folder import Folder
2424
from letta.schemas.passage import Passage
25+
from letta.schemas.source import Source, SourceCreate, SourceUpdate
2526
from letta.schemas.source_metadata import OrganizationSourcesStats
2627
from letta.schemas.user import User
2728
from letta.server.rest_api.utils import get_letta_server
@@ -125,7 +126,7 @@ async def list_folders(
125126

126127
@router.post("/", response_model=Folder, operation_id="create_folder")
127128
async def create_folder(
128-
folder_create: FolderCreate,
129+
folder_create: SourceCreate,
129130
server: "SyncServer" = Depends(get_letta_server),
130131
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
131132
):
@@ -144,7 +145,7 @@ async def create_folder(
144145
embedding_chunk_size=folder_create.embedding_chunk_size or constants.DEFAULT_EMBEDDING_CHUNK_SIZE,
145146
actor=actor,
146147
)
147-
folder = Folder(
148+
folder = Source(
148149
name=folder_create.name,
149150
embedding_config=folder_create.embedding_config,
150151
description=folder_create.description,
@@ -157,7 +158,7 @@ async def create_folder(
157158
@router.patch("/{folder_id}", response_model=Folder, operation_id="modify_folder")
158159
async def modify_folder(
159160
folder_id: str,
160-
folder: FolderUpdate,
161+
folder: SourceUpdate,
161162
server: "SyncServer" = Depends(get_letta_server),
162163
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
163164
):

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ llama-index = "^0.12.2"
7272
llama-index-embeddings-openai = "^0.3.1"
7373
e2b-code-interpreter = {version = "^1.0.3", optional = true}
7474
anthropic = "^0.49.0"
75-
letta_client = "^0.1.213"
75+
letta_client = "^0.1.219"
7676
openai = "^1.60.0"
7777
opentelemetry-api = "1.30.0"
7878
opentelemetry-sdk = "1.30.0"

0 commit comments

Comments
 (0)