DeerFlow 的 grep 与 glob 工具:为 Coding Agent 构建受控的文件系统检索层
2026/9/5 17:56:30 网站建设 项目流程

DeerFlow 的 grep 与 glob 工具:为 Coding Agent 构建受控的文件系统检索层

【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow

本文基于 DeerFlow 仓库中的 RFC 文档 rfc-grep-glob-tools.md,并结合该 RFC 在仓库中已落地的实现源码,系统讲解 DeerFlow 为什么要在 sandbox 工具层新增glob(按路径模式找文件)与grep(按内容模式找位置)两个 built-in 只读检索工具、它们的设计边界(参数语义、路径权限、结果硬限制)、底层 Python 实现细节(忽略规则、二进制跳过、截断提示),以及如何在配置中启用和调优。读完后,你将理解 Agent 化文件检索工具区别于 "shell 命令包装" 的核心取舍,并能完整复现 DeerFlow 中glob -> grep -> read_file -> str_replace的仓库探索工作流。

问题起点:ls / read_file 还不够

RFC 的出发点很明确:如果 DeerFlow 想更接近 Claude Code 这类 coding agent 的实际工作流,仅有ls/read_file/write_file/str_replace/bash还不够。模型在进入修改前,通常还需要两类能力:

  • glob:快速按路径模式找文件,例如 "所有*.tsx的 page 文件";
  • grep:快速按内容模式找候选位置,例如 "某个 symbol / 文案 / 配置键在哪里出现"。

RFC 列举了当时的典型痛点:

  1. 模型想找特定后缀文件时,只能反复ls多层目录,或者退回bash find
  2. 模型想找某个符号出现位置时,只能逐文件read_file,或者退回bash grep/rg
  3. 一旦退回bash,工具调用就失去结构化输出,结果也更难做裁剪、分页、审计和跨 sandbox 一致化;
  4. 对没有开启 host bash 的本地模式,bash甚至可能不可用,此时缺少足够强的只读检索能力。

因此结论是:DeerFlow 缺的不是 "再多一个 shell 命令",而是文件系统检索层。这两类工具的价值不是 "功能上 bash 也能做",而是能以更低 token 成本、更强约束、更稳定的输出格式,替代模型频繁走bash find/bash grep/rg的习惯。

目标与非目标

RFC 给出的Goals

  • 为 agent 提供稳定的路径搜索和内容搜索能力;
  • 减少对bash的依赖,特别是在仓库探索阶段;
  • 保持与现有 sandbox 安全模型一致;
  • 输出格式结构化,便于模型后续串联read_file/str_replace
  • 让本地 sandbox、容器 sandbox、未来 MCP 文件系统工具都能遵守同一语义。

Non-Goals同样重要,它划定了边界:

  • 不做通用 shell 兼容层;
  • 不暴露完整 grep/find/rg CLI 语法;
  • 不在第一版支持二进制检索、复杂 PCRE 特性、上下文窗口高亮渲染等重功能;
  • 不把它做成 "任意磁盘搜索",仍然只允许在 DeerFlow 已授权的路径内执行。

直接收益可以概括为五点:更低的模型负担(不用拼find/grep/xargs/quoting 细节)、更稳定的跨环境行为(本地、Docker、AIO sandbox 不必依赖容器里是否装了rg)、更强的安全与审计(调用参数天然就是 "搜索什么、在哪搜、最多返回多少")、更好的 token 效率(返回命中摘要而非整段文件),以及对tool_search友好(高频基础工具值得保留为 built-in)。

工具定义:glob 与 grep 的参数与返回格式

RFC 建议增加两个 built-in sandbox tools,放在 sandbox/tools.py。仓库中这两个工具已经实现,定义见 glob_tool 与 grep_tool。

glob 工具

用途:按路径模式查找文件或目录。当前实现的签名(与 RFC 建议的 schema 基本一致):

@tool("glob", parse_docstring=True) def glob_tool( runtime: Runtime, pattern: str, path: str, description: str = "", include_dirs: bool = False, max_results: int = _DEFAULT_GLOB_MAX_RESULTS, # 200 ) -> str: """Find files or directories that match a glob pattern under a root directory."""

参数语义:

参数说明
patternglob 模式,相对于根路径匹配,例如**/*.pysrc/**/test_*.ts
path搜索根目录,必须是绝对路径
description与现有工具保持一致的 UI 展示说明
include_dirs是否返回目录,默认False
max_results最大返回条数,默认 200,防止一次性打爆上下文

返回格式(由_format_glob_results生成,见 tools.py):

Found 3 paths under /mnt/user-data/workspace 1. /mnt/user-data/workspace/backend/app.py 2. /mnt/user-data/workspace/backend/tests/test_app.py 3. /mnt/user-data/workspace/scripts/build.py

结果为空时返回No files matched under <root>;命中超过上限时,首行会追加(showing first N),并附一句Results truncated. Narrow the path or pattern to see fewer matches.的截断提示。

grep 工具

用途:按内容模式搜索文件,返回命中位置摘要。当前实现:

@tool("grep", parse_docstring=True) def grep_tool( runtime: Runtime, pattern: str, path: str, description: str = "", glob: str | None = None, literal: bool = False, case_sensitive: bool = False, max_results: int = _DEFAULT_GREP_MAX_RESULTS, # 100 ) -> str: """Search for matching lines inside a text file or files under a root directory."""

参数语义:

参数说明
pattern搜索词或 Python 正则
path搜索目标,可以是单个文件或根目录,必须是绝对路径
glob可选路径过滤,例如**/*.py,用于缩小扫描范围
literalTrue时按普通字符串匹配,不解释为正则(内部用re.escape转义)
case_sensitive是否大小写敏感,默认False(即默认re.IGNORECASE
max_results最大返回命中行数(不是文件数),默认 100

返回格式(_format_grep_results):

Found 4 matches under /mnt/user-data/workspace /mnt/user-data/workspace/backend/config.py:12: TOOL_GROUPS = [...] /mnt/user-data/workspace/backend/config.py:48: def load_tool_config(...): /mnt/user-data/workspace/backend/tools.py:91: "tool_groups" /mnt/user-data/workspace/backend/tests/test_config.py:22: assert "tool_groups" in data

第一版只返回文件路径 + 行号 + 命中行摘要,不返回上下文块,避免结果过大;模型需要上下文时再调用read_file(path, start_line, end_line)。截断时同样输出Results truncated. Narrow the path or add a glob filter.提示。

设计原则:为什么不做 shell wrapper

这是 RFC 中最有分歧也最关键的一条决策。不建议grep实现为subprocess.run("grep ..."),也不建议在容器里直接拼find/rg命令。原因:

  • 会引入 shell quoting 和注入面;
  • 会依赖不同 sandbox 镜像是否安装了同一套命令;
  • Windows / macOS / Linux 行为不一致;
  • 很难稳定控制输出条数与格式。

正确方向是:glob使用 Python 标准库路径遍历,grep使用 Python 逐文件扫描,输出由 DeerFlow 自己格式化。如果未来为了性能要优先调用rg,也应该封装在 provider 内部并保证外部语义不变,而不是把 CLI 暴露给模型。

从源码结构看,这个原则得到了完整贯彻:核心检索逻辑放在独立的 search.py 中,只依赖os.walkrefnmatchPurePosixPath等标准库,没有任何子进程调用。

实现解析:search.py 中的检索内核

search.py 是两个工具的共享内核,包含四个关键部件。

1. 统一的忽略规则集

RFC 要求glob的默认忽略项尽量与ls对齐,并 "抽一个共享 ignore 集"。实现中这是一个 50 项的IGNORE_PATTERNS列表,覆盖:

  • 版本控制目录:.git.svn.hg.bzr
  • 依赖与虚拟环境:node_modules.venvvenvsite-packages
  • 构建产物:distbuildtargetout.next.nuxt.output.turbo
  • 缓存与临时文件:__pycache__.pytest_cache.mypy_cache.ruff_cache*.log*.tmp*.bak*.swp等。

值得注意的性能细节:should_ignore_name在目录树遍历时每个条目都要执行一次,因此实现把纯字面量名称预编译成frozenset(O(1) 查找),只把含*?[的少数通配模式合并成一条正则,避免每个文件名做约 50 次fnmatch调用。os.path.normcase同时保持了与fnmatch一致的大小写行为(POSIX 敏感、Windows 折叠)。

2. glob 匹配:find_glob_matches

find_glob_matches(root, pattern, *, include_dirs, max_results)的行为:

  • 根目录不存在抛FileNotFoundError,不是目录抛NotADirectoryError(对应 RFC "输入根目录不存在/根路径不是目录:返回清晰错误");
  • os.walk遍历,且通过原地改写dirs[:]把忽略目录从遍历中直接剪掉;
  • 模式匹配相对路径(path_matchesPurePosixPath.match,并额外兼容**/前缀模式);
  • 命中数达到max_results时立即返回,并通过第二个返回值truncated=True告知调用方结果被截断——这正是 RFC "大结果集会被截断并明确提示" 验收标准的落点。

3. grep 匹配:find_grep_matches

find_grep_matchesGrepMatch数据类(path/line_number/line三元组,与 RFC 建议的抽象层签名一致)实现了以下行为,逐条对应 RFC 的 "Detailed Behavior":

  • 默认只扫描文本文件;is_binary_file检查前 8KB 内是否含\0字节,命中即跳过;
  • 超过max_file_size(默认DEFAULT_MAX_FILE_SIZE_BYTES = 1_000_000,即 RFC 建议的 1MB 上限)的文件直接跳过;
  • literal=True时先re.escape,否则把pattern当 Pythonre编译;编译失败会抛re.error,由工具层捕获并返回Error: Invalid regex pattern: ...(对应 "regex 编译失败时返回参数错误");
  • case_sensitive=False时加re.IGNORECASE
  • 跳过符号链接,并要求 resolve 后的文件仍位于 root 之下(防越权);
  • 读取用encoding="utf-8", errors="replace",保证脏字节不会中断扫描;
  • 单行超过line_summary_length * 10(即 2000 字符)的行直接跳过,注释里写明这是为了防止对 minified / 无换行文件的 ReDoS;
  • 每行命中后经truncate_line截断到 200 字符(DEFAULT_LINE_SUMMARY_LENGTH),对应 RFC "单行摘要最大长度 200 字符";
  • 按文件路径、行号的自然遍历顺序输出,保持稳定排序。

4. 从工具到 Sandbox 抽象:RFC 中 Option B 已落地

RFC 给出两个实现选项:Option A(直接在sandbox/tools.py实现第一版)与 Option B(先扩展Sandbox抽象,新增glob/grep抽象方法),结论是 "第一版建议走 Option A,等工具价值验证后再下沉到Sandbox抽象层"。

从当前仓库看,项目走完了两步:Sandbox ABC 现在包含globgrep两个抽象方法,各 sandbox provider 可以各自实现/优化;工具层调用sandbox.glob(...)/sandbox.grep(...),各环境(本地、容器、远程)遵守同一语义。这正对应 RFC 的目标之一 "让本地 sandbox、容器 sandbox、未来 MCP 文件系统工具都能遵守同一语义"。

安全模型:沿用路径权限与输出脱敏

RFC 原则 B 要求两个工具复用ls/read_file的路径校验逻辑,"它们属于file:read,不是 bash 的替代越权入口"。在 tools.py 中可以确认这条调用链:

  1. 禁用技能拦截:先检查_is_disabled_skill_path,被禁用的 skill 目录直接返回错误;
  2. 沙箱初始化ensure_sandbox_initialized(runtime)+ensure_thread_directories_exist(runtime)
  3. 路径解析与权限校验(仅本地 sandbox 分支):_resolve_local_read_path(path, thread_data)内部调用validate_local_tool_path(path, thread_data, read_only=True),随后区分两类路径——skills / ACP workspace / 自定义挂载路径交给 sandbox 的 PathMapping 解析,其余 user-data 虚拟路径走_resolve_and_validate_user_data_path解析。这保证了 thread workspace / uploads / outputs 虚拟路径、/mnt/skills/.../mnt/acp-workspace/...均被支持,越权路径与 path traversal 被拒绝;
  4. 输出脱敏:结果回来后用mask_local_paths_in_output把宿主真实路径反掩回虚拟路径(glob 对每条 match、grep 对每条GrepMatch.path),保证 "输出不泄露宿主机真实路径" 的验收标准;
  5. 结果级过滤:即使 root 本身合法,遍历过程中遇到的已禁用 skill 路径仍会被_drop_disabled_skill_paths逐条剔除;
  6. 错误脱敏_sanitize_error会把异常信息中解析出的宿主路径掩码回虚拟路径再返回,避免错误消息成为路径泄露通道。

两个工具还各自捕获了FileNotFoundError/NotADirectoryError/PermissionError(grep 额外捕获re.error),统一转成Error: ...文本返回,模型可以据此自纠参数。异步侧则通过给glob_tool/grep_toolcoroutine_glob_tool_async/_grep_tool_async)适配 LangGraph 的异步调用,同步函数体不变。

结果硬限制:默认值、上限与配置覆写

RFC 原则 C 规定 "没有硬限制的 glob/grep 很容易炸上下文",建议第一版:glob.max_results默认 200、最大 1000;grep.max_results默认 100、最大 500;单行摘要 200 字符;跳过二进制与超大文件;命中超阈值时返回 "已展示条数 + 被截断事实 + 缩小范围建议"。

实现与这些数值完全对应,见 tools.py:

_DEFAULT_GLOB_MAX_RESULTS = 200 _MAX_GLOB_MAX_RESULTS = 1000 _DEFAULT_GREP_MAX_RESULTS = 100 _MAX_GREP_MAX_RESULTS = 500

截断提示文案也符合 RFC 的示例风格("已展示 N 条 + 建议缩小 path/pattern/glob")。

还有一个超出 RFC 文本、但在代码中确认的机制:_resolve_max_results会读取config.example.yaml中该工具配置的max_results键(_get_tool_config_int),并与模型请求的max_resultsmin。也就是说,配置里写死的上限是全局天花板——即使模型在调用时传入更大的max_results,也会被钳制回配置值;非法值(<=0)回退默认值。这为运维侧限流提供了单一控制点。

启用与配置:file:read 工具组

RFC 的 Suggested Config 与仓库根目录的 config.example.yaml 实际内容一致:

tools: - name: glob group: file:read use: deerflow.sandbox.tools:glob_tool max_results: 200 - name: grep group: file:read use: deerflow.sandbox.tools:grep_tool max_results: 100

要点:

  • 两个工具归属file:read组,与ls/read_file同权限等级,是只读检索工具而非 bash 的越权入口;
  • max_results键会被_resolve_max_results读入,作为该工具返回上限的全局天花板(glob 天花板 1000、grep 天花板 500,超出部分会被 clamp);
  • use指向deerflow.sandbox.tools下的具体 tool 对象,与仓库中注册位置一一对应。

推荐工作流与 Prompt 引导

RFC 明确了四个工具的互补关系,推荐模型工作流为:

  1. glob找候选文件;
  2. grep找候选位置;
  3. read_file读局部上下文;
  4. str_replace/write_file执行修改。

边界清晰的好处是利于在系统提示中教模型形成稳定习惯。RFC 同时强调:引入这两个工具时,必须同步更新系统提示——查找文件名模式时优先glob,查找代码符号、配置项、文案时优先grep,只有工具不足以完成目标时才退回bash;否则模型仍会习惯性先调bash

风险、备选方案与验收标准

RFC 讨论过的风险与缓解(原文四节):

  1. bash能力重叠——是事实但不是问题;lsread_file也能被bash替代,仍保留,因为结构化工具更适合 agent;
  2. 性能——大仓库上纯 Pythongrep可能比rg慢;缓解:结果上限 + 文件大小上限(1MB)、强制 root path、glob过滤缩小扫描范围、必要时在 provider 内部做rg优化但保持同一 schema。当前实现还额外加了 "超长行跳过" 防 ReDoS;
  3. 忽略规则不一致——ls能看到而glob看不到的路径会让模型困惑;缓解:统一 ignore 集(即 search.py 中那份共享列表),并在文档中明确 "默认跳过常见依赖和构建目录";
  4. 正则方言过复杂——第一版只支持 Pythonre,并提供literal=True简单模式。

Alternatives Considered全部被否定,理由值得记录:

  • 完全依赖bash:会让 DeerFlow 在代码探索体验上持续落后,且削弱无 bash / 受限 bash 场景能力;
  • 只加glob不加grep:只解决 "找文件" 没解决 "找位置",模型最终仍退回bash grep
  • 只加grep不加globgrep缺少路径模式过滤时扫描范围经常过大,glob是它的天然前置;
  • 直接接入 MCP filesystem server:MCP 可作为补充,但glob/grep作为基础 coding tool 最好是 built-in,才能在默认安装中稳定可用。

Acceptance Criteria(RFC 原文):

  • config.example.yaml中可默认启用globgrep
  • 两个工具归属file:read组;
  • 本地 sandbox 下严格遵守现有路径权限;
  • 输出不泄露宿主机真实路径;
  • 大结果集会被截断并明确提示;
  • 模型可以通过glob -> grep -> read_file -> str_replace完成典型改码流;
  • 在禁用 host bash 的本地模式下,仓库探索能力明显提升。

对应的回归测试覆盖见 test_sandbox_search_tools.py,针对路径校验、虚拟路径映射、结果截断与二进制跳过等场景做验证。

小结:三条被坚守的边界

RFC 的最终推荐是 "可以加,而且应该加",但明确卡住三个边界,这也正是当前实现可核对到的事实:

  1. grep/glob必须是built-in 的只读结构化工具——归属file:read组,复用validate_local_tool_path(read_only=True)权限模型;
  2. 第一版不做 shell wrapper,不把 CLI 方言直接暴露给模型——检索内核纯标准库实现于search.py
  3. 先在sandbox/tools.py验证价值,再下沉到Sandboxprovider 抽象——当前仓库已完成这一步,SandboxABC 中同时存在glob/grep抽象方法,各 provider 共享同一对外语义。

按这个方向,DeerFlow 在 coding / repo exploration 场景下的可用性得到提升,且风险可控:检索行为可审计、可限流、跨环境一致,并且与既有文件工具形成清晰的探索—定位—阅读—修改闭环。

【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询