fix: Fix 0 indexing for offset (#4086)
This commit is contained in:
@@ -21,8 +21,8 @@ async def open_files(agent_state: "AgentState", file_requests: List[FileOpenRequ
|
||||
|
||||
Open multiple files with different view ranges:
|
||||
file_requests = [
|
||||
FileOpenRequest(file_name="project_utils/config.py", offset=1, length=50), # Lines 1-50
|
||||
FileOpenRequest(file_name="project_utils/main.py", offset=100, length=100), # Lines 100-199
|
||||
FileOpenRequest(file_name="project_utils/config.py", offset=0, length=50), # Lines 1-50
|
||||
FileOpenRequest(file_name="project_utils/main.py", offset=100, length=100), # Lines 101-200
|
||||
FileOpenRequest(file_name="project_utils/utils.py") # Entire file
|
||||
]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class SearchTask(BaseModel):
|
||||
class FileOpenRequest(BaseModel):
|
||||
file_name: str = Field(description="Name of the file to open")
|
||||
offset: Optional[int] = Field(
|
||||
default=None, description="Optional starting line number (1-indexed). If not specified, starts from beginning of file."
|
||||
default=None, description="Optional offset for starting line number (0-indexed). If not specified, starts from beginning of file."
|
||||
)
|
||||
length: Optional[int] = Field(
|
||||
default=None, description="Optional number of lines to view from offset (inclusive). If not specified, views to end of file."
|
||||
|
||||
@@ -151,16 +151,16 @@ class LettaFileToolExecutor(ToolExecutor):
|
||||
offset = file_request.offset
|
||||
length = file_request.length
|
||||
|
||||
# Convert 1-indexed offset/length to 0-indexed start/end for LineChunker
|
||||
# Use 0-indexed offset/length directly for LineChunker
|
||||
start, end = None, None
|
||||
if offset is not None or length is not None:
|
||||
if offset is not None and offset < 1:
|
||||
raise ValueError(f"Offset for file {file_name} must be >= 1 (1-indexed), got {offset}")
|
||||
if offset is not None and offset < 0:
|
||||
raise ValueError(f"Offset for file {file_name} must be >= 0 (0-indexed), got {offset}")
|
||||
if length is not None and length < 1:
|
||||
raise ValueError(f"Length for file {file_name} must be >= 1, got {length}")
|
||||
|
||||
# Convert to 0-indexed for LineChunker
|
||||
start = (offset - 1) if offset is not None else None
|
||||
# Use offset directly as it's already 0-indexed
|
||||
start = offset if offset is not None else None
|
||||
if start is not None and length is not None:
|
||||
end = start + length
|
||||
else:
|
||||
@@ -193,7 +193,7 @@ class LettaFileToolExecutor(ToolExecutor):
|
||||
visible_content=visible_content,
|
||||
max_files_open=agent_state.max_files_open,
|
||||
start_line=start + 1 if start is not None else None, # convert to 1-indexed for user display
|
||||
end_line=end if end is not None else None, # end is already exclusive in slicing, so this is correct
|
||||
end_line=end if end is not None else None, # end is already exclusive, shows as 1-indexed inclusive
|
||||
)
|
||||
|
||||
opened_files.append(file_name)
|
||||
@@ -220,10 +220,14 @@ class LettaFileToolExecutor(ToolExecutor):
|
||||
for req in file_requests:
|
||||
previous_info = format_previous_range(req.file_name)
|
||||
if req.offset is not None and req.length is not None:
|
||||
end_line = req.offset + req.length - 1
|
||||
file_summaries.append(f"{req.file_name} (lines {req.offset}-{end_line}){previous_info}")
|
||||
# Display as 1-indexed for user readability: (offset+1) to (offset+length)
|
||||
start_line = req.offset + 1
|
||||
end_line = req.offset + req.length
|
||||
file_summaries.append(f"{req.file_name} (lines {start_line}-{end_line}){previous_info}")
|
||||
elif req.offset is not None:
|
||||
file_summaries.append(f"{req.file_name} (lines {req.offset}-end){previous_info}")
|
||||
# Display as 1-indexed
|
||||
start_line = req.offset + 1
|
||||
file_summaries.append(f"{req.file_name} (lines {start_line}-end){previous_info}")
|
||||
else:
|
||||
file_summaries.append(f"{req.file_name}{previous_info}")
|
||||
|
||||
|
||||
@@ -424,7 +424,7 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, client: LettaSDK
|
||||
assert initial_content_length > 10, f"Expected file content > 10 chars, got {initial_content_length}"
|
||||
|
||||
# Ask agent to open the file for a specific range using offset/length
|
||||
offset, length = 1, 5 # 1-indexed offset, 5 lines
|
||||
offset, length = 0, 5 # 0-indexed offset, 5 lines
|
||||
print(f"Requesting agent to open file with offset={offset}, length={length}")
|
||||
open_response1 = client.agents.messages.create(
|
||||
agent_id=agent_state.id,
|
||||
@@ -453,7 +453,7 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, client: LettaSDK
|
||||
assert "5: " in old_value, f"Expected line 5 to be present, got: {old_value}"
|
||||
|
||||
# Ask agent to open the file for a different range
|
||||
offset, length = 6, 5 # Different offset, same length
|
||||
offset, length = 5, 5 # Different offset, same length
|
||||
open_response2 = client.agents.messages.create(
|
||||
agent_id=agent_state.id,
|
||||
messages=[
|
||||
@@ -482,8 +482,8 @@ def test_agent_uses_open_close_file_correctly(disable_pinecone, client: LettaSDK
|
||||
assert "10: " in new_value, f"Expected line 10 to be present, got: {new_value}"
|
||||
|
||||
print(f"Comparing content ranges:")
|
||||
print(f" First range (offset=1, length=5): '{old_value}'")
|
||||
print(f" Second range (offset=6, length=5): '{new_value}'")
|
||||
print(f" First range (offset=0, length=5): '{old_value}'")
|
||||
print(f" Second range (offset=5, length=5): '{new_value}'")
|
||||
|
||||
assert new_value != old_value, f"Different view ranges should have different content. New: '{new_value}', Old: '{old_value}'"
|
||||
|
||||
@@ -703,7 +703,7 @@ def test_view_ranges_have_metadata(disable_pinecone, client: LettaSDKClient, age
|
||||
assert block.value.startswith("[Viewing file start (out of 100 lines)]")
|
||||
|
||||
# Open a specific range using offset/length
|
||||
offset = 50 # 1-indexed line 50
|
||||
offset = 49 # 0-indexed for line 50
|
||||
length = 5 # 5 lines (50-54)
|
||||
open_response = client.agents.messages.create(
|
||||
agent_id=agent_state.id,
|
||||
@@ -960,9 +960,9 @@ def test_open_files_schema_descriptions(disable_pinecone, client: LettaSDKClient
|
||||
# Check that examples are included
|
||||
assert "Examples:" in description
|
||||
assert 'FileOpenRequest(file_name="project_utils/config.py")' in description
|
||||
assert 'FileOpenRequest(file_name="project_utils/config.py", offset=1, length=50)' in description
|
||||
assert 'FileOpenRequest(file_name="project_utils/config.py", offset=0, length=50)' in description
|
||||
assert "# Lines 1-50" in description
|
||||
assert "# Lines 100-199" in description
|
||||
assert "# Lines 101-200" in description
|
||||
assert "# Entire file" in description
|
||||
assert "close_all_others=True" in description
|
||||
assert "View specific portions of large files (e.g. functions or definitions)" in description
|
||||
@@ -1009,7 +1009,7 @@ def test_open_files_schema_descriptions(disable_pinecone, client: LettaSDKClient
|
||||
# Check offset field
|
||||
assert "offset" in file_request_properties
|
||||
offset_prop = file_request_properties["offset"]
|
||||
expected_offset_desc = "Optional starting line number (1-indexed). If not specified, starts from beginning of file."
|
||||
expected_offset_desc = "Optional offset for starting line number (0-indexed). If not specified, starts from beginning of file."
|
||||
assert offset_prop["description"] == expected_offset_desc
|
||||
assert offset_prop["type"] == "integer"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user