From feb33e1f99d797880d64da99d5cb2165627309c3 Mon Sep 17 00:00:00 2001 From: Wu Shuwen Date: Mon, 31 Aug 2026 14:28:41 +0800 Subject: [PATCH] docs(tools): clarify edit_file selector exclusivity (#5598) * docs(tools): clarify edit selector exclusivity * docs(tools): streamline edit_file guidance --------- Co-authored-by: chengyongru --- nanobot/agent/tools/filesystem.py | 18 +++++++----------- nanobot/templates/agent/tool_contract.md | 2 +- tests/tools/test_tool_descriptions.py | 8 ++++++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/nanobot/agent/tools/filesystem.py b/nanobot/agent/tools/filesystem.py index 0b5b96975..b187cc5a1 100644 --- a/nanobot/agent/tools/filesystem.py +++ b/nanobot/agent/tools/filesystem.py @@ -861,8 +861,10 @@ def _best_window(old_text: str, content: str) -> tuple[float, int, list[str], li @tool_parameters( tool_parameters_schema( path=StringSchema("The file path to edit"), - old_text=StringSchema("The text to find and replace"), - new_text=StringSchema("The text to replace with"), + old_text=StringSchema("The text to find and replace; copy it from read_file."), + new_text=StringSchema( + "The replacement text; must differ from old_text for an existing file." + ), replace_all=BooleanSchema(description="Replace all occurrences (default false)"), occurrence=IntegerSchema( description="Optional 1-based occurrence to replace when old_text appears multiple times.", @@ -899,15 +901,9 @@ class EditFileTool(_FsTool): @property def description(self) -> str: return ( - "Perform a small, exact replacement in one file by replacing " - "old_text with new_text. When replacing text in an existing file, " - "old_text and new_text must be different. Use this for narrow text substitutions " - "with old_text copied from read_file. For multi-file, structural, " - "or generated code edits, prefer apply_patch. If old_text matches " - "multiple times, provide more context or set occurrence, line_hint, " - "replace_all, and expected_replacements. When editing from numbered " - "read_file output, set line_hint to the exact target line. " - "Shows closest-match diagnostics on failure." + "Perform a small, exact replacement in one file. " + "Prefer apply_patch for multi-file, structural, or generated edits. " + "occurrence, line_hint, and replace_all=true are mutually exclusive." ) @staticmethod diff --git a/nanobot/templates/agent/tool_contract.md b/nanobot/templates/agent/tool_contract.md index c91128852..445c3a80f 100644 --- a/nanobot/templates/agent/tool_contract.md +++ b/nanobot/templates/agent/tool_contract.md @@ -40,7 +40,7 @@ result with its original consumer or checker when one is available. - Use `apply_patch` as the default code editing tool, especially for multi-file changes, structural edits, generated code, moves, adds, or deletes. - Use `apply_patch dry_run=true` when the patch is uncertain and you want validation plus a change summary before writing. -- Use `edit_file` only for small exact replacements in one file, with `old_text` copied from `read_file`; when editing a specific numbered line, pass that exact line as `line_hint`; add `occurrence` or `expected_replacements` when ambiguity matters. +- Use `edit_file` only for small exact replacements in one file, with `old_text` copied from `read_file`. - Use `write_file` for new files or intentional full-file rewrites, not routine partial edits. - If `apply_patch` or `edit_file` fails, re-read with `force=true`, narrow the context, and try a smaller patch rather than switching to shell `sed` or `echo`. diff --git a/tests/tools/test_tool_descriptions.py b/tests/tools/test_tool_descriptions.py index 40d33ece6..9d4cec95f 100644 --- a/tests/tools/test_tool_descriptions.py +++ b/tests/tools/test_tool_descriptions.py @@ -9,7 +9,9 @@ from nanobot.agent.tools.shell import ExecTool def test_coding_tool_descriptions_steer_editing_priority() -> None: apply_patch = ApplyPatchTool().description.lower() - edit_file = EditFileTool().description.lower() + edit_tool = EditFileTool() + edit_file = edit_tool.description.lower() + edit_parameters = edit_tool.parameters["properties"] write_file = WriteFileTool().description.lower() assert "default tool for code edits" in apply_patch @@ -18,8 +20,10 @@ def test_coding_tool_descriptions_steer_editing_priority() -> None: assert "edit_file only for small exact replacements" in apply_patch assert "small, exact replacement" in edit_file - assert "copied from read_file" in edit_file assert "prefer apply_patch" in edit_file + assert "occurrence, line_hint, and replace_all=true are mutually exclusive" in edit_file + assert "copy it from read_file" in edit_parameters["old_text"]["description"].lower() + assert "must differ from old_text" in edit_parameters["new_text"]["description"].lower() assert "replace an entire file" in write_file assert "prefer apply_patch" in write_file