FilesystemFileSearchMiddleware: `grep_search` swallows ripgrep errors and reports “No matches found”; Python fallback never runs and `max_fi

在 FilesystemFileSearchMiddleware.grep_search 中启用 use_ripgrep=True 时,只要 ripgrep 对模式报错(例如使用了它不支持的 look-ahead),或运行时忽略 max_file_size_mb ,就会出现“明明有匹配却返回 No

快速结论:在 FilesystemFileSearchMiddleware.grep_search 中启用 use_ripgrep=True 时,只要 ripgrep 对模式报错(例如使用了它不支持的 look-ahead),或运行时忽略 max_file_size_mb,就会出现“明明有匹配却返回 No matches found”或“超大文件仍被返回”的现象。优先排查 ripgrep 的退出码处理与 --max-filesize 是否下发。

适用环境:Issue 已确认工具为 LangChain(langchain 包),环境为 Linux,ripgrep 13.0.0 与 14.1.0,langchain 1.4.3(master commit 57236d55d9)。复现脚本为纯 Python,依赖 langchain.agents.middleware.file_search.FilesystemFileSearchMiddleware,路径与 rg. --json 的控制台调用。Issue 未声明 Python 与 CUDA/显卡信息。

最快修复方案:暂无已在发布版本中确认的一步修复方案。Issue 讨论中提交者本地验证的改法是将 ripgrep 退出码非 0/1 视为失败并返回 None,从而触发既有的 Python fallback;同时给 rg 命令补上 --max-filesize,让 max_file_size_mb 在 ripgrep 路径生效。该改动在提交者环境(ripgrep 14.1.0)本地验证通过,但 PR 因未分配作者被自动关闭,需维护者分配后重新提交才能合入。

注意事项:以上修复方案尚未合并到稳定版本,属于可优先尝试的本地改动思路。若你选择自己打补丁,请保留 FileNotFoundError、TimeoutExpired 等既有分支行为;是否把 ripgrep 的 stderr 直接透传给模型,是维护者层面的设计问题,Issue 中未定论。

问题场景

在 LangChain 的 agent middleware 中使用 FilesystemFileSearchMiddleware.grep_search 进行文件内容检索时触发。该工具文档声称:有 ripgrep 就用 ripgrep,否则回退到纯 Python 搜索。用户的实际场景有两种:

  • 使用 Python 正则引擎接受、但 ripgrep 不支持的语法(如 look-ahead foo(?=bar)),期望得到与 Python fallback 一致的结果。
  • 设置了 max_file_size_mb 限制,期望超过阈值的文件在两条搜索路径下都被跳过。

两种场景在 use_ripgrep=True 与 use_ripgrep=False 下结果不一致,说明两条路径的行为没有对齐。

报错原文

use_ripgrep=True: 'No matches found'
use_ripgrep=False: '/a.txt:1:foobar'
rg returncode: 2 | stderr: error: look-around, including look-ahead and look-behind, is not supported
max_file_size_mb=10 use_ripgrep=True: /big.txt
max_file_size_mb=10 use_ripgrep=False: No matches found

原因分析

可能原因有两个,均与 _ripgrep_search 的实现细节有关:

  • ripgrep 出错被吞掉。subprocess.run 使用了 check=False,ripgrep 因不支持 look-around 退出码为 2,stdout 为空,于是 _ripgrep_search 返回空结果而不是 None。调用方的回退逻辑只在结果为 None 时才执行 Python 搜索,因此 fallback 永远不会触发,最终返回与“真的没有匹配”相同的字符串 No matches found。
  • 文件大小限制未下发。max_file_size_mb 只在 Python fallback 路径上被强制检查,ripgrep 命令行没有加入 --max-filesize,所以 11 MB 文件在 max_file_size_mb=10 时仍被 ripgrep 返回。

Issue 讨论还提出一个设计层面的疑问:ripgrep 报错时,是应当回退到 Python 搜索,还是把 stderr 返回给模型让它改写模式。讨论中的结论倾向于回退,因为方法已有 fallback 文档承诺,且 FileNotFoundError/TimeoutExpired 分支的处理方式也是回退。

环境排查

  • 确认 LangChain 版本,Issue 复现基于 langchain 1.4.3(master commit 57236d55d9)。
  • 确认 ripgrep 是否在 PATH 中,以及版本号:Issue 中 13.0.0 和 14.1.0 均可复现。
  • 确认操作系统:Linux。
  • 确认 FilesystemFileSearchMiddleware 的构造参数:use_ripgrep、max_file_size_mb、root_path。
  • 确认 Python 环境中能否导入 langchain.agents.middleware.file_search;Issue 中曾出现测试环境缺少 blockbuster 导致 pytest 无法收集相关文件的情况。
  • Issue 未涉及 CUDA、PyTorch、显卡或模型版本,无需排查这些项。

解决步骤

  1. 先在本地复现两类不一致:用 foo(?=bar) 分别以 use_ripgrep=True 和 False 调用 grep_search;再构造一个超过 max_file_size_mb 的文件,观察两条路径的差异。
  2. 手动执行 rg --json -- "foo(?=bar)" <dir>,确认返回码是否为 2 且 stderr 提示 look-around 不受支持,以证实“ripgrep 报错被吞”的判断。
  3. 定位 libs/langchain_v1/langchain/agents/middleware/file_search.py 中 _ripgrep_search 的退出码处理位置(Issue 讨论提到约在 :314 与 :347),将退出码非 0(匹配)且非 1(无匹配)的情况改为返回 None,让调用方已有的 Python fallback 生效。
  4. 在同一命令构造处,将 max_file_size_bytes 转换为 --max-filesize 参数传给 rg,使文件大小限制在 ripgrep 路径同样生效。
  5. 补充回归测试:覆盖 ripgrep 退出码 2 时回退到 Python 搜索、_ripgrep_search 在错误时返回 None、命令中包含 --max-filesize 三种情况。Issue 中提到这三条测试在原始代码上失败、打补丁后通过,整个 file-search 测试套件 45 项全绿。
  6. 提交前运行 ruff 与 mypy strict 检查。如果所在环境缺少 blockbuster 导致 pytest 无法收集测试文件,需要先补齐该依赖或换用配置完整的解释器。

验证方法

重新运行 Issue 中的复现脚本,检查两点:use_ripgrep=True 对 foo(?=bar) 返回结果应与 use_ripgrep=False 一致(即 /a.txt:1:foobar),而不再是 No matches found;在 max_file_size_mb=10 下,11 MB 的 big.txt 在两条路径上都应被跳过。若能运行测试,确认新增的三条回归用例与整个 file-search 套件(45 项)全部通过。由于该修复尚未合入发布版本,验证仅在本地补丁或合并后的分支上有意义。

参考来源

langchain-ai/langchain #41026

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 28552

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注