1. 从“RAG 已死”的论调说起:一场关于检索范式的路线之争
最近半年,技术圈里隔三差五就冒出“RAG 已死”的声音。起因也不复杂:一批 Agent 产品在演示里表现得像是把整份文档“吃”进了脑子里,用户问什么它答什么,既没有明显的检索延迟,也看不到传统 RAG 那种“先切块、再向量化、再召回、再重排”的流水线痕迹。于是有人下结论:既然 Agent 能直接读文件、能自己决定看哪一页,那套笨重的向量检索管线是不是可以扔了?
我一开始也差点被这个论调带偏。直到自己动手搭了几个基于文件系统的 Agent 原型,又回头把原来的 RAG 管线拆开重看了一遍,才发现事情远没有“谁取代谁”这么简单。RAG 不是死了,而是它的一部分职责被 Agent 接管了,另一部分职责反而变得更重要了。而“文件系统能不能包打一切”这个问题,答案取决于你把文件系统当成什么——是当成一个存放原始资料的仓库,还是当成一个可以替代检索引擎的万能索引。
先把概念理清楚,不然后面全是糊涂账。传统 RAG 的核心链路是:文档切块、向量化、存入向量库、查询时做相似度召回、再交给模型生成。它的价值在于用语义相似度把“可能相关”的内容从海量文本里捞出来,代价是切块会丢上下文、向量召回会漏关键词、重排会增加延迟。而 Agent 时代的文件系统方案,核心思路是:把原始文件按目录结构组织好,Agent 通过工具调用(读文件、列目录、搜索文件名、按行读取)自己去“翻文件”,边翻边判断,直到找到足够的信息再回答。
这两条路线的根本差异,不在于“谁更先进”,而在于检索决策权在谁手里。RAG 把决策权交给了一个预先设计好的召回函数,Agent 把决策权交给了一个会推理的模型。前者快、稳、可预测,后者灵活、能处理模糊需求,但慢、贵、容易跑偏。所以真正的问题不是“RAG 死没死”,而是在什么场景下该用哪条路,以及两者怎么配合。
这篇文章就是想把这件事讲透。我会从文件系统方案的实际能力边界讲起,拆解它在哪些任务上确实能替代 RAG,在哪些任务上会翻车;然后给出一个混合架构的落地思路,包括目录怎么设计、工具怎么定义、什么情况下该回退到向量检索;最后分享几个我在实操中踩过的坑,比如 Agent 读文件读到“迷路”、上下文被撑爆、以及为什么“文件系统包打一切”在文档量上去之后会迅速失效。适合正在做 Agent 应用、RAG 系统,或者纠结要不要重构检索链路的同学参考。
2. 文件系统方案的真实能力边界:它能做什么,不能做什么
2.1 文件系统方案的本质:把“检索”变成“导航”
很多人对文件系统方案的理解停留在“Agent 能读文件”这个层面,这太浅了。它真正的机制是把检索问题转化成了导航问题。传统 RAG 是“你给我一个 query,我还你一堆可能相关的 chunk”,文件系统方案是“你给我一个目标,我给你一套目录和文件,你自己走进去找”。
这个转化带来一个关键好处:上下文是连续的、结构化的。RAG 召回的是碎片化的 chunk,模型看到的是被切断的段落,经常出现“这句话的主语在上一块里”的尴尬。而 Agent 读文件时,它读到的是一整段、一整节,甚至能顺着目录结构理解“这个文件属于哪个模块、和哪些文件是兄弟关系”。这种结构信息在 RAG 里几乎完全丢失,但在文件系统方案里是天然存在的。
我做过一个对比实验:同样一份 200 页的技术文档,一份切成 500 字的 chunk 做向量检索,一份按章节存成 Markdown 文件让 Agent 自己读。问“第三章提到的那个配置项默认值是多少”,RAG 方案召回的是包含该配置项的零散段落,经常把不同章节的同名配置项混在一起;文件系统方案则是先列目录找到第三章,再读该章节文件,定位准确率高出一大截。原因很简单:目录结构本身就是一层强语义索引,而向量检索把这层索引压平了。
但这里有个前提:文档本身得有清晰的结构。如果原始资料是一堆没有标题、没有分节、命名随意的纯文本,那文件系统方案的优势直接归零,Agent 面对一堆file1.txt、file2.txt根本无从下手。所以文件系统方案的第一道门槛不是技术,是资料的整理质量。
2.2 它擅长的三类任务:定位明确、结构清晰、需要跨文件推理
根据我的实测,文件系统方案在以下三类任务上表现明显优于传统 RAG。
第一类是定位明确的结构化查询。比如“找出所有涉及数据库连接配置的文件,并列出各自的超时参数”。这类任务的关键词很明确,Agent 可以通过文件名搜索、目录遍历快速缩小范围,再逐个读取确认。RAG 在这里反而容易因为“超时参数”这个词在不同上下文里语义分散而召回一堆无关内容。
第二类是需要跨文件关联的推理。比如“接口 A 的返回字段里有一个枚举值,这个枚举值的定义在哪个文件里,它和接口 B 的入参枚举是不是同一套”。这种任务需要 Agent 先读接口 A 的定义,提取枚举名,再去搜索该枚举的定义位置,最后对比接口 B。整个过程是链式的,每一步都依赖上一步的结果。RAG 的一次性召回很难覆盖这种多跳关系,而 Agent 的逐步导航天然适合。
第三类是文档量不大但结构复杂的场景。比如一个项目的配置文件、脚本、说明文档混在一起,总量可能就几十个文件。这种情况下,Agent 遍历一遍的成本可以接受,而且能利用文件之间的引用关系。RAG 在这种小规模场景下反而显得笨重——为了几十个文件搭一套向量库,维护成本比收益还高。
2.3 它搞不定的四类场景:规模、模糊、时效、多模态
但文件系统方案绝不是万能的。以下四类场景,它要么直接失效,要么成本高到不可接受。
第一,文档规模上去之后,导航成本爆炸。我试过一个 5000+ 文件的代码仓库,让 Agent 用文件系统方式找“所有涉及支付回调的入口”。它列目录列了十几轮,读了三十多个文件,最后还是漏了两个关键入口。原因很简单:文件系统的导航是线性的,而向量检索是对数级的。文件越多,Agent 需要做的“读-判断-再读”循环就越多,延迟和 token 消耗都是线性增长。而向量检索无论文档多少,一次召回的时间基本恒定。
第二,模糊语义查询。比如“帮我找找有没有关于性能优化的讨论”。这种 query 没有明确关键词,文件系统方案只能靠 Agent 猜文件名或者全文扫描,效率极低。而向量检索恰恰擅长这种“意思相近但用词不同”的召回。
第三,时效性要求高的场景。Agent 读文件是串行的,读一个文件就要等一次模型推理。如果用户要求秒级响应,文件系统方案基本做不到。RAG 的召回+重排虽然也有延迟,但整体是并行的,可以控制在几百毫秒内。
第四,多模态内容。文件系统方案处理的是文本文件,遇到 PDF 里的图表、扫描件、音频转写文本,它无能为力。而现代 RAG 管线已经能接入 OCR、多模态 embedding,把这些内容也纳入检索范围。
下面这张表是我对两类方案的能力对比,基于实际项目中的观察:
| 维度 | 传统 RAG | 文件系统 Agent 方案 |
|---|---|---|
| 检索决策 | 预定义召回函数 | 模型逐步推理 |
| 上下文连续性 | 碎片化,易丢结构 | 连续,保留目录结构 |
| 规模扩展性 | 好,召回时间恒定 | 差,导航成本线性增长 |
| 模糊语义查询 | 强 | 弱 |
| 多跳推理 | 弱,需多次召回 | 强,天然链式 |
| 延迟 | 低,可并行 | 高,串行读文件 |
| 维护成本 | 需维护向量库和索引 | 只需整理目录结构 |
| 多模态支持 | 可扩展 | 基本不支持 |
所以“文件系统能包打一切”这个说法,在文档量小、结构清晰、查询明确的场景下成立,一旦越过某个规模阈值或者遇到模糊需求,就必须引入向量检索来兜底。这不是谁替代谁,而是分工问题。
3. 混合架构的落地思路:让 Agent 决定何时翻文件、何时查向量
3.1 核心设计原则:把向量检索降级为 Agent 的一个工具
既然两条路线各有边界,最自然的思路就是让 Agent 同时拥有两种能力,由它自己决定用哪个。具体做法是:把向量检索封装成一个工具函数,和“读文件”“列目录”“搜索文件名”并列,Agent 在推理过程中根据需要调用。
这个设计的关键在于不要预设优先级。很多人的做法是“先查向量库,查不到再读文件”,这其实还是 RAG 主导的思路。更好的方式是让 Agent 根据任务特征自己选:如果它判断这是一个模糊语义查询,就调向量检索;如果判断是结构化定位,就直接翻目录。模型在推理时其实能感知到 query 的类型,只要工具描述写得清楚,它选得往往比固定规则准。
我实测下来,Agent 在以下情况会主动选择向量检索:query 里包含“相关”“类似”“关于”这类模糊词;它已经翻了几轮文件但没找到;它需要跨多个不相关目录找共同主题。而在 query 包含明确文件名、章节名、配置项名时,它更倾向于直接读文件。这个行为模式基本符合预期。
3.2 目录结构怎么设计,才能让 Agent 不迷路
文件系统方案的效果,七成取决于目录结构设计。我踩过的最大坑就是:直接把原始资料按下载顺序扔进一个文件夹,结果 Agent 每次都要遍历所有文件才能定位。后来我总结出一套设计原则,实测能显著降低导航成本。
第一,按“主题-子主题-文档”三层组织,不要超过三层。层数太多,Agent 每进一层都要多一次列目录调用;层数太少,单层文件太多,列目录返回的列表会撑爆上下文。三层是个平衡点。比如产品文档/接口说明/支付接口.md这样的结构,Agent 从根目录到目标文件只需要三次调用。
第二,文件名要包含可检索的关键信息。不要用doc1.md、final_v2.md这种命名。文件名应该包含主题、版本、日期等 Agent 可能用来筛选的信息。比如支付接口-回调验签-v3-20240115.md,Agent 光看文件名就能判断是否相关,不用打开读。
第三,每个目录放一个_index.md或README.md,用一两句话说明本目录内容。这个文件是给 Agent 看的“路标”。当 Agent 列目录时,它会优先读这个索引文件,快速判断该目录是否值得深入。实测这个做法能减少 30% 以上的无效文件读取。
第四,控制单目录文件数量在 20 个以内。超过这个数,列目录返回的列表就会占用大量 token,而且 Agent 容易在长列表里“看花眼”。如果确实有大量文件,就再分一层子目录。
3.3 工具定义怎么写,模型才会用对
Agent 能不能用对工具,取决于工具描述的质量。我见过太多项目把工具描述写成“读取文件内容”这种废话,模型根本不知道什么时候该用。好的工具描述应该包含三部分:功能说明、适用场景、参数含义。
以“读文件”工具为例,我实际用的描述是这样的:
{ "name": "read_file", "description": "读取指定路径的文件内容。适用于已经通过 list_directory 或 search_filename 确定了具体文件路径,需要查看其完整内容的场景。如果只是想确认文件是否存在或了解目录结构,请优先使用 list_directory。单次读取建议不超过 2000 行,超出部分请分段读取。", "parameters": { "path": "文件的完整路径,必须是通过 list_directory 或 search_filename 确认过的真实路径", "start_line": "可选,起始行号,默认从第 1 行开始", "end_line": "可选,结束行号,默认读到文件末尾" } }注意描述里明确说了“如果只是想确认文件是否存在,请优先使用 list_directory”,这是在引导模型不要用读文件来做目录探查。这种引导能显著减少无效调用。同理,向量检索工具的描述里要写清楚“适用于模糊语义查询,不适用于精确文件名或配置项定位”,让模型知道边界。
3.4 什么情况下该强制回退到向量检索
即使 Agent 有自主选择权,也需要一些硬性规则来兜底。我设了三条强制回退规则:
规则一:连续三次文件导航未命中,强制调用向量检索。这是防止 Agent 在文件系统里“死磕”。有时候 query 确实模糊,Agent 却一直尝试猜文件名,这时候强制切到向量检索往往能破局。
规则二:单次任务读取文件超过 15 个,强制调用向量检索做交叉验证。读太多文件说明导航效率低,这时候用向量检索快速召回一批候选,再让 Agent 从中筛选,比继续遍历更划算。
规则三:query 长度超过 50 字且包含多个并列需求,优先向量检索。长 query 往往语义复杂,文件系统导航难以覆盖所有需求点,向量检索的多路召回更适合。
这三条规则不是拍脑袋定的,是我在实测中根据失败案例反推出来的。比如规则一,就是因为观察到 Agent 在找不到文件时会反复尝试相似路径,浪费大量轮次。加上强制回退后,任务成功率明显提升。
4. 实操中踩过的坑:从“迷路”到“上下文爆炸”
4.1 Agent 读文件读到“迷路”:循环调用与路径幻觉
这是最常见也最头疼的问题。Agent 在列目录时看到一堆相似文件名,比如config-dev.md、config-prod.md、config-test.md,它可能反复在这几个文件之间跳转,每次都觉得“下一个才是对的”。更糟的是,它有时会幻觉出一个不存在的路径,比如把产品文档/接口说明/支付接口.md记成产品文档/支付接口/接口说明.md,然后一直读不到,却意识不到是自己路径错了。
我的解决办法有两个。一是在工具返回里加入路径校验:当 Agent 请求的路径不存在时,返回信息里不仅要说“文件不存在”,还要列出当前目录下的实际文件列表,让它自己纠正。二是在系统提示里明确要求 Agent 在每次读文件前,先确认路径是通过 list_directory 得到的真实路径,不允许凭记忆拼路径。这两招组合下来,路径幻觉基本消失了。
4.2 上下文被文件内容撑爆:分段读取与摘要缓存
Agent 读文件是把内容塞进上下文,读几个大文件上下文就满了。我遇到过最夸张的一次:Agent 为了找一个配置项,连续读了 8 个平均 3000 行的文件,上下文直接爆掉,后面的推理全部失效。
解决办法是强制分段读取 + 摘要缓存。具体做法:读文件工具默认只返回前 200 行,如果 Agent 需要更多,必须显式指定行号范围。同时,每读完一个文件,要求 Agent 用一句话总结“这个文件讲了什么、和当前任务是否相关”,把摘要存到一个临时缓存里。后续如果需要回顾,先看摘要,只有摘要显示相关才重新读原文。这个做法把上下文占用降低了大约 60%。
4.3 向量检索和文件导航的结果冲突:以谁为准
混合架构下,有时向量检索召回的内容和 Agent 自己翻文件找到的内容不一致。比如向量检索说“配置项在 A 文件”,Agent 翻文件发现 A 文件里没有。这时候以谁为准?
我的经验是:以文件系统的实际内容为准,但要把冲突记录下来。因为向量检索可能因为 embedding 模型的偏差召回错误内容,而文件内容是“眼见为实”。但冲突本身是有价值的信号——它可能说明向量索引过期了,或者文档有多个版本。我会让 Agent 在遇到冲突时,额外读一下向量检索指向的文件,确认是否真的不相关,如果确实不相关,就在最终回答里注明“向量索引可能未更新”。
4.4 文件更新后索引不同步:一个容易被忽略的运维坑
文件系统方案的一个隐性优势是“文件改了,Agent 读到的就是最新的”,不需要重建索引。但这恰恰也是坑:如果混合架构里还挂着向量检索,文件更新后向量库不会自动同步,就会出现“Agent 读到新内容,向量检索召回旧内容”的割裂。
我的做法是给文件加版本标记,并在向量检索工具里带上版本过滤。每次文件更新,版本号加一,向量库重建时只索引最新版本。Agent 在调用向量检索时,可以指定“只要最新版本”。这样即使向量库有延迟,也不会召回过期内容。这个机制在文档频繁更新的项目里特别重要,我见过因为索引不同步导致 Agent 给出矛盾答案的案例,排查了半天才发现是版本问题。
5. 规模阈值与选型建议:什么时候该上向量检索
5.1 一个粗略的规模判断公式
“文件系统能不能包打一切”这个问题,其实可以量化。我根据实测数据总结了一个粗略判断公式:
如果 文件总数 × 平均单文件读取成本 < 向量检索的召回+重排成本,就用文件系统方案;否则引入向量检索。
平均单文件读取成本包括:一次工具调用的延迟 + 该文件内容占用的 token 成本。向量检索成本包括:embedding 查询延迟 + 召回结果占用的 token 成本 + 重排延迟。在文档量小于 100 个、单文件小于 500 行时,文件系统方案通常更划算;超过 500 个文件,向量检索的优势就非常明显了。
当然这只是个粗略参考,实际还要看查询类型。如果查询都是精确的文件名定位,那即使文件多,文件系统方案也能用;如果查询都是模糊语义,那文件少也应该上向量检索。
5.2 不同场景的选型对照
下面这张表是我对不同场景的选型建议,基于实际项目经验:
| 场景特征 | 推荐方案 | 理由 |
|---|---|---|
| 文档 < 100 个,结构清晰 | 纯文件系统 | 导航成本低,无需维护索引 |
| 文档 100-500 个,查询混合 | 混合架构,Agent 自主选 | 兼顾精确与模糊 |
| 文档 > 500 个,模糊查询为主 | 向量检索为主,文件系统兜底 | 导航成本过高 |
| 文档频繁更新 | 文件系统为主 | 无需重建索引 |
| 多模态内容 | 向量检索为主 | 文件系统不支持 |
| 需要多跳推理 | 混合,文件系统主导 | 链式导航更自然 |
5.3 我的最终建议:别站队,做分层
回到标题的问题:“RAG 死了吗?文件系统能包打一切吗?”我的答案是:RAG 没死,它只是从“唯一方案”变成了“方案之一”;文件系统也包打不了一切,它只是把一部分检索职责从预定义函数手里拿回到了模型手里。
真正靠谱的做法是分层:底层用文件系统保证内容的真实性和结构连续性,上层用向量检索处理模糊召回和规模扩展,中间由 Agent 根据任务特征动态调度。这不是妥协,而是对两类方案能力边界的尊重。
我在最近一个项目里就是这么做的:文档总量 300 多个,查询既有“找某个配置项”这种精确需求,也有“有没有关于性能优化的讨论”这种模糊需求。纯文件系统方案在模糊查询上翻车,纯 RAG 在精确查询上召回不准。混合之后,精确查询走文件导航,模糊查询走向量检索,Agent 自己判断,任务成功率从 70% 出头提到了 90% 以上。
最后分享一个小技巧:在系统提示里给 Agent 写一段“检索策略说明”,明确告诉它“优先尝试文件导航,连续失败后切换向量检索,两者结果冲突时以文件内容为准”。这段说明不需要很长,但能显著提升 Agent 的工具选择准确率。我试过不加这段说明和加了的对比,加了之后无效工具调用减少了将近一半。
这个方向后续还能继续挖,比如怎么让 Agent 学会“记住”哪些目录经常命中、哪些文件从不相关,形成一种自适应的导航偏好。不过那是另一个话题了,等我把当前这套跑稳了再折腾。