RAG 这些年几乎成了企业知识库的默认答案。做文档问答、内部制度查询、客服知识库、技术手册检索,很多团队的第一反应都是:切片、向量化、入库、召回、重排、生成。
这条链路当然有价值。它把大模型从纯参数记忆里拉出来,让答案能够依赖外部知识。但真正落到企业文档场景里,很多问题也会很快暴露出来:明明文档里有答案,模型没找到;明明召回了相关片段,回答却漏掉关键条件;引用看起来像那么回事,用户点回去却很难验证上下文。
这些问题不完全是模型能力不够,也不完全是向量数据库不够好。更底层的矛盾在于:我们把原本完整的文档拆成了碎片,再指望模型从碎片里拼回语义。这个过程有点像把一本制度手册撕成纸条,再让一个同事根据几张纸条回答你完整规则。能答对一些问题,但稳定性很难让人放心。
一个更朴素的思路是:既然现在长上下文已经足够长,为什么不让 LLM 像人一样先看目录,再决定打开哪些文件精读?也就是把知识库做成一个可导航的文件系统,让模型从目录索引中选择文件路径,再基于完整原文生成答案。
这不是要在所有场景里替代 RAG,而是在一类很典型的企业知识库场景里,重新评估“碎片检索”是不是唯一合理的工程解。
一、RAG的问题藏在切片里
传统 RAG 的标准流程大致是这样:

这条链路的问题,很多时候不是某一个组件坏了,而是整个范式有天然损耗。
语义被切断
文档切片看上去是一个工程实现细节,其实影响很大。
企业文档里的知识经常不是一句话能讲完的。比如:
- 某项福利的适用对象在第一段;
- 申请条件在第二段;
- 特殊情况在附录;
- 表格里的数字依赖前面的标题;
- 某个例外条款引用了另一份制度。
一旦按固定 token 长度切片,文档原本的结构就会被破坏。哪怕加 overlap,也只是缓解,不是根治。
很多团队会反复调这些参数:
| 参数 | 常见做法 | 代价 |
|---|---|---|
| chunk size | 从 300 调到 800,再调到 1200 | 大切片召回更粗,小切片上下文更碎 |
| overlap | 50、100、200 token 重叠 | 增加存储与召回冗余 |
| topK | 多召回几个片段 | Prompt 更长,噪声也更多 |
| rerank | 引入重排序模型 | 成本和延迟上升 |
这些优化有用,但它们都在同一个前提下工作:文档已经被切开了。
如果问题恰好落在一个完整片段里,效果会不错;如果答案依赖跨段落、跨表格、跨文件关系,质量就开始摇摆。生产环境里最麻烦的往往就是这种“看起来差一点”的错误,它不是完全胡说,却漏掉了最关键的限定条件。
溯源不够自然
RAG 通常也会给引用来源,但很多引用对应的是切片 ID、段落片段或某个向量库里的记录。
这对系统来说足够,对用户来说不一定够。
用户真正想验证的是:
- 这句话来自哪份文件?
- 文件路径是什么?
- 原文上下文是什么?
- 这个答案有没有漏掉前后限制条件?
- 多个来源之间有没有被模型强行拼接?
如果引用只是“chunk_1278”,那溯源体验其实很弱。即便前端把它包装成一个引用卡片,用户点进去看到的也可能只是一个被截断的段落。
在制度、合规、财务、产品手册这些场景里,答案错一次的成本可能比系统慢一点高得多。可验证性不是锦上添花,而是基础能力。
GraphRAG不是免费午餐
为了解决碎片化和跨文档关系问题,GraphRAG、知识图谱、实体关系抽取这些方案被重新推到台前。
它们确实能解决一部分问题,尤其是多跳推理、实体关系密集、跨文档关联复杂的场景。但工程成本也很实在:
- 要做实体抽取;
- 要做关系建模;
- 要处理同义词、别名、歧义;
- 要维护图谱更新;
- 要评估图上的召回质量;
- 还要把图检索结果重新转成 LLM 能理解的上下文。
很多企业内部知识库的高频问题,其实没那么复杂。用户问的是“年假怎么算”“报销标准是什么”“某个 API 参数怎么填”“某个流程谁审批”。这类问题更依赖精准定位和完整原文理解,而不是复杂图推理。
所以这里有一个很现实的权衡:如果 80% 的问题是文件级定位和条款理解,用一套高成本图谱系统去覆盖 20% 甚至更低比例的复杂问题,未必划算。
二、让模型先看目录
换一个角度看,企业知识库并不一定要从“片段”开始建模。
更自然的方式是从文件系统开始:

这里的核心变化是:索引只负责导航,答案必须来自完整文件。
也就是说,文件摘要不是答案来源,只是帮助模型判断“应该打开哪份文件”。真正生成答案时,系统读取对应的 Markdown 原文,让模型在完整上下文里作答。
这和传统 RAG 的差异很关键。
| 维度 | 传统 RAG | 文件索引方案 |
|---|---|---|
| 检索单位 | 文本切片 | 文件路径与摘要 |
| 答案来源 | 召回片段 | 完整原文文件 |
| 模型角色 | 被动接收上下文 | 主动选择要读的文件 |
| 溯源方式 | chunk、片段、段落 | 文件路径,可直接打开 |
| 基础设施 | 向量库、检索服务、重排模型 | 文件系统、索引文件、LLM |
| 主要风险 | 语义断裂、召回噪声 | 文档治理质量、索引摘要质量 |
这套方案不是把检索取消了,而是把检索从“语义向量匹配”改成“目录导航 文件精读”。
人查文档也是这么干的。通常不会先把所有文档切成 500 字片段再找相似内容,而是先看目录、标题、摘要,判断哪几份文件可能相关,然后打开原文看细节。
现在的问题只是:LLM 有没有能力做这个动作?在长上下文模型越来越成熟之后,答案开始变得务实起来。
三、索引文件是关键资产
这个方案里最重要的中间产物,不是向量库,而是一份模型能读懂、人也能读懂的索引文件。
一种简单格式可以是:
# /制度/考勤
- 年假规定.md | 员工年假适用条件、年假天数计算、申请与审批流程
- 加班调休规定.md | 加班认定标准、调休申请方式、补偿规则
# /制度/报销
- 差旅报销标准.md | 交通、住宿、餐补标准及特殊城市规则
- 发票管理规范.md | 发票类型、开票要求、报销审核注意事项
# /产品/API
- 用户认证接口.md | 登录、刷新 token、权限校验及错误码说明
- 订单查询接口.md | 订单查询参数、返回字段、分页与状态码说明
这个格式很土,但工程上很舒服。
它有几个好处:
- 人类可读出问题时可以直接打开看,不需要进向量库查 embedding。
- 模型友好路径、文件名、摘要都有明确结构,LLM 很容易输出候选路径。
- 可增量更新新增或修改文件时,只需要重新生成对应文件摘要,再更新索引条目。
- 上下文开销可控 把公共路径提取成标题,比每行写完整路径更省 token。
如果使用 256K 上下文模型,把其中一部分空间用于加载索引,容纳几百到上千个文件的路径和摘要并不夸张。尤其是经过治理后的高价值文档集,通常不会无限大。
这里要克制一点:不要把所有历史文件、聊天记录、扫描件、废弃版本都塞进来。知识库不是垃圾桶。模型再强,也不能替组织做信息治理。
四、文件粒度怎么定
文件粒度是这个方案成败的分水岭。
如果一个文件太大,比如一份 300 页制度汇编,模型即使打开了,也会浪费大量上下文,还容易在文件内部迷路。
如果文件太小,比如每个小节都拆成一个文件,索引数量会膨胀,文件之间的上下文关系又会被人为打散,最后重新走回 RAG 的老路。
比较合理的原则是:一个文件承载一个可独立引用的知识单元。
例如:
- 一份“年假规定”可以是一个文件;
- 一份“差旅报销标准”可以是一个文件;
- 一个“订单查询 API”可以是一个文件;
- 一个“部署手册”如果包含安装、升级、回滚、故障处理,可能需要拆成多个文件。
可以粗略用这几个标准判断:
| 判断项 | 建议 |
|---|---|
| 文件是否能独立回答一类问题 | 能,适合保留为一个文件 |
| 文件内部是否包含多个明显主题 | 拆分 |
| 文件是否依赖大量前置上下文 | 合并或补充背景说明 |
| 文件是否超过模型可舒适阅读范围 | 拆分 |
| 文件是否只是一个孤立小段 | 合并到相邻主题 |
很多团队做到这里会卡住,因为文档治理看起来不像“AI 项目”,更像苦活。但这一步绕不过去。未治理的文档堆,不管接 RAG、GraphRAG 还是长上下文,最后都会把脏数据的问题暴露出来。
五、查询链路可以很轻
工程实现上,这套链路可以保持得很薄。
一次查询可以分成两个阶段。
第一阶段,让模型读索引,选择文件:
你是企业知识库助手。
下面是一份文档索引,每行包含文件路径和摘要。
请根据用户问题,选择最可能相关的 1-5 个文件路径。
只返回 JSON 数组,不要回答问题。
用户问题:
员工入职不满一年,年假怎么计算?
文档索引:
...
模型返回:
[
"/制度/考勤/年假规定.md",
"/制度/考勤/员工入离职假期规则.md"
]
第二阶段,系统读取这些文件原文,再让模型回答:
请只基于以下原文回答用户问题。
如果原文没有依据,请明确说明无法从文档确认。
回答中必须列出引用文件路径。
用户问题:
员工入职不满一年,年假怎么计算?
引用文档:
[文件路径] /制度/考勤/年假规定.md
[文件内容]
...
[文件路径] /制度/考勤/员工入离职假期规则.md
[文件内容]
...
这条链路里,应用层只需要做几件事:
- 读取索引文件;
- 调用 LLM 选择路径;
- 校验路径是否合法;
- 读取 Markdown 原文;
- 再调用 LLM 生成答案;
- 返回答案和文件路径引用。
一个最小示意代码大概是这样:
# 示意代码:省略具体 LLM SDK 初始化,只展示核心流程
from pathlib import Path
import json
DOC_ROOT = Path("./knowledge_base")
INDEX_FILE = Path("./index.md")
def read_index() -> str:
return INDEX_FILE.read_text(encoding="utf-8")
def safe_read_doc(path: str) -> str:
"""
防止模型返回越权路径,例如 ../../secret.env
"""
normalized = (DOC_ROOT / path.lstrip("/")).resolve()
root = DOC_ROOT.resolve()
if not str(normalized).startswith(str(root)):
raise ValueError(f"非法路径: {path}")
if not normalized.exists():
raise FileNotFoundError(f"文件不存在: {path}")
return normalized.read_text(encoding="utf-8")
def llm_call(prompt: str) -> str:
"""
这里替换为实际的大模型 API 调用
"""
raise NotImplementedError
def select_files(question: str) -> list[str]:
index = read_index()
prompt = f"""
你是文档导航助手。
请根据用户问题,从索引中选择最相关的 1-5 个文件路径。
只返回 JSON 数组,不要解释。
用户问题:
{question}
文档索引:
{index}
"""
result = llm_call(prompt)
return json.loads(result)
def answer_question(question: str) -> str:
paths = select_files(question)
docs = []
for p in paths:
content = safe_read_doc(p)
docs.append(f"[文件路径] {p}\n[文件内容]\n{content}")
prompt = f"""
请只基于给定文档回答问题。
如果文档中没有依据,请说明无法确认。
回答末尾列出引用文件路径。
用户问题:
{question}
文档:
{chr(10).join(docs)}
"""
return llm_call(prompt)
这里有两个细节很重要。
第一,必须做路径校验。模型输出的路径不能直接信任,否则很容易引入越权读取风险。
第二,回答阶段要约束模型“只基于原文”。这不能完全消灭幻觉,但可以把错误空间压小。再配合文件路径引用,用户至少能回到原文核验。
六、不同规模下的策略
这套方案不是只能用于几百份文件。随着知识库规模变大,可以分阶段扩展。
小规模:全量索引
如果文档在几十到几百份,直接把完整索引塞进上下文是最省心的。
适合:
- 部门制度库;
- 产品帮助中心;
- 内部技术手册;
- 项目交付文档。
优点是链路短、稳定、容易调试。缺点是索引规模继续增长后,上下文成本会上升。
中规模:分块索引
如果文档达到几千份,可以按主题拆成多个索引文件:
/index
制度索引.md
产品索引.md
技术索引.md
客服索引.md
查询时先让模型选择索引分区,再加载对应分区的详细索引。

这相当于两级导航,仍然保持“索引用于导航,原文用于回答”的原则。
大规模:向量作为辅助定位
当文档规模很大,或者用户问题经常跨主题跳跃,可以引入向量检索。但这里的定位和传统 RAG 不一样。
向量检索只返回:
- 文件路径;
- 文件摘要;
- 可选的命中片段位置。
它不直接把切片作为答案来源喂给模型。
更合理的做法是:

这样向量库变成了“导航工具”,不是“答案材料仓库”。
这是一个很重要的工程取舍:保留向量检索的召回能力,但不让模型基于断裂片段直接下结论。成本比纯文件索引高一些,但在大规模知识库里更稳。
七、成本不是只看Token
有人会担心:每次加载索引,会不会很贵?
这个问题要算总账。
传统 RAG 的成本不只是推理 token,还包括:
- 文档切片任务;
- embedding 生成;
- 向量数据库;
- 检索服务;
- rerank 模型;
- 索引更新流程;
- 召回质量评估;
- 线上调参和问题排查。
文件索引方案的成本主要是:
- 文档治理;
- 文件摘要生成;
- 索引加载 token;
- 两次 LLM 调用;
- 原文读取与回答 token。
如果知识库规模不大,文件索引方案的基础设施成本会低很多。纯文件系统加一个轻量服务就能跑起来。索引文件可能只有几百 KB,Markdown 原文几十 MB,这种量级完全没必要上来就引入数据库。
但它也不是免费方案。
它对长上下文模型依赖更强。模型如果在长索引里导航能力差,选错文件,后面回答也会错。它还依赖摘要质量,如果摘要过短或过泛,模型很难判断文件相关性。
这里的权衡可以说清楚:
| 方案 | 更适合的情况 | 主要代价 |
|---|---|---|
| 传统 RAG | 海量内容、弱结构文本、召回优先 | 语义碎片化、链路复杂 |
| 文件索引 | 治理后文档、强溯源、文件级知识 | 依赖文档治理和长上下文 |
| GraphRAG | 多实体、多关系、多跳推理 | 建模成本高、维护复杂 |
| 混合方案 | 大规模企业知识库 | 架构复杂度上升 |
所以结论不是“彻底抛弃 RAG”。更准确的判断是:在经过治理的企业文档问答场景里,文件索引 原文精读可能比传统切片 RAG 更直接,也更容易解释。
八、文档治理是前置条件
这个方案有一个不太讨喜但很真实的前提:文档要先治理。
最低限度要做几件事:
- 筛选有效文档过期制度、重复版本、临时草稿,不要直接进入知识库。
- 统一格式尽量转换成 Markdown。PDF、Word、网页都可以保留原始副本,但检索层最好使用统一文本格式。
- 按知识单元拆分太大的文件拆开,太碎的内容合并。
- 补充标题和元信息文件名要能表达主题,路径要体现分类。
- 人工抽查摘要 摘要不必写得很长,但要覆盖文件的关键范围。
文档治理不是这个方案额外发明出来的负担。只要做严肃知识库,迟早都要面对这件事。区别只是传统 RAG 往往把这个问题藏在切片和 embedding 后面,等线上效果不稳定时才暴露出来。
比较务实的启动方式,是先拿几十份高频文档做试点。不要一开始就治理全公司文档。先验证这条链路是否能在核心问题上稳定回答,再决定是否扩展。
九、一个可落地的试点路径
如果要在团队里试一下,可以按三步走。
第一步,选一批高价值文档。
数量不用多,30 到 100 份就够。最好选择问题边界清晰、用户经常查询、对溯源有要求的文档,比如制度、手册、API 文档、运维 SOP。
第二步,生成索引文件。
把文档统一成 Markdown,按目录组织。用小模型或大模型为每个文件生成一句摘要,再合并成层级索引。
摘要建议控制在一行,不要写成小作文。索引是导航,不是全文压缩。
第三步,做双阶段问答。
先让模型从索引选文件,再读取完整原文回答。记录这些指标:
- 文件选择是否准确;
- 回答是否遗漏关键条件;
- 引用路径是否可验证;
- 与传统 RAG 的答案差异;
- 用户是否能快速核验答案。
如果实验里发现模型经常选错文件,优先检查摘要和目录结构,而不是马上上复杂检索系统。很多时候问题出在文件命名含糊、摘要过泛、同类文档边界不清。
如果用户材料里确实有实验代码、评测数据或 GitHub 仓库,建议把它放在文末,读者会更容易复现。但没有链接时,不要硬写“见文末仓库”。技术文章里这种承诺一旦落空,很伤信任。
十、边界要讲清楚
文件索引方案适合这些场景:
- 企业内部制度库;
- 政策条款问答;
- 产品手册和帮助中心;
- 技术文档查询;
- 运维 SOP;
- 对引用可验证性要求高的知识库。
它不适合直接套到这些场景:
- 未清洗的杂乱文件堆;
- 百亿级公开网页搜索;
- 实时流式数据分析;
- 高度依赖复杂实体关系推理的领域;
- 文件本身特别长且难以拆分的材料;
- 用户问题需要综合大量弱相关证据的开放式研究场景。
还有一个容易被忽略的点:文件级原文并不等于绝对正确。文档过期、互相冲突、缺少版本控制,模型照样会给出有问题的答案。只不过这种方案更容易把问题暴露出来,因为用户可以直接看到引用文件。
这在工程上反而是好事。系统能不能稳定,不只取决于模型,也取决于知识资产本身有没有被管理起来。
十一、RAG会留下来,但位置会变化
RAG 不会消失。向量检索、重排、图谱、多路召回都有自己的适用场景。
但在长上下文越来越可用之后,企业知识库的默认设计可以更灵活一些。过去我们不得不把文档切碎,因为模型装不下、读不动。现在至少在中小规模治理文档里,可以让模型先看目录,再打开原文。
这带来的变化很直接:
- 检索结果从片段变成文件;
- 摘要从答案材料变成导航线索;
- 引用从 chunk ID 变成文件路径;
- 向量库从必选基础设施变成可选辅助能力;
- 文档治理从后期补救变成前置资产建设。
如果手头正好有一批重要文档要做 AI 问答,不妨先别急着搭完整 RAG 流水线。挑几十份文档,整理成 Markdown,生成一份索引,让模型试着自己翻文件。
你可能会发现,很多答案并不需要复杂召回链路。它们一直在原文里,只是过去我们给模型看的东西太碎了。[DONE]



























































