mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(sdk): guard empty binary reads with empty-content warning (#3675)
Fixes #3664 --- Empty binary file reads now return the same `"System reminder: File exists but has empty contents"` warning that empty text reads already produce, instead of silently emitting a degenerate `{"type": "file", "base64": "", ...}` content block with `status="success"`. Previously the `check_empty_content` guard only ran on the text branch of `_handle_read_result`, so empty binary reads fell through to a degenerate content block. The fix lifts the guard above the encoding/type routing into a single check, so empty files return the warning uniformly whether the extension is a known binary type (e.g. `.pdf`), unmapped (e.g. `.docx`), or text. ## Social handles (optional) LinkedIn: https://linkedin.com/in/ninaadrao --------- Co-authored-by: Mason Daugherty <mason@langchain.dev> Co-authored-by: Mason Daugherty <github@mdrxy.com>
This commit is contained in:
@@ -949,9 +949,21 @@ class FilesystemMiddleware(AgentMiddleware[FilesystemState, ContextT, ResponseT]
|
||||
encoding = read_result.file_data.get("encoding", "utf-8")
|
||||
content = read_result.file_data["content"]
|
||||
|
||||
# Empty files get a uniform warning regardless of encoding/type, so
|
||||
# check before routing to avoid a degenerate empty content block for
|
||||
# binary reads.
|
||||
empty_msg = check_empty_content(content)
|
||||
if empty_msg:
|
||||
return ToolMessage(
|
||||
content=empty_msg,
|
||||
name="read_file",
|
||||
tool_call_id=tool_call_id,
|
||||
status="success",
|
||||
)
|
||||
|
||||
# Route on the backend-declared encoding first: `"base64"` means the
|
||||
# content is binary and must never be line-numbered as text, even
|
||||
# when the extension is absent from `_EXTENSION_TO_FILE_TYPE` (#3657).
|
||||
# when the extension is absent from `_EXTENSION_TO_FILE_TYPE`.
|
||||
# The extension map is only consulted to pick the multimodal block
|
||||
# type; unknown binary extensions fall back to the generic `"file"`.
|
||||
if encoding == "base64" or file_type != "text":
|
||||
@@ -965,15 +977,6 @@ class FilesystemMiddleware(AgentMiddleware[FilesystemState, ContextT, ResponseT]
|
||||
status="success",
|
||||
)
|
||||
|
||||
empty_msg = check_empty_content(content)
|
||||
if empty_msg:
|
||||
return ToolMessage(
|
||||
content=empty_msg,
|
||||
name="read_file",
|
||||
tool_call_id=tool_call_id,
|
||||
status="success",
|
||||
)
|
||||
|
||||
content = format_content_with_line_numbers(content, start_line=offset + 1)
|
||||
# `limit` already bounded raw source lines at the backend; do not
|
||||
# re-truncate by row count here, or wrapped continuation rows would
|
||||
|
||||
@@ -1295,6 +1295,66 @@ class TestFilesystemMiddleware:
|
||||
expected_mime = mimetypes.guess_type("file.docx")[0] or "application/octet-stream"
|
||||
assert result.content[0]["mime_type"] == expected_mime
|
||||
|
||||
def test_read_file_empty_mapped_binary_returns_empty_content_warning(self):
|
||||
"""Empty reads of mapped binary extensions (e.g. .pdf) return the empty-file warning (#3664)."""
|
||||
|
||||
class EmptyPdfBackend(StateBackend):
|
||||
def read(self, path, *, offset=0, limit=100):
|
||||
return ReadResult(
|
||||
file_data={
|
||||
"content": "",
|
||||
"encoding": "base64",
|
||||
}
|
||||
)
|
||||
|
||||
middleware = FilesystemMiddleware(backend=EmptyPdfBackend())
|
||||
state = FilesystemState(messages=[], files={})
|
||||
runtime = ToolRuntime(
|
||||
state=state,
|
||||
context=None,
|
||||
tool_call_id="empty-pdf-1",
|
||||
store=None,
|
||||
stream_writer=lambda _: None,
|
||||
config={},
|
||||
)
|
||||
|
||||
read_file_tool = next(tool for tool in middleware.tools if tool.name == "read_file")
|
||||
result = read_file_tool.invoke({"file_path": "/docs/report.pdf", "runtime": runtime})
|
||||
|
||||
assert isinstance(result, ToolMessage)
|
||||
assert result.status == "success"
|
||||
assert result.content == EMPTY_CONTENT_WARNING
|
||||
|
||||
def test_read_file_empty_unmapped_binary_returns_empty_content_warning(self):
|
||||
"""Empty reads of unmapped binary extensions (e.g. .docx) return the empty-file warning (#3664)."""
|
||||
|
||||
class EmptyDocxBackend(StateBackend):
|
||||
def read(self, path, *, offset=0, limit=100):
|
||||
return ReadResult(
|
||||
file_data={
|
||||
"content": "",
|
||||
"encoding": "base64",
|
||||
}
|
||||
)
|
||||
|
||||
middleware = FilesystemMiddleware(backend=EmptyDocxBackend())
|
||||
state = FilesystemState(messages=[], files={})
|
||||
runtime = ToolRuntime(
|
||||
state=state,
|
||||
context=None,
|
||||
tool_call_id="empty-docx-1",
|
||||
store=None,
|
||||
stream_writer=lambda _: None,
|
||||
config={},
|
||||
)
|
||||
|
||||
read_file_tool = next(tool for tool in middleware.tools if tool.name == "read_file")
|
||||
result = read_file_tool.invoke({"file_path": "/docs/report.docx", "runtime": runtime})
|
||||
|
||||
assert isinstance(result, ToolMessage)
|
||||
assert result.status == "success"
|
||||
assert result.content == EMPTY_CONTENT_WARNING
|
||||
|
||||
def test_read_file_image_returns_error_when_download_fails(self):
|
||||
"""Image reads should return a clear backend error string."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user