feat(PHASE-07): 最小 Grep 工具 + 工具优先级优化(搜索能力补齐)

This commit is contained in:
hc 2026-04-10 21:26:15 +08:00
parent 103725e652
commit 04ebf6bb2f
3 changed files with 46 additions and 2 deletions

View File

@ -37,11 +37,12 @@
## 工具使用规则 ## 工具使用规则
- `Read`:用于读取文件内容。 - `Read`:用于读取文件内容。
- `Glob`:用于按模式查找文件。 - 需要按文件名或路径模式查找时,优先使用 `Glob`
- 需要搜索文件内容时,优先使用 `Grep`
- 修改已有文件内容时,优先使用 `Edit` 工具。 - 修改已有文件内容时,优先使用 `Edit` 工具。
- 创建新文件时,优先使用 `Write` 工具。 - 创建新文件时,优先使用 `Write` 工具。
- `Bash`:用于执行必须通过 shell 完成的最小命令。 - `Bash`:用于执行必须通过 shell 完成的最小命令。
- 只有在确实需要 shell 特性时才使用 Bash。 - 只有在确实需要复杂 shell 特性时才使用 Bash。
- 不要用 Bash 拼接文件内容。 - 不要用 Bash 拼接文件内容。
- Windows 环境下优先使用兼容写法,不默认使用 `cat <<EOF`、`ls -la` 等 Unix 风格写法。 - Windows 环境下优先使用兼容写法,不默认使用 `cat <<EOF`、`ls -la` 等 Unix 风格写法。
- 同一写入或创建目标,最多尝试 2 种不同 Bash 方案。 - 同一写入或创建目标,最多尝试 2 种不同 Bash 方案。

View File

@ -20,6 +20,7 @@
- 重构时: - 重构时:
- 先找全部用法 - 先找全部用法
- 避免修改无关文件 - 避免修改无关文件
- 搜索文件内容时,优先使用 `Grep`,而不是 `Bash`
- 修改已有文件时,优先使用 `Edit`,而不是 `Bash``Write` - 修改已有文件时,优先使用 `Edit`,而不是 `Bash``Write`
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash` - 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
- Windows 下先验证 shell 兼容性,再选择命令写法 - Windows 下先验证 shell 兼容性,再选择命令写法

View File

@ -73,6 +73,20 @@ def build_default_tools(workspace: Path) -> list[Tool]:
}, },
execute=lambda data: write_tool(workspace, data), execute=lambda data: write_tool(workspace, data),
), ),
Tool(
name="Grep",
description="在文件或目录中搜索普通文本内容。",
input_schema={
"type": "object",
"properties": {
"pattern": {"type": "string", "description": "要搜索的普通文本字符串"},
"path": {"type": "string", "description": "可选,仅支持文件路径或目录路径,默认当前工作区"},
},
"required": ["pattern"],
"additionalProperties": False,
},
execute=lambda data: grep_tool(workspace, data),
),
Tool( Tool(
name="Bash", name="Bash",
description="在工作区中执行一条 shell 命令。", description="在工作区中执行一条 shell 命令。",
@ -138,6 +152,34 @@ def write_tool(workspace: Path, data: dict[str, Any]) -> str:
return f"{action}文件: {path.relative_to(workspace)}" return f"{action}文件: {path.relative_to(workspace)}"
def grep_tool(workspace: Path, data: dict[str, Any]) -> str:
pattern = str(data["pattern"])
target = _safe_path(workspace, str(data.get("path") or "."))
if not target.exists():
return f"文件不存在: {target.relative_to(workspace)}"
results: list[str] = []
files = [target] if target.is_file() else [path for path in target.rglob("*") if path.is_file()]
if target.is_dir() and not files:
return f"目录为空: {target.relative_to(workspace)}"
for file_path in files:
try:
text = file_path.read_text(encoding="utf-8", errors="replace")
except OSError:
continue
for line_no, line in enumerate(text.splitlines(), start=1):
if pattern in line:
results.append(f"{file_path.relative_to(workspace)}:{line_no}: {line}")
if len(results) >= 20:
return "\n".join(results)
if not results:
return "未找到匹配内容"
return "\n".join(results)
def bash_tool(workspace: Path, data: dict[str, Any]) -> str: def bash_tool(workspace: Path, data: dict[str, Any]) -> str:
command = str(data["command"]) command = str(data["command"])
proc = subprocess.run( proc = subprocess.run(