mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-30 20:38:09 +00:00
fix(runtime): guide malformed write_file recovery (#3040)
* fix(runtime): guide malformed write_file recovery * fix(runtime): align write_file recovery guidance
This commit is contained in:
parent
872079b894
commit
e683ed6a76
@ -26,6 +26,11 @@ from langchain_core.messages import ToolMessage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Workaround for issue #2894: malformed write_file calls can carry huge Markdown
|
||||
# payloads in invalid tool-call args. Keep recovery error details short so the
|
||||
# synthetic ToolMessage does not echo large or malformed content back to the model.
|
||||
_MAX_RECOVERY_ERROR_DETAIL_LEN = 500
|
||||
|
||||
|
||||
class DanglingToolCallMiddleware(AgentMiddleware[AgentState]):
|
||||
"""Inserts placeholder ToolMessages for dangling tool calls before model invocation.
|
||||
@ -98,9 +103,25 @@ class DanglingToolCallMiddleware(AgentMiddleware[AgentState]):
|
||||
@staticmethod
|
||||
def _synthetic_tool_message_content(tool_call: dict) -> str:
|
||||
if tool_call.get("invalid"):
|
||||
name = tool_call.get("name")
|
||||
error = tool_call.get("error")
|
||||
if isinstance(error, str) and error:
|
||||
return f"[Tool call could not be executed because its arguments were invalid: {error}]"
|
||||
error_text = error[:_MAX_RECOVERY_ERROR_DETAIL_LEN] if isinstance(error, str) and error else ""
|
||||
# Workaround for issue #2894: malformed write_file calls can carry huge Markdown
|
||||
# payloads in invalid tool-call args. Keep recovery guidance actionable without
|
||||
# echoing large or malformed content back to the model.
|
||||
if name == "write_file":
|
||||
details = f" Parser error: {error_text}" if error_text else ""
|
||||
return (
|
||||
"[write_file failed before execution: the tool-call arguments were not valid JSON, "
|
||||
"so no file was written. This often happens when the model tries to write a very "
|
||||
"large Markdown file in a single tool call, especially when `content` contains "
|
||||
"unescaped quotes, inline JSON, backslashes, or code fences. Do not retry the same "
|
||||
"large `write_file` payload for this artifact; provide the report/content directly "
|
||||
"as normal assistant text in your next response. If a file write is still needed "
|
||||
f"later, split the file into smaller sections instead of one large payload.{details}]"
|
||||
)
|
||||
if error_text:
|
||||
return f"[Tool call could not be executed because its arguments were invalid: {error_text}]"
|
||||
return "[Tool call could not be executed because its arguments were invalid.]"
|
||||
return "[Tool call was interrupted and did not return a result.]"
|
||||
|
||||
|
||||
@ -333,8 +333,27 @@ class TestBuildPatchedMessagesPatching:
|
||||
assert patched[1].tool_call_id == "write_file:36"
|
||||
assert patched[1].name == "write_file"
|
||||
assert patched[1].status == "error"
|
||||
assert "write_file failed before execution" in patched[1].content
|
||||
assert "no file was written" in patched[1].content
|
||||
assert "very large Markdown file in a single tool call" in patched[1].content
|
||||
assert "Do not retry the same large `write_file` payload" in patched[1].content
|
||||
assert "split the file into smaller sections" in patched[1].content
|
||||
assert "normal assistant text" in patched[1].content
|
||||
assert "Failed to parse tool arguments" in patched[1].content
|
||||
assert 'bad {"json"}' not in patched[1].content
|
||||
|
||||
def test_non_write_file_invalid_tool_call_uses_generic_recovery_message(self):
|
||||
mw = DanglingToolCallMiddleware()
|
||||
msgs = [_ai_with_invalid_tool_calls([_invalid_tc(name="search", tc_id="search:1")])]
|
||||
|
||||
patched = mw._build_patched_messages(msgs)
|
||||
|
||||
assert patched is not None
|
||||
assert patched[1].tool_call_id == "search:1"
|
||||
assert patched[1].name == "search"
|
||||
assert "arguments were invalid" in patched[1].content
|
||||
assert "Failed to parse tool arguments" in patched[1].content
|
||||
assert "write_file failed before execution" not in patched[1].content
|
||||
|
||||
def test_valid_and_invalid_tool_calls_are_both_patched(self):
|
||||
mw = DanglingToolCallMiddleware()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user