RAG
1.全局总览
一、离线知识库构建链路(绿色,"提前准备参考书")
这部分是事先做好的,目的是把你的文档变成 AI 能快速查阅的知识库,一共 7 步:
- 原始文档:你的资料,比如 PDF、Word、网页等。
- 文档解析:把各种格式读出来,变成纯文字。
- 文档清洗:去掉乱码、页眉页脚、广告等垃圾内容。
- 文档分块:把长文章切成一小段一小段,方便后面精准检索。
- 元数据生成:给每段贴 "标签",比如标题、来源、时间、关键词等。
- 向量化:把文字转成一串数字(向量),让计算机能计算 "语义相似度"。
- 向量数据库:把这些向量存进专门的数据库,等着被查询。
核心思想:把书 "消化" 成 AI 容易检索的形式,存起来备用。
二、在线用户问答链路(蓝 / 橙 / 紫色,"用户提问时实时处理")
用户真正提问时走这条线,一共 9 步。
理解问题阶段(蓝 + 橙)
- 用户问题:用户输入问题。
- 上下文补全:结合前面的对话,补全省略的信息(比如 "它" 指的是谁)。
- 意图理解:搞清楚用户到底想问什么。
- 歧义判断:判断问题清不清楚。如果不清楚,就走澄清追问(反问用户一句,再回到第 2 步重新理解);如果清楚,就继续往下。
检索与生成阶段(蓝 + 紫)
- 查询处理:把问题 "优化" 得更适合检索,常见手段有改写、扩展、拆解等。你只需知道这一步是 "把问题打磨得更好查" 即可。
- 检索与筛选:去知识库里面找出最相关的几段资料(相似度计算、按权限和元数据过滤等)。这是 RAG 里的 "R(检索)" 核心。
- 重排序 / 压缩:把找到的资料排个序、去重、精简,只留最有用的部分。
- 生成回答:把 "问题 + 检索到的资料" 一起交给大模型,让它基于资料生成答案,并附上引用出处、做事实核查,答不了就拒答,避免乱编。这是 RAG 里的 "G(生成)" 核心。
- 返回用户:把最终答案返回给用户。

2.RAG评价指标
Rag 场景下,比较著名的评估模型方法的框架是:RAGAS。RAGAS 方法在评估过程中主要使用忠诚度,答案相关性,和上下文相关性来评判整个 RAG 系统中 llm 生成答案的质量。RAGAS 方法的一个优点是不需要参考答案。
忠诚度是指用户判断 LLM 的输出是否是根据召回文本的内容生成的。
答案相关性是衡量生成的答案是否和用户的问题相关的。
上下文相关性是用于判断召回文本是否和用户问题相关的。
(下面的可以挑一个具体的指标来展开给面试宣讲,都展开讲太长了,而且内容有点抽象,如果讲完面试官不懂,可以给他举具体的例子讲解)
具体来讲:
- 忠实度的计算方式通常包括两个步骤:
- 使用 LLM 生成的问题和答案,让大模型生成多个陈述。
- 让 LLM 判断是否可以通过召回的文本,推断出这些陈述。 最后采用能够推断出来的陈述 / 总陈述量作为忠诚度的评分。
- 答案相关性:根据 LLM 的回答生成若干个假问题,然后计算假问题和真实问题的相似度,将相似度平均值作为答案相关性的评分。
- 上下文相关性:让 LLM 挑选出召回文本片段中能够回答用户问题的文本子集,用文本子集占召回文本的比例作为上下文的相关性数值。
3.RAG的步骤
3.1 意图识别
意图识别位于 RAG 全局总览的第 3,4(图中橙色的部分)
是什么?
意图识别位于 RAG 的检索前处理阶段,核心作用是先理解用户到底想问什么,再决定后面应该如何检索。它不是简单给用户问题打一个分类标签,而是要把用户的自然语言问题转成后续 RAG 可以执行的检索计划。
在实际场景中,用户的问题经常是不完整、口语化、依赖上下文的。比如用户问 "这个怎么申请?"" 它和那个有什么区别?" 如果直接把原句送进向量数据库,很容易召回错误内容。因此在正式检索前,系统需要先判断用户的真实意图、问题中的关键对象、时间地点等限制条件,以及这个问题是否需要先澄清。
怎么做?
意图识别通常会做三件事。
第一,判断用户的问题类型。 比如用户是在问定义、查事实、问流程、做比较、要总结、查政策,还是排查问题。不同类型的问题适合不同的处理方式:事实类问题通常适合精确检索,比较类问题适合拆成多个子问题,总结类问题需要召回更大范围的上下文,政策类问题则通常需要结合地区、时间、政策类型等元数据过滤。
第二,抽取实体和槽位。 实体是用户问题中真正关心的对象,槽位是检索时需要用到的限制条件。例如"2024 年上海人才补贴怎么申请?"中,主题是 "人才补贴",地区是 "上海",时间是 "2024 年",问题类型是 "申请流程"。这些信息可以直接用于过滤文档集合,避免在整个知识库里盲目检索。
第三,决定检索策略。
- 如果用户问题已经明确,可以直接检索;
- 如果问题表达口语化或依赖上下文,可以先做查询改写;
- 如果用户用词和文档用词可能不一致,可以做查询扩展;
- 如果问题比较短、抽象,可以用 HyDE 生成假设性文档辅助召回;
- 如果问题包含多个子目标,可以进行查询分解;
- 如果缺少关键信息,就应该先向用户澄清,而不是强行检索。
比如用户问 "这个怎么申请?",如果上一轮正在讨论 "上海人才租房补贴",系统就可以把它改写成 "上海人才租房补贴怎么申请?" 再检索。如果没有上下文,系统就应该追问 "你想申请的是哪类事项?比如人才补贴、项目申报、发票报销,还是其他业务?" 这就是意图识别在多轮 RAG 中的价值:能补全的自动补全,不能确定的先澄清。

在工程实现上,可以用规则、小模型分类器,也可以直接用大模型做结构化理解。现在更常见的方式是让大模型输出一个结构化 JSON 结果,示例如下:
{
"intent": "policy_query",
"entities": ["人才补贴"],
"filters": {
"region": "上海",
"year": "2024"
},
"needs_clarification": false,
"route": "policy_knowledge_base",
"retrieval_strategy": "metadata_filter",
"rewrite_query": "2024年上海人才补贴政策的申请条件、补贴标准和办理流程是什么?"
}
如何判断意图识别做得好不好?
我们需要从意图分类,槽位抽取,是否需要澄清,路由是否正确这几个维度来综合评估。
- 意图分类可以看 整体准确率、精确率、召回率、F1;
- 实体和槽位抽取可以看 Slot 精确率、Slot 召回率、Slot F1;
- 是否需要澄清可以单独看澄清判断的准确率和召回率;
- 路由是否正确可以看 Route Accuracy;
最终还要看加入意图识别后,正确文档是否更容易进入 Top‑K,答案准确率是否提升。
- 整体准确率 (Accuracy):所有判断里预测对(包括对和错两类判定都对)的样本占总体的比例。
- 精确率 (Precision):模型预测为 "是" 的结果里,真正正确的比例(主要防误报、防乱判)。
- 召回率 (Recall):在实际为 "是" 的所有样本中,模型成功找出来多少的比例(主要防漏报、防漏判)。
- F1 值 (F1‑Score):精确率和召回率的调和平均值,两者双高才能拿到高分,反映综合好坏。
- 槽位 (Slot):从用户提问里抽出来的结构化属性约束条件,如时间、地区、主题。
- 前 K 个检索结果 (Top‑K):指搜索引擎按相关性排序最终返回给用户的前 K 个文档片段。
- 平均倒数排名 (MRR):评判第一个正确结果排在第几名,排名越靠前(第一名为倒数为 1,第二名为 0.5)分值越高。
- 前 K 个命中率 (Hit@K):测试样本集中,检索前 K 个文档包含有正确答案的样本比例。
- 归一化折损累计增益 (nDCG):不仅衡量找回的文档是否相关,还非常看重最相关的文档是不是排在最前排。
3.2 输入文件处理

你在处理 pdf 转成 markdown 文本的这个步骤,你是怎么处理的,可以分享一下吗?
📑 参考答案 我们没有直接用一种解析器处理所有 PDF,而是设计了一套分阶段、按页面类型路由的处理流程。
-
首先进行文件级体检,通过文件哈希去重,并检查文件是否加密、损坏、页面方向是否异常,同时记录文件名和总页数。
-
接下来逐页分析文字数量、图片覆盖率、异常字符比例和版面结构,将页面分为原生文本页、扫描页、混合页、复杂布局页和异常页。
-
对原生文本页,我使用 PyMuPDF 提取文字及坐标;扫描页先渲染为图片,再使用 PaddleOCR 识别;混合页同时提取原生文字和重要图片区域,对图片执行 OCR 或多模态理解后再去重合并。对于多栏、表格和图文混排等复杂布局页,我会优先使用 Unstructured 的 hi_res 策略识别标题、正文、表格、图片区域及其坐标,再按照坐标恢复阅读顺序;如果页面结构特别复杂,也可以使用 LlamaParse 直接解析成带层级的 Markdown。对于乱码页面,先用 PyMuPDF 提取;如果存在字体编码或字符映射问题,就切换到
pdfplumber或pypdf交叉验证;这些解析器仍无法恢复时,再将异常页面渲染成图片,使用 PaddleOCR 做页面级兜底。 -
不同路线的输出最后会统一为相同结构,按照原始页码和页面内阅读顺序组合成 Markdown,并保留文件名、页码和异常标记,保证结果可以追溯到原文。
-
完成后还会检查漏页、乱码、页眉页脚残留、OCR 重复、多栏错序及表格关键数字。只有通过质量检查的内容,才会进入后续分块和向量化流程。
3.3 切割
为什么需要分块
第一,满足大语言模型的上下文窗口限制。直接输入整篇长文档可能超过模型的最大上下文长度。分块后只把与问题相关的内容送入模型,可以控制上下文规模。
第二,提高生成上下文的质量。召回内容过多、过长会增加 Token 成本,并产生 “Lost in the Middle” 问题,使真正有用的信息被大量无关内容掩盖。
第三,满足嵌入模型的输入限制。嵌入模型通常存在最大 Token 数,超出后可能被截断。例如部分 BGE 或 Sentence‑Transformers 模型的上限为 512 Tokens,过长文本会造成后半部分语义丢失。
第四,提高检索精度。一个文本块如果同时包含多个主题,其向量表示会被不同语义平均和稀释,用户查询某个细节时不容易准确召回。较小且主题集中的文本块通常更容易与查询匹配。
常见分块策略
一 固定长度分块
固定长度分块按照固定字符数或 Token 数切分文档,并可设置 Overlap,使相邻块保留一部分重复内容。
LlamaIndex 中可以使用 TokenTextSplitter:
from llama_index.core.node_parser import TokenTextSplitter
splitter = TokenTextSplitter(
chunk_size=500,
chunk_overlap=50,
)
nodes = splitter.get_nodes_from_documents(documents)
优点:实现简单、速度快、块大小稳定,便于控制索引规模和成本,适合作为快速验证 RAG 流程的基线。
缺点:可能从句子或段落中间切断,不理解文档结构和语义;Overlap 过大还会导致索引冗余和重复召回。
二 递归分块
LlamaIndex 中可以使用 SentenceSplitter:
from llama_index.core.node_parser import SentenceSplitter
splitter = SentenceSplitter(
chunk_size=500,
chunk_overlap=50,
)
nodes = splitter.get_nodes_from_documents(documents)
递归分块的完整过程如下:
-
先按照段落切分;
-
检查每个片段是否超过 chunk_size;
-
如果某个段落仍然太长,再按照句子切分;
-
如果句子仍然太长,再按照标点、空格等更细的边界切分;
-
最后使用字符或 Token 强制切分作为兜底;
-
将较小片段重新合并成接近目标长度的 Chunk,并保留适量 Overlap。
输入:一篇长文档•
↓ 按段落切•
[段落 A] [超长段落 B] [段落 C]•
↓ 按句子继续切•
[句 1] [句 2] [超长句 3]•
↓ 按标点或字符切•
[片段 3.1] [片段 3.2]
优点:尽量保留段落和句子的完整性;速度快,不需要调用模型;是普通 RAG 项目中最常用的分块方式。
缺点:依赖文本解析和清洗质量;OCR 错误换行可能造成误切;它并不真正理解标题和主题变化,输入没有正常换行或标点时会退化为固定长度切分。
三 结构化分块
结构化分块利用文件本身的组织结构确定边界,而不是只根据长度切分。
- Markdown:按照标题、段落和代码块切分;
- HTML:按照 h1~h6、p、li、table 等标签切分;
- DOCX:按照 Heading 样式、段落、列表和表格切分;
- 代码:按照文件、类、函数和方法切分;
- PDF/PPT:按照标题、正文、列表、表格、图片和页面区域切分。
优点:能够保留标题与正文、表头与数据、函数与代码之间的关系,结果更容易引用和追溯。
缺点:不同文件需要不同解析器;复杂 PDF 需要根据字号、坐标和版面推断结构;解析错误会直接影响后续分块质量。
四 语义分块
根据主题或语义变化确定边界。常见实现会先切句,再使用嵌入模型计算相邻句子或句子窗口的语义距离,在语义突变的位置设置断点,并将连续且语义相近的句子合并成一个 Chunk。
LlamaIndex 中可以使用 SemanticSplitterNodeParser:
from llama_index.core.node_parser import SemanticSplitterNodeParser•
splitter = SemanticSplitterNodeParser(
embed_model=embed_model,
breakpoint_percentile_threshold=85,
)
nodes = splitter.get_nodes_from_documents(documents)
五 分块核心技巧

第一步:根据文档结构选择主分块策略
首先判断文档是否存在可靠的自然边界。
如果文档结构清晰,应优先尊重原始结构:
- Markdown、HTML、Word:先按照标题、章节、段落等结构切分;
- 代码:按照文件、类、函数和方法切分;
- PDF/PPT:先完成版面解析,再按照标题、正文、列表和表格等结构切分;
- FAQ、商品资料、人物信息等强结构化数据:一条完整记录本身就是一个语义实体,可以直接作为一个 Chunk,不必强行拆分。
如果结构单元仍然过长,再在结构单元内部使用递归分块。
如果文档缺少可靠结构,则根据文本质量选择:
- 普通文章、说明书、制度:使用递归分块;
- OCR 错乱、换行不可靠但句子语义仍可识别:先清洗文本,再考虑递归分块或语义分块;
- 会议纪要、访谈、长篇报告等主题频繁变化的文本:考虑语义分块;
- 事实高度密集,并且需要检索独立人物、属性或关系:考虑命题分块,同时保留命题与原文的映射。
因此,结构化分块、递归分块、语义分块和命题分块不是简单的优劣关系,而是分别解决不同的数据形态问题。
第二步:根据检索目标确定 Chunk 粒度
确定主分块策略后,再根据用户问题所需的证据粒度决定 Chunk 大小。
- 只需要定位相关文档或章节:Chunk 可以相对大,以保留完整上下文;
- 需要定位具体条款、步骤、句子或事实:Chunk 应相对小,以提高检索精度;
- 用户问题通常对应一个完整段落:让 Chunk 尽量覆盖一个自然段落或语义单元;
- 一个 Chunk 中经常包含多个主题:说明粒度过大,应减小 Chunk,或者使用语义分块寻找主题边界;
- 检索结果很准确,但回答缺少前后文:说明检索粒度合适、生成上下文不足,不应直接增大所有 Chunk,而应使用句子窗口、父子块或层级索引扩充上下文。
理想状态不是 Chunk 越小或越大越好,而是一个 Chunk 尽量对应用户问题所需要的最小完整证据单元。
第三步:使用默认方案建立实验基线
当项目处于早期阶段,尚未积累足够的真实问题和评测数据时,可以先采用一个低成本、容易解释的基线方案:
结构化解析
→ 在超长结构单元内部递归分块
→ 设置有限 Overlap
→ 保存标题、页码和来源等元数据
初始实验可以从约 400~600 Tokens 的 chunk_size 和 10%~20% 的 Overlap 开始。
默认方案不是最终选型原则,也不与“根据文档类型选择”冲突。它只是在信息不足时提供一个可运行的起点。获得真实召回结果后,仍然要根据文档特点和问题类型进行调整。
第四步:根据召回问题定向调整
运行基线方案后,应根据实际问题判断需要修改哪一层,而不是盲目调大或调小 Chunk。
- 正确内容经常召回不到:Chunk 可能过大或结构边界不合理,应减小 Chunk或调整检索方式;
- 已召回正确内容,但上下文不完整:适当增加 Overlap,或使用句子窗口、父子块扩充上下文;
- 一个 Chunk 中经常存在多个主题:减小 Chunk,或改用语义分块;
- Top-K 结果高度重复:减少 Overlap,并对召回结果去重;
- 标题与正文经常分离:改善结构解析,并把标题路径写入 Chunk 元数据;
- 表格问题效果差:将表头与数据行绑定,按照完整记录或逻辑区域分块;
- OCR 文档切分混乱:先清理页眉页脚、水印和错误换行,再调整分块策略。
这一步的关键是先定位问题属于“解析错误、边界错误、粒度错误还是上下文不足”,再采取对应措施。
4.嵌入向量
4.1 常见的 embedding
常见的 embedding 相关模型大概可以分成几类:
通用语言理解模型:BERT
这类模型本质上是通用文本理解底座,语言理解能力强,适合分类、抽取式问答、序列标注、精排等任务。
但它们不是专门为向量检索训练的。如果直接拿 BERT 的句向量去做相似度搜索,效果通常不稳定:有时两个句子看起来很像,向量距离却不近;有时语义不相关,分数又拉不开。
所以在 RAG 里,BERT 更常作为底座或精排模型使用,不是最推荐直接拿来做第一阶段召回。
句向量模型:SBERT、Sentence Transformers
这类模型是在 BERT 基础上改出来的,目标就是让句子向量更适合做相似度计算。它会把问题和文档分别编码成向量,然后用向量距离判断相关性。
它的优点是:开源、好部署、生态成熟、适合语义搜索和 RAG 召回。相比原生 BERT,它更适合做“用户问题和知识片段是否相关”的判断。
4.2 Embedding选择策略
先看要解决什么检索问题:如果只是普通知识库问答,优先选面向“问题找文档”训练过的检索模型;如果是代码、法律、金融、医疗,就要考虑领域模型;如果要中文问英文资料,或者中英混合检索,就要看跨语言能力。不要用一个“通用句子相似度模型”硬套所有场景。另外,看能不能满足工程约束:模型效果好还不够,还要看向量维度、索引大小、查询延迟、建库速度、调用成本、是否能私有化部署。比如商业 API 接入快,但有费用和数据合规问题;开源模型可控,但要自己维护推理服务和机器资源。