From b73545cd609203ae44d1e0583ef1c1d85106902a Mon Sep 17 00:00:00 2001 From: Shelley Pham Date: Wed, 12 Nov 2025 14:34:16 -0800 Subject: [PATCH] fix: agents created from templates cannot read attached files [LET-6146] (#6137) * fix: Ensure agents created from templates can read attached files * test: Add test for template-based agent file attachment from sources --- letta/services/agent_manager.py | 39 +++++++++++++ tests/managers/test_agent_manager.py | 84 +++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/letta/services/agent_manager.py b/letta/services/agent_manager.py index 7db7e626..0e62579e 100644 --- a/letta/services/agent_manager.py +++ b/letta/services/agent_manager.py @@ -607,6 +607,45 @@ class AgentManager: await self.message_manager.create_many_messages_async( pydantic_msgs=init_messages, actor=actor, project_id=result.project_id, template_id=result.template_id ) + + # Attach files from sources if this is a template-based creation + # Use the new agent's sources (already copied from template via source_ids) + if isinstance(agent_create, InternalTemplateAgentCreate) and source_ids: + try: + from letta.services.file_manager import FileManager + + file_manager = FileManager() + + # Get all files from the new agent's sources + all_files_metadata = [] + for source_id in source_ids: + try: + files_in_source = await file_manager.list_files( + source_id=source_id, + actor=actor, + limit=1000, + ) + all_files_metadata.extend(files_in_source) + except Exception as e: + logger.warning(f"Failed to get files from source {source_id}: {e}") + + if all_files_metadata: + try: + await self.file_agent_manager.attach_files_bulk( + agent_id=result.id, + files_metadata=all_files_metadata, + visible_content_map={}, # Empty map - content generated on-demand + actor=actor, + max_files_open=result.max_files_open or DEFAULT_MAX_FILES_OPEN, + ) + except Exception as e: + logger.error(f"Failed to attach files: {e}") + except Exception as e: + logger.error(f"====> Failed to attach files from sources: {e}") + import traceback + + traceback.print_exc() + return result @enforce_types diff --git a/tests/managers/test_agent_manager.py b/tests/managers/test_agent_manager.py index aa05c3df..9c870fbf 100644 --- a/tests/managers/test_agent_manager.py +++ b/tests/managers/test_agent_manager.py @@ -54,7 +54,7 @@ from letta.orm import Base, Block from letta.orm.block_history import BlockHistory from letta.orm.errors import NoResultFound, UniqueConstraintViolationError from letta.orm.file import FileContent as FileContentModel, FileMetadata as FileMetadataModel -from letta.schemas.agent import CreateAgent, UpdateAgent +from letta.schemas.agent import CreateAgent, InternalTemplateAgentCreate, UpdateAgent from letta.schemas.block import Block as PydanticBlock, BlockUpdate, CreateBlock from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.enums import ( @@ -1667,3 +1667,85 @@ async def test_agent_state_relationship_loads(server: SyncServer, default_user, assert agent_state.sources assert not agent_state.tags assert not agent_state.tools + + +async def test_create_template_agent_with_files_from_sources(server: SyncServer, default_user, print_tool, default_block): + """Test that agents created from templates properly attach files from their sources""" + from letta.schemas.file import FileMetadata as PydanticFileMetadata + + memory_blocks = [CreateBlock(label="human", value="TestUser"), CreateBlock(label="persona", value="I am a test assistant")] + + # Create a source with files + source = await server.source_manager.create_source( + source=PydanticSource( + name="test_template_source", + embedding_config=EmbeddingConfig.default_config(provider="openai"), + ), + actor=default_user, + ) + + # Create files in the source + file1_metadata = PydanticFileMetadata( + file_name="template_file_1.txt", + organization_id=default_user.organization_id, + source_id=source.id, + ) + file1 = await server.file_manager.create_file(file_metadata=file1_metadata, actor=default_user, text="content for file 1") + + file2_metadata = PydanticFileMetadata( + file_name="template_file_2.txt", + organization_id=default_user.organization_id, + source_id=source.id, + ) + file2 = await server.file_manager.create_file(file_metadata=file2_metadata, actor=default_user, text="content for file 2") + + # Create agent using InternalTemplateAgentCreate with the source + create_agent_request = InternalTemplateAgentCreate( + name="test_template_agent_with_files", + system="test system", + memory_blocks=memory_blocks, + llm_config=LLMConfig.default_config("gpt-4o-mini"), + embedding_config=EmbeddingConfig.default_config(provider="openai"), + block_ids=[default_block.id], + tool_ids=[print_tool.id], + source_ids=[source.id], # Attach the source with files + include_base_tools=False, + base_template_id="base_template_123", + template_id="template_456", + deployment_id="deployment_789", + entity_id="entity_012", + ) + + # Create the agent + created_agent = await server.agent_manager.create_agent_async( + create_agent_request, + actor=default_user, + ) + + # Verify agent was created + assert created_agent is not None + assert created_agent.name == "test_template_agent_with_files" + + # Verify that the source is attached + attached_sources = await server.agent_manager.list_attached_sources_async(agent_id=created_agent.id, actor=default_user) + assert len(attached_sources) == 1 + assert attached_sources[0].id == source.id + + # Verify that files from the source are attached to the agent + attached_files = await server.file_agent_manager.list_files_for_agent( + created_agent.id, per_file_view_window_char_limit=created_agent.per_file_view_window_char_limit, actor=default_user + ) + + # Should have both files attached + assert len(attached_files) == 2 + attached_file_names = {f.file_name for f in attached_files} + assert "template_file_1.txt" in attached_file_names + assert "template_file_2.txt" in attached_file_names + + # Verify files are properly linked to the source + for attached_file in attached_files: + assert attached_file.source_id == source.id + + # Clean up + await server.agent_manager.delete_agent_async(created_agent.id, default_user) + await server.source_manager.delete_source(source.id, default_user)