diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index eaf989f9..52b13bc7 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -1,7 +1,7 @@ import functools import os import time -from typing import Optional, Union +from typing import Any, Optional, Union from letta_client import AsyncLetta, Letta @@ -294,22 +294,32 @@ def upload_file_and_wait( """Helper function to upload a file and wait for processing to complete""" with open(file_path, "rb") as f: if duplicate_handling: - file_metadata = client.sources.files.upload(source_id=source_id, file=f, duplicate_handling=duplicate_handling, name=name) + file_metadata = client.folders.files.upload(folder_id=source_id, file=f, duplicate_handling=duplicate_handling, name=name) else: - file_metadata = client.sources.files.upload(source_id=source_id, file=f, name=name) + file_metadata = client.folders.files.upload(folder_id=source_id, file=f, name=name) # wait for the file to be processed start_time = time.time() - while file_metadata.processing_status != "completed" and file_metadata.processing_status != "error": + file_metadata_id = file_metadata.id + processing_status = file_metadata.processing_status + while processing_status != "completed" and processing_status != "error": if time.time() - start_time > max_wait: raise TimeoutError(f"File processing timed out after {max_wait} seconds") time.sleep(1) - file_metadata = client.sources.get_file_metadata(source_id=source_id, file_id=file_metadata.id) - print("Waiting for file processing to complete...", file_metadata.processing_status) + file_metadata = client.get( + path=f"/v1/sources/{source_id}/files/{file_metadata_id}", + cast_to=dict[str, Any], + ) + print("Waiting for file processing to complete...", file_metadata["processing_status"]) + processing_status = file_metadata["processing_status"] - if file_metadata.processing_status == "error": + if isinstance(file_metadata, dict) and file_metadata["processing_status"] == "error": + raise RuntimeError(f"File processing failed: {file_metadata['error_message']}") + elif hasattr(file_metadata, "processing_status") and file_metadata.processing_status == "error": raise RuntimeError(f"File processing failed: {file_metadata.error_message}") + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() return file_metadata diff --git a/tests/test_sources.py b/tests/test_sources.py index 39eb82ae..e4d14d69 100644 --- a/tests/test_sources.py +++ b/tests/test_sources.py @@ -5,11 +5,13 @@ import tempfile import threading import time from datetime import datetime, timedelta +from typing import Any import pytest from dotenv import load_dotenv -from letta_client import CreateBlock, Letta as LettaSDKClient, LettaRequest, MessageCreate as ClientMessageCreate -from letta_client.types import AgentState +from letta_client import Letta as LettaSDKClient +from letta_client.types import CreateBlockParam +from letta_client.types.agent_state import AgentState from letta.constants import DEFAULT_ORG_ID, FILES_TOOLS from letta.helpers.pinecone_utils import should_use_pinecone @@ -27,16 +29,17 @@ SERVER_PORT = 8283 def get_raw_system_message(client: LettaSDKClient, agent_id: str) -> str: """Helper function to get the raw system message from an agent's preview payload.""" - raw_payload = client.agents.messages.preview_raw_payload( - agent_id=agent_id, - request=LettaRequest( - messages=[ - ClientMessageCreate( - role="user", - content="Testing", - ) + raw_payload = client.post( + f"/v1/agents/{agent_id}/messages/preview-raw-payload", + cast_to=dict[str, Any], + body={ + "messages": [ + { + "role": "user", + "content": "Testing", + } ], - ), + }, ) return raw_payload["messages"][0]["content"] @@ -44,8 +47,8 @@ def get_raw_system_message(client: LettaSDKClient, agent_id: str) -> str: @pytest.fixture(autouse=True) def clear_sources(client: LettaSDKClient): # Clear existing sources - for source in client.sources.list(): - client.sources.delete(source_id=source.id) + for source in list(client.folders.list()): + client.folders.delete(folder_id=source.id) def run_server(): @@ -67,21 +70,21 @@ def client() -> LettaSDKClient: thread.start() wait_for_server(server_url) print("Running client tests with server:", server_url) - client = LettaSDKClient(base_url=server_url, token=None) + client = LettaSDKClient(base_url=server_url) client.tools.upsert_base_tools() yield client @pytest.fixture def agent_state(disable_pinecone, client: LettaSDKClient): - open_file_tool = client.tools.list(name="open_files")[0] - search_files_tool = client.tools.list(name="semantic_search_files")[0] - grep_tool = client.tools.list(name="grep_files")[0] + open_file_tool = list(client.tools.list(name="open_files"))[0] + search_files_tool = list(client.tools.list(name="semantic_search_files"))[0] + grep_tool = list(client.tools.list(name="grep_files"))[0] agent_state = client.agents.create( name="test_sources_agent", memory_blocks=[ - CreateBlock( + CreateBlockParam( label="human", value="username: sarah", ), @@ -101,7 +104,7 @@ def test_auto_attach_detach_files_tools(disable_pinecone, disable_turbopuffer, c # Create agent with basic configuration agent = client.agents.create( memory_blocks=[ - CreateBlock(label="human", value="username: sarah"), + CreateBlockParam(label="human", value="username: sarah"), ], model="openai/gpt-4o-mini", embedding="openai/text-embedding-3-small", @@ -125,28 +128,32 @@ def test_auto_attach_detach_files_tools(disable_pinecone, disable_turbopuffer, c assert_no_file_tools(agent) # Create and attach first source - source_1 = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") - assert len(client.sources.list()) == 1 + source_1 = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") + assert len(list(client.folders.list())) == 1 - agent = client.agents.sources.attach(source_id=source_1.id, agent_id=agent.id) - assert len(client.agents.retrieve(agent_id=agent.id).sources) == 1 + client.agents.folders.attach(folder_id=source_1.id, agent_id=agent.id) + agent = client.agents.retrieve(agent_id=agent.id, include=["agent.sources", "agent.tools"]) + assert len(agent.sources) == 1 assert_file_tools_present(agent, set(FILES_TOOLS)) # Create and attach second source - source_2 = client.sources.create(name="another_test_source", embedding="openai/text-embedding-3-small") - assert len(client.sources.list()) == 2 + source_2 = client.folders.create(name="another_test_source", embedding="openai/text-embedding-3-small") + assert len(list(client.folders.list())) == 2 - agent = client.agents.sources.attach(source_id=source_2.id, agent_id=agent.id) - assert len(client.agents.retrieve(agent_id=agent.id).sources) == 2 + client.agents.folders.attach(folder_id=source_2.id, agent_id=agent.id) + agent = client.agents.retrieve(agent_id=agent.id, include=["agent.sources", "agent.tools"]) + assert len(agent.sources) == 2 # File tools should remain after attaching second source assert_file_tools_present(agent, set(FILES_TOOLS)) # Detach second source - tools should remain (first source still attached) - agent = client.agents.sources.detach(source_id=source_2.id, agent_id=agent.id) + client.agents.folders.detach(folder_id=source_2.id, agent_id=agent.id) + agent = client.agents.retrieve(agent_id=agent.id, include=["agent.sources", "agent.tools"]) assert_file_tools_present(agent, set(FILES_TOOLS)) # Detach first source - all file tools should be removed - agent = client.agents.sources.detach(source_id=source_1.id, agent_id=agent.id) + client.agents.folders.detach(folder_id=source_1.id, agent_id=agent.id) + agent = client.agents.retrieve(agent_id=agent.id, include=["agent.sources", "agent.tools"]) assert_no_file_tools(agent) @@ -185,22 +192,22 @@ def test_file_upload_creates_source_blocks_correctly( settings.mistral_api_key = None # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") - assert len(client.sources.list()) == 1 + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") + assert len(list(client.folders.list())) == 1 # Attach - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Upload the file upload_file_and_wait(client, source.id, file_path) # Get uploaded files - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id # Check that blocks were created - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 1 assert any(expected_value in b.value for b in blocks) @@ -217,10 +224,10 @@ def test_file_upload_creates_source_blocks_correctly( assert 'status="open"' in raw_system_message # Remove file from source - client.sources.files.delete(source_id=source.id, file_id=files[0].id) + client.folders.files.delete(folder_id=source.id, file_id=files[0].id) # Confirm blocks were removed - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 0 assert not any(expected_value in b.value for b in blocks) @@ -243,8 +250,8 @@ def test_attach_existing_files_creates_source_blocks_correctly( disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState ): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") - assert len(client.sources.list()) == 1 + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") + assert len(list(client.folders.list())) == 1 # Load files into the source file_path = "tests/data/test.txt" @@ -253,12 +260,12 @@ def test_attach_existing_files_creates_source_blocks_correctly( upload_file_and_wait(client, source.id, file_path) # Get the first file with pagination - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id # Attach after uploading the file - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) raw_system_message = get_raw_system_message(client, agent_state.id) # Assert that the expected chunk is in the raw system message @@ -284,17 +291,17 @@ def test_attach_existing_files_creates_source_blocks_correctly( assert expected_chunk in raw_system_message # Get the agent state, check blocks exist - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 1 assert any("test" in b.value for b in blocks) assert any(b.value.startswith("[Viewing file start") for b in blocks) # Detach the source - client.agents.sources.detach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.detach(folder_id=source.id, agent_id=agent_state.id) # Get the agent state, check blocks do NOT exist - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 0 assert not any("test" in b.value for b in blocks) @@ -310,10 +317,10 @@ def test_delete_source_removes_source_blocks_correctly( disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState ): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") - assert len(client.sources.list()) == 1 + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") + assert len(list(client.folders.list())) == 1 - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) raw_system_message = get_raw_system_message(client, agent_state.id) assert "test_source" in raw_system_message assert "" in raw_system_message @@ -347,20 +354,20 @@ def test_delete_source_removes_source_blocks_correctly( assert expected_chunk in raw_system_message # Get the agent state, check blocks exist - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 1 assert any("test" in b.value for b in blocks) # Remove file from source - client.sources.delete(source_id=source.id) + client.folders.delete(folder_id=source.id) raw_system_message_after_detach = get_raw_system_message(client, agent_state.id) assert expected_chunk not in raw_system_message_after_detach assert "test_source" not in raw_system_message_after_detach assert "" not in raw_system_message_after_detach # Get the agent state, check blocks do NOT exist - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 0 assert not any("test" in b.value for b in blocks) @@ -368,13 +375,13 @@ def test_delete_source_removes_source_blocks_correctly( def test_agent_uses_open_close_file_correctly(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") - sources_list = client.sources.list() + sources_list = list(client.folders.list()) assert len(sources_list) == 1 # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Load files into the source file_path = "tests/data/long_test.txt" @@ -383,13 +390,13 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, disable_turbopuf upload_file_and_wait(client, source.id, file_path) # Get uploaded files - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id file = files[0] # Check that file is opened initially - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks print(f"Agent has {len(blocks)} file block(s)") if blocks: @@ -414,8 +421,9 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, disable_turbopuf print(open_response1.messages) # Check that file is opened - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks + assert len(blocks) == 1 old_value = blocks[0].value old_content_length = len(old_value) print(f"File content length after first open: {old_content_length} characters") @@ -443,7 +451,7 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, disable_turbopuf # Check that file is opened, but for different range print("Verifying file is opened with second range...") - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks new_value = blocks[0].value new_content_length = len(new_value) @@ -471,13 +479,13 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, disable_turbopuf def test_agent_uses_search_files_correctly(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") - sources_list = client.sources.list() + sources_list = list(client.folders.list()) assert len(sources_list) == 1 # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Load files into the source file_path = "tests/data/long_test.txt" @@ -485,10 +493,10 @@ def test_agent_uses_search_files_correctly(disable_pinecone, disable_turbopuffer # Upload the files file_metadata = upload_file_and_wait(client, source.id, file_path) - print(f"File uploaded and processed: {file_metadata.file_name}") + print(f"File uploaded and processed: {file_metadata['file_name']}") # Get uploaded files - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id @@ -517,13 +525,13 @@ def test_agent_uses_search_files_correctly(disable_pinecone, disable_turbopuffer def test_agent_uses_grep_correctly_basic(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") - sources_list = client.sources.list() + sources_list = list(client.folders.list()) assert len(sources_list) == 1 # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Load files into the source file_path = "tests/data/long_test.txt" @@ -531,10 +539,12 @@ def test_agent_uses_grep_correctly_basic(disable_pinecone, disable_turbopuffer, # Upload the files file_metadata = upload_file_and_wait(client, source.id, file_path) - print(f"File uploaded and processed: {file_metadata.file_name}") + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() + print(f"File uploaded and processed: {file_metadata['file_name']}") # Get uploaded files - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id @@ -559,13 +569,13 @@ def test_agent_uses_grep_correctly_basic(disable_pinecone, disable_turbopuffer, def test_agent_uses_grep_correctly_advanced(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") - sources_list = client.sources.list() + sources_list = list(client.folders.list()) assert len(sources_list) == 1 # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Load files into the source file_path = "tests/data/list_tools.json" @@ -573,10 +583,12 @@ def test_agent_uses_grep_correctly_advanced(disable_pinecone, disable_turbopuffe # Upload the files file_metadata = upload_file_and_wait(client, source.id, file_path) - print(f"File uploaded and processed: {file_metadata.file_name}") + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() + print(f"File uploaded and processed: {file_metadata['file_name']}") # Get uploaded files - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id @@ -608,15 +620,15 @@ def test_agent_uses_grep_correctly_advanced(disable_pinecone, disable_turbopuffe def test_create_agent_with_source_ids_creates_source_blocks_correctly(disable_pinecone, disable_turbopuffer, client: LettaSDKClient): """Test that creating an agent with source_ids parameter correctly creates source blocks.""" # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") - assert len(client.sources.list()) == 1 + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") + assert len(list(client.folders.list())) == 1 # Upload a file to the source before attaching file_path = "tests/data/long_test.txt" upload_file_and_wait(client, source.id, file_path) # Get uploaded files to verify - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id @@ -624,7 +636,7 @@ def test_create_agent_with_source_ids_creates_source_blocks_correctly(disable_pi temp_agent_state = client.agents.create( name="test_agent_with_sources", memory_blocks=[ - CreateBlock( + CreateBlockParam( label="human", value="username: sarah", ), @@ -650,13 +662,13 @@ def test_create_agent_with_source_ids_creates_source_blocks_correctly(disable_pi def test_view_ranges_have_metadata(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") - sources_list = client.sources.list() + sources_list = list(client.folders.list()) assert len(sources_list) == 1 # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Load files into the source file_path = "tests/data/1_to_100.py" @@ -665,13 +677,13 @@ def test_view_ranges_have_metadata(disable_pinecone, disable_turbopuffer, client upload_file_and_wait(client, source.id, file_path) # Get uploaded files - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].source_id == source.id file = files[0] # Check that file is opened initially - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 1 block = blocks[0] @@ -693,7 +705,7 @@ def test_view_ranges_have_metadata(disable_pinecone, disable_turbopuffer, client print(open_response.messages) # Check that file is opened correctly - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) blocks = agent_state.memory.file_blocks assert len(blocks) == 1 block = blocks[0] @@ -714,22 +726,22 @@ def test_view_ranges_have_metadata(disable_pinecone, disable_turbopuffer, client def test_duplicate_file_renaming(disable_pinecone, disable_turbopuffer, client: LettaSDKClient): """Test that duplicate files are renamed with count-based suffixes (e.g., file.txt, file (1).txt, file (2).txt)""" # Create a new source - source = client.sources.create(name="test_duplicate_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_duplicate_source", embedding="openai/text-embedding-3-small") # Upload the same file three times file_path = "tests/data/test.txt" with open(file_path, "rb") as f: - first_file = client.sources.files.upload(source_id=source.id, file=f) + first_file = client.folders.files.upload(folder_id=source.id, file=f) with open(file_path, "rb") as f: - second_file = client.sources.files.upload(source_id=source.id, file=f) + second_file = client.folders.files.upload(folder_id=source.id, file=f) with open(file_path, "rb") as f: - third_file = client.sources.files.upload(source_id=source.id, file=f) + third_file = client.folders.files.upload(folder_id=source.id, file=f) # Get all uploaded files - files = client.sources.files.list(source_id=source.id, limit=10) + files = list(client.folders.files.list(folder_id=source.id, limit=10)) assert len(files) == 3, f"Expected 3 files, got {len(files)}" # Sort files by creation time to ensure predictable order @@ -753,13 +765,13 @@ def test_duplicate_file_renaming(disable_pinecone, disable_turbopuffer, client: def test_duplicate_file_handling_replace(disable_pinecone, disable_turbopuffer, client: LettaSDKClient): """Test that DuplicateFileHandling.REPLACE replaces existing files with same name""" # Create a new source - source = client.sources.create(name="test_replace_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_replace_source", embedding="openai/text-embedding-3-small") # Create agent and attach source to test memory blocks agent_state = client.agents.create( name="test_replace_agent", memory_blocks=[ - CreateBlock(label="human", value="username: sarah"), + CreateBlockParam(label="human", value="username: sarah"), ], model="openai/gpt-4o-mini", embedding="openai/text-embedding-3-small", @@ -778,13 +790,13 @@ def test_duplicate_file_handling_replace(disable_pinecone, disable_turbopuffer, upload_file_and_wait(client, source.id, temp_file_path) # Verify original file was uploaded - files = client.sources.files.list(source_id=source.id, limit=10) + files = list(client.folders.files.list(folder_id=source.id, limit=10)) assert len(files) == 1, f"Expected 1 file, got {len(files)}" original_file = files[0] assert original_file.original_file_name == temp_filename # Get agent state and verify original content is in memory blocks - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) file_blocks = agent_state.memory.file_blocks assert len(file_blocks) == 1, f"Expected 1 file block, got {len(file_blocks)}" original_block_content = file_blocks[0].value @@ -801,7 +813,7 @@ def test_duplicate_file_handling_replace(disable_pinecone, disable_turbopuffer, replacement_file = upload_file_and_wait(client, source.id, temp_file_path, duplicate_handling=DuplicateFileHandling.REPLACE) # Verify we still have only 1 file (replacement, not addition) - files_after_replace = client.sources.files.list(source_id=source.id, limit=10) + files_after_replace = list(client.folders.files.list(folder_id=source.id, limit=10)) assert len(files_after_replace) == 1, f"Expected 1 file after replacement, got {len(files_after_replace)}" replaced_file = files_after_replace[0] @@ -814,7 +826,7 @@ def test_duplicate_file_handling_replace(disable_pinecone, disable_turbopuffer, assert replaced_file.id != original_file.id, "Replacement file should have different ID" # Verify agent memory blocks contain replacement content - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) updated_file_blocks = agent_state.memory.file_blocks assert len(updated_file_blocks) == 1, f"Expected 1 file block after replacement, got {len(updated_file_blocks)}" @@ -838,11 +850,11 @@ def test_upload_file_with_custom_name(disable_pinecone, disable_turbopuffer, cli agent_state = client.agents.create( name="test_agent_custom_name", memory_blocks=[ - CreateBlock( + CreateBlockParam( label="persona", value="I am a helpful assistant", ), - CreateBlock( + CreateBlockParam( label="human", value="The user is a developer", ), @@ -852,10 +864,10 @@ def test_upload_file_with_custom_name(disable_pinecone, disable_turbopuffer, cli ) # Create source - source = client.sources.create(name="test_source_custom_name", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source_custom_name", embedding="openai/text-embedding-3-small") # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Create a temporary file with specific content import tempfile @@ -869,19 +881,21 @@ def test_upload_file_with_custom_name(disable_pinecone, disable_turbopuffer, cli # Upload file with custom name custom_name = "my_custom_file_name.txt" file_metadata = upload_file_and_wait(client, source.id, temp_file_path, name=custom_name) + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() # Verify the file uses the custom name - assert file_metadata.file_name == custom_name - assert file_metadata.original_file_name == custom_name + assert file_metadata["file_name"] == custom_name + assert file_metadata["original_file_name"] == custom_name # Verify file appears in source files list with custom name - files = client.sources.files.list(source_id=source.id, limit=1) + files = list(client.folders.files.list(folder_id=source.id, limit=1)) assert len(files) == 1 assert files[0].file_name == custom_name assert files[0].original_file_name == custom_name # Verify the custom name is used in file blocks - agent_state = client.agents.retrieve(agent_id=agent_state.id) + agent_state = client.agents.retrieve(agent_id=agent_state.id, include=["agent.blocks"]) file_blocks = agent_state.memory.file_blocks assert len(file_blocks) == 1 # Check that the custom name appears in the block label @@ -897,11 +911,13 @@ def test_upload_file_with_custom_name(disable_pinecone, disable_turbopuffer, cli # Upload same file with different custom name should succeed different_custom_name = "folder_a/folder_b/another_custom_name.txt" file_metadata2 = upload_file_and_wait(client, source.id, temp_file_path, name=different_custom_name) - assert file_metadata2.file_name == different_custom_name - assert file_metadata2.original_file_name == different_custom_name + if not isinstance(file_metadata2, dict): + file_metadata2 = file_metadata2.model_dump() + assert file_metadata2["file_name"] == different_custom_name + assert file_metadata2["original_file_name"] == different_custom_name # Verify both files exist - files = client.sources.files.list(source_id=source.id, limit=10) + files = list(client.folders.files.list(folder_id=source.id, limit=10)) assert len(files) == 2 file_names = {f.file_name for f in files} assert custom_name in file_names @@ -917,7 +933,7 @@ def test_open_files_schema_descriptions(disable_pinecone, disable_turbopuffer, c """Test that open_files tool schema contains correct descriptions from docstring""" # Get the open_files tool - tools = client.tools.list(name="open_files") + tools = list(client.tools.list(name="open_files")) assert len(tools) == 1, "Expected exactly one open_files tool" open_files_tool = tools[0] @@ -1000,7 +1016,7 @@ def test_grep_files_schema_descriptions(disable_pinecone, disable_turbopuffer, c """Test that grep_files tool schema contains correct descriptions from docstring""" # Get the grep_files tool - tools = client.tools.list(name="grep_files") + tools = list(client.tools.list(name="grep_files")) assert len(tools) == 1, "Expected exactly one grep_files tool" grep_files_tool = tools[0] @@ -1085,17 +1101,19 @@ def test_grep_files_schema_descriptions(disable_pinecone, disable_turbopuffer, c def test_agent_open_file(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): """Test client.agents.open_file() function""" # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Upload a file file_path = "tests/data/test.txt" file_metadata = upload_file_and_wait(client, source.id, file_path) + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() # Basic test open_file function - closed_files = client.agents.files.open(agent_id=agent_state.id, file_id=file_metadata.id) + closed_files = client.agents.files.open(agent_id=agent_state.id, file_id=file_metadata["id"]) assert len(closed_files) == 0 system = get_raw_system_message(client, agent_state.id) @@ -1106,20 +1124,22 @@ def test_agent_open_file(disable_pinecone, disable_turbopuffer, client: LettaSDK def test_agent_close_file(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): """Test client.agents.close_file() function""" # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Upload a file file_path = "tests/data/test.txt" file_metadata = upload_file_and_wait(client, source.id, file_path) + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() # First open the file - client.agents.files.open(agent_id=agent_state.id, file_id=file_metadata.id) + client.agents.files.open(agent_id=agent_state.id, file_id=file_metadata["id"]) # Test close_file function - client.agents.files.close(agent_id=agent_state.id, file_id=file_metadata.id) + client.agents.files.close(agent_id=agent_state.id, file_id=file_metadata["id"]) system = get_raw_system_message(client, agent_state.id) assert '' in system @@ -1128,19 +1148,21 @@ def test_agent_close_file(disable_pinecone, disable_turbopuffer, client: LettaSD def test_agent_close_all_open_files(disable_pinecone, disable_turbopuffer, client: LettaSDKClient, agent_state: AgentState): """Test client.agents.close_all_open_files() function""" # Create a new source - source = client.sources.create(name="test_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_source", embedding="openai/text-embedding-3-small") # Attach source to agent - client.agents.sources.attach(source_id=source.id, agent_id=agent_state.id) + client.agents.folders.attach(folder_id=source.id, agent_id=agent_state.id) # Upload multiple files file_paths = ["tests/data/test.txt", "tests/data/test.md"] file_metadatas = [] for file_path in file_paths: file_metadata = upload_file_and_wait(client, source.id, file_path) + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() file_metadatas.append(file_metadata) # Open each file - client.agents.files.open(agent_id=agent_state.id, file_id=file_metadata.id) + client.agents.files.open(agent_id=agent_state.id, file_id=file_metadata["id"]) system = get_raw_system_message(client, agent_state.id) assert ' 0: - assert file_metadata.chunks_embedded == file_metadata.total_chunks, ( - f"File {file_metadata.file_name} should have all chunks embedded: {file_metadata.chunks_embedded}/{file_metadata.total_chunks}" + if file_metadata["total_chunks"] and file_metadata["total_chunks"] > 0: + assert file_metadata["chunks_embedded"] == file_metadata["total_chunks"], ( + f"File {file_metadata['file_name']} should have all chunks embedded: {file_metadata['chunks_embedded']}/{file_metadata['total_chunks']}" ) # cleanup - client.sources.delete(source_id=source.id) + client.folders.delete(folder_id=source.id) def test_pinecone_lifecycle_file_and_source_deletion(disable_turbopuffer, client: LettaSDKClient): @@ -1343,7 +1377,7 @@ def test_pinecone_lifecycle_file_and_source_deletion(disable_turbopuffer, client print("Testing Pinecone file and source deletion lifecycle") # Create source - source = client.sources.create(name="test_lifecycle_source", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_lifecycle_source", embedding="openai/text-embedding-3-small") # Upload multiple files and wait for processing file_paths = ["tests/data/test.txt", "tests/data/test.md"] @@ -1364,7 +1398,7 @@ def test_pinecone_lifecycle_file_and_source_deletion(disable_turbopuffer, client print(f"Found {len(records_before)} records for file before deletion") # Delete the file - client.sources.files.delete(source_id=source.id, file_id=file_to_delete.id) + client.folders.files.delete(folder_id=source.id, file_id=file_to_delete.id) # Allow time for deletion to propagate time.sleep(2) @@ -1386,7 +1420,7 @@ def test_pinecone_lifecycle_file_and_source_deletion(disable_turbopuffer, client print(f"Found {records_before} records for remaining files before source deletion") # Delete the entire source - client.sources.delete(source_id=source.id) + client.folders.delete(folder_id=source.id) # Allow time for deletion to propagate time.sleep(3) @@ -1413,14 +1447,14 @@ def test_turbopuffer_search_files_tool(disable_pinecone, client: LettaSDKClient) agent = client.agents.create( name="test_turbopuffer_agent", memory_blocks=[ - CreateBlock(label="human", value="username: testuser"), + CreateBlockParam(label="human", value="username: testuser"), ], model="openai/gpt-4o-mini", embedding="openai/text-embedding-3-small", ) - source = client.sources.create(name="test_turbopuffer_source", embedding="openai/text-embedding-3-small") - client.agents.sources.attach(source_id=source.id, agent_id=agent.id) + source = client.folders.create(name="test_turbopuffer_source", embedding="openai/text-embedding-3-small") + client.agents.folders.attach(folder_id=source.id, agent_id=agent.id) file_path = "tests/data/long_test.txt" upload_file_and_wait(client, source.id, file_path) @@ -1445,40 +1479,44 @@ def test_turbopuffer_search_files_tool(disable_pinecone, client: LettaSDKClient) ) client.agents.delete(agent_id=agent.id) - client.sources.delete(source_id=source.id) + client.folders.delete(folder_id=source.id) def test_turbopuffer_file_processing_status(disable_pinecone, client: LettaSDKClient): """Test that file processing completes successfully with Turbopuffer""" print("Testing Turbopuffer file processing status") - source = client.sources.create(name="test_tpuf_file_status", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_tpuf_file_status", embedding="openai/text-embedding-3-small") file_paths = ["tests/data/long_test.txt", "tests/data/test.md"] uploaded_files = [] for file_path in file_paths: file_metadata = upload_file_and_wait(client, source.id, file_path) uploaded_files.append(file_metadata) - assert file_metadata.processing_status == "completed", f"File {file_path} should be completed" + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() + assert file_metadata["processing_status"] == "completed", f"File {file_path} should be completed" - files_list = client.sources.files.list(source_id=source.id, limit=100) + files_list = client.folders.files.list(folder_id=source.id, limit=100).items assert len(files_list) == len(uploaded_files), f"Expected {len(uploaded_files)} files, got {len(files_list)}" for file_metadata in files_list: - assert file_metadata.processing_status == "completed", f"File {file_metadata.file_name} should show completed status" + if not isinstance(file_metadata, dict): + file_metadata = file_metadata.model_dump() + assert file_metadata["processing_status"] == "completed", f"File {file_metadata['file_name']} should show completed status" - if file_metadata.total_chunks and file_metadata.total_chunks > 0: - assert file_metadata.chunks_embedded == file_metadata.total_chunks, ( - f"File {file_metadata.file_name} should have all chunks embedded: {file_metadata.chunks_embedded}/{file_metadata.total_chunks}" + if file_metadata["total_chunks"] and file_metadata["total_chunks"] > 0: + assert file_metadata["chunks_embedded"] == file_metadata["total_chunks"], ( + f"File {file_metadata['file_name']} should have all chunks embedded: {file_metadata['chunks_embedded']}/{file_metadata['total_chunks']}" ) - client.sources.delete(source_id=source.id) + client.folders.delete(folder_id=source.id) def test_turbopuffer_lifecycle_file_and_source_deletion(disable_pinecone, client: LettaSDKClient): """Test that file and source deletion removes records from Turbopuffer""" - source = client.sources.create(name="test_tpuf_lifecycle", embedding="openai/text-embedding-3-small") + source = client.folders.create(name="test_tpuf_lifecycle", embedding="openai/text-embedding-3-small") file_paths = ["tests/data/test.txt", "tests/data/test.md"] uploaded_files = [] @@ -1495,19 +1533,19 @@ def test_turbopuffer_lifecycle_file_and_source_deletion(disable_pinecone, client passages_before = asyncio.run( tpuf_client.query_file_passages( - source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_to_delete.id, top_k=100 + source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_to_delete["id"], top_k=100 ) ) print(f"Found {len(passages_before)} passages for file before deletion") assert len(passages_before) > 0, "Should have passages before deletion" - client.sources.files.delete(source_id=source.id, file_id=file_to_delete.id) + client.folders.files.delete(folder_id=source.id, file_id=file_to_delete["id"]) time.sleep(2) passages_after = asyncio.run( tpuf_client.query_file_passages( - source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_to_delete.id, top_k=100 + source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_to_delete["id"], top_k=100 ) ) print(f"Found {len(passages_after)} passages for file after deletion") @@ -1518,7 +1556,7 @@ def test_turbopuffer_lifecycle_file_and_source_deletion(disable_pinecone, client for file_metadata in uploaded_files[1:]: passages = asyncio.run( tpuf_client.query_file_passages( - source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_metadata.id, top_k=100 + source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_metadata["id"], top_k=100 ) ) remaining_passages_before.extend(passages) @@ -1526,7 +1564,7 @@ def test_turbopuffer_lifecycle_file_and_source_deletion(disable_pinecone, client print(f"Found {len(remaining_passages_before)} passages for remaining files before source deletion") assert len(remaining_passages_before) > 0, "Should have passages for remaining files" - client.sources.delete(source_id=source.id) + client.folders.delete(folder_id=source.id) time.sleep(3) @@ -1535,7 +1573,7 @@ def test_turbopuffer_lifecycle_file_and_source_deletion(disable_pinecone, client try: passages = asyncio.run( tpuf_client.query_file_passages( - source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_metadata.id, top_k=100 + source_ids=[source.id], organization_id=user.organization_id, actor=user, file_id=file_metadata["id"], top_k=100 ) ) remaining_passages_after.extend(passages) @@ -1550,8 +1588,8 @@ def test_turbopuffer_lifecycle_file_and_source_deletion(disable_pinecone, client def test_turbopuffer_multiple_sources(disable_pinecone, client: LettaSDKClient): """Test that Turbopuffer correctly isolates passages by source in org-scoped namespace""" - source1 = client.sources.create(name="test_tpuf_source1", embedding="openai/text-embedding-3-small") - source2 = client.sources.create(name="test_tpuf_source2", embedding="openai/text-embedding-3-small") + source1 = client.folders.create(name="test_tpuf_source1", embedding="openai/text-embedding-3-small") + source2 = client.folders.create(name="test_tpuf_source2", embedding="openai/text-embedding-3-small") file1_metadata = upload_file_and_wait(client, source1.id, "tests/data/test.txt") file2_metadata = upload_file_and_wait(client, source2.id, "tests/data/test.md") @@ -1574,15 +1612,15 @@ def test_turbopuffer_multiple_sources(disable_pinecone, client: LettaSDKClient): assert len(source2_passages) > 0, "Source2 should have passages" for passage, _, _ in source1_passages: - assert passage.source_id == source1.id, f"Passage should belong to source1, but has source_id={passage.source_id}" - assert passage.file_id == file1_metadata.id, f"Passage should belong to file1, but has file_id={passage.file_id}" + assert passage.source_id == source1.id, f"Passage should belong to source1, but has folder_id={passage.source_id}" + assert passage.file_id == file1_metadata["id"], f"Passage should belong to file1, but has file_id={passage.file_id}" for passage, _, _ in source2_passages: - assert passage.source_id == source2.id, f"Passage should belong to source2, but has source_id={passage.source_id}" - assert passage.file_id == file2_metadata.id, f"Passage should belong to file2, but has file_id={passage.file_id}" + assert passage.source_id == source2.id, f"Passage should belong to source2, but has folder_id={passage.source_id}" + assert passage.file_id == file2_metadata["id"], f"Passage should belong to file2, but has file_id={passage.file_id}" # delete source1 and verify source2 is unaffected - client.sources.delete(source_id=source1.id) + client.folders.delete(folder_id=source1.id) time.sleep(2) source2_passages_after = asyncio.run( @@ -1593,7 +1631,7 @@ def test_turbopuffer_multiple_sources(disable_pinecone, client: LettaSDKClient): f"Source2 should still have all passages after source1 deletion: {len(source2_passages_after)} vs {len(source2_passages)}" ) - client.sources.delete(source_id=source2.id) + client.folders.delete(folder_id=source2.id) # --- End Turbopuffer Tests ---