docs(tools): clarify coding tool guidance

This commit is contained in:
Xubin Ren 2026-05-21 14:28:39 +08:00
parent 7e122d6e49
commit 44ef697aac
6 changed files with 85 additions and 22 deletions

View File

@ -298,11 +298,14 @@ class ApplyPatchTool(_FsTool):
@property @property
def description(self) -> str: def description(self) -> str:
return ( return (
"Apply a structured patch for code edits. The patch must include " "Default tool for code edits. Apply a structured patch with "
"*** Begin Patch and *** End Patch. Supports Add File, Update File, " "*** Begin Patch and *** End Patch. Supports Add File, Update File, "
"Delete File, and Move to. Paths must be relative. Prefer this for " "Delete File, and Move to across one or more files. Use this for "
"multi-file coding changes; use edit_file for small exact replacements. " "multi-file changes, structural edits, generated code, or any edit "
"Set dry_run=true to validate and preview the change without writing files." "where a reviewable patch is clearer than an exact replacement. "
"Paths must be relative. Set dry_run=true to validate and preview "
"the change summary without writing files. Use edit_file only for "
"small exact replacements copied from read_file."
) )
async def execute(self, patch: str, dry_run: bool = False, **kwargs: Any) -> str: async def execute(self, patch: str, dry_run: bool = False, **kwargs: Any) -> str:

View File

@ -424,11 +424,12 @@ class WriteStdinTool(Tool):
@property @property
def description(self) -> str: def description(self) -> str:
return ( return (
"Write text to a running exec session and return recent output. " "Interact with a running exec session created by exec with "
"Use chars='' to poll without writing. Set close_stdin=true to send EOF, " "yield_time_ms. Use chars='' to poll without writing, chars to send "
"or terminate=true to stop the session. Use wait_for to keep polling " "stdin, close_stdin=true to send EOF, or terminate=true to stop the "
"until expected output appears. Sessions finish automatically when " "process. Use wait_for with wait_timeout_ms for dev servers, test "
"their process exits." "watchers, and prompts where you need to wait for expected output. "
"Do not use this to start new commands; start them with exec."
) )
async def execute( async def execute(
@ -561,7 +562,8 @@ class ListExecSessionsTool(Tool):
return ( return (
"List active long-running exec sessions, including session_id, cwd, " "List active long-running exec sessions, including session_id, cwd, "
"elapsed time, idle time, remaining timeout, and command preview. " "elapsed time, idle time, remaining timeout, and command preview. "
"Use this to recover a session_id before polling with write_stdin." "Use this to recover a session_id after context shifts before "
"polling, writing stdin, or terminating with write_stdin."
) )
@property @property

View File

@ -158,6 +158,9 @@ class ReadFileTool(_FsTool):
"Text output format: LINE_NUM|CONTENT. " "Text output format: LINE_NUM|CONTENT. "
"Images return visual content for analysis. " "Images return visual content for analysis. "
"Supports PDF, DOCX, XLSX, PPTX documents. " "Supports PDF, DOCX, XLSX, PPTX documents. "
"Use find_files/list_dir first when the path is uncertain. "
"Read the relevant range before editing so replacements or patches "
"are based on current content. "
"Use offset and limit for large text files. " "Use offset and limit for large text files. "
"Use force=true to re-read content even if unchanged. " "Use force=true to re-read content even if unchanged. "
"Reads exceeding ~128K chars are truncated." "Reads exceeding ~128K chars are truncated."
@ -384,9 +387,10 @@ class WriteFileTool(_FsTool):
@property @property
def description(self) -> str: def description(self) -> str:
return ( return (
"Write content to a file. Overwrites if the file already exists; " "Create a new file or intentionally replace an entire file with "
"creates parent directories as needed. " "the provided content. Overwrites existing files and creates parent "
"For partial edits, prefer edit_file instead." "directories as needed. For code changes or partial edits, prefer "
"apply_patch; use edit_file only for small exact replacements."
) )
async def execute(self, path: str | None = None, content: str | None = None, **kwargs: Any) -> str: async def execute(self, path: str | None = None, content: str | None = None, **kwargs: Any) -> str:
@ -711,10 +715,13 @@ class EditFileTool(_FsTool):
@property @property
def description(self) -> str: def description(self) -> str:
return ( return (
"Edit a file by replacing old_text with new_text. " "Perform a small, exact replacement in one file by replacing "
"Tolerates minor whitespace/indentation differences and curly/straight quote mismatches. " "old_text with new_text. Use this for narrow text substitutions "
"If old_text matches multiple times, you must provide more context " "with old_text copied from read_file. For multi-file, structural, "
"or set occurrence/line_hint/replace_all. Shows a diff of the closest match on failure." "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. Shows closest-match "
"diagnostics on failure."
) )
@staticmethod @staticmethod

View File

@ -130,8 +130,10 @@ class FindFilesTool(_SearchTool):
def description(self) -> str: def description(self) -> str:
return ( return (
"Find files by path fragment, glob, or file type. " "Find files by path fragment, glob, or file type. "
"Use this before read_file when you need to locate files. " "Use this before read_file when you need to locate files, and "
"Returns workspace-relative paths and skips common dependency/build directories." "prefer it over shell find/ls for ordinary workspace discovery. "
"Returns workspace-relative paths and skips common dependency/build "
"directories."
) )
@property @property
@ -289,7 +291,8 @@ class GrepTool(_SearchTool):
return ( return (
"Search file contents with a regex pattern. " "Search file contents with a regex pattern. "
"Default output_mode is files_with_matches (file paths only); " "Default output_mode is files_with_matches (file paths only); "
"use content mode for matching lines with context. " "use content mode for matching lines with context. Prefer this "
"over shell grep for ordinary workspace searches. "
"Skips binary and files >2 MB. Supports glob/type filtering." "Skips binary and files >2 MB. Supports glob/type filtering."
) )

View File

@ -211,8 +211,10 @@ class ExecTool(Tool):
def description(self) -> str: def description(self) -> str:
return ( return (
"Execute a shell command and return its output. " "Execute a shell command and return its output. "
"Prefer read_file/write_file/edit_file over cat/echo/sed, " "Use this for tests, builds, package commands, git commands, and "
"and grep/glob over shell find/grep. " "other process execution. Prefer read_file/find_files/grep for "
"inspection and apply_patch/write_file/edit_file for file changes "
"instead of cat, shell find/grep, echo, or sed. "
"Use -y or --yes flags to avoid interactive prompts. " "Use -y or --yes flags to avoid interactive prompts. "
"For long-running or interactive commands, pass yield_time_ms; " "For long-running or interactive commands, pass yield_time_ms; "
"if the command keeps running, exec returns a session_id that can " "if the command keeps running, exec returns a session_id that can "

View File

@ -0,0 +1,46 @@
from nanobot.agent.tools.apply_patch import ApplyPatchTool
from nanobot.agent.tools.exec_session import ListExecSessionsTool, WriteStdinTool
from nanobot.agent.tools.filesystem import EditFileTool, ReadFileTool, WriteFileTool
from nanobot.agent.tools.search import FindFilesTool, GrepTool
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()
write_file = WriteFileTool().description.lower()
assert "default tool for code edits" in apply_patch
assert "multi-file" in apply_patch
assert "dry_run=true" in apply_patch
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 "replace an entire file" in write_file
assert "prefer apply_patch" in write_file
def test_coding_tool_descriptions_steer_discovery_and_shell_usage() -> None:
read_file = ReadFileTool().description.lower()
find_files = FindFilesTool().description.lower()
grep = GrepTool().description.lower()
exec_tool = ExecTool().description.lower()
write_stdin = WriteStdinTool().description.lower()
list_sessions = ListExecSessionsTool().description.lower()
assert "find_files/list_dir first" in read_file
assert "before editing" in read_file
assert "prefer it over shell find/ls" in find_files
assert "prefer this over shell grep" in grep
assert "tests, builds" in exec_tool
assert "prefer read_file/find_files/grep" in exec_tool
assert "apply_patch/write_file/edit_file" in exec_tool
assert "yield_time_ms" in exec_tool
assert "do not use this to start new commands" in write_stdin
assert "wait_for" in write_stdin
assert "recover a session_id" in list_sessions