检索增强生成全链路解析:从文档加载到评估的大模型知识库工程实践
很多开发者第一次接触大模型时,第一反应是“调用 API 太简单了”:把 Prompt 封装好,用户问题丢给 GPT 或国内商用模型,一个聊天机器人就上线了。但真正进入企业项目就会发现,API 只是一个入口,真正难的部分在于:怎么让模型“看到”私有知识、怎么控制幻觉、怎么评估系统效果、怎么在有限显存环境下做推理部署。这些问题背后,正是 LLM 工程化的完整链路。
如果说 LLM 工程是一门需要体系化学习的课程,那么 GenAI 和 RAG 就是其中最核心的两个板块。RAG 不是“把一个 PDF 塞给大模型”这种一句话能讲清的操作,而是一套数据工程、检索系统、生成策略和评估体系的组合。这也是这门 Udemy 课程第三部分的重点:从纯 Prompt 调用走向生产级知识库应用。
这篇文章会以该课程第三部分为线索,梳理 RAG 完整链路(文档加载、切分、向量化、检索、生成)、Agentic RAG 和 OAG 等进阶方向、FP16/FP32/BF16 精度问题,以及 RAG 知识库评估指标。如果你正在做知识库问答、想系统学习 LLM 工程,或者正从传统后端转向 AI 应用开发,这篇内容应该能帮你建立一份更清晰的行动地图。
1. 为什么 RAG 是 LLM 落地绕不开的方向
先做一个判断:在 2025 年这个时间点,RAG 已经不只是“一个技术方案”,而是 LLM 应用落地事实上的标准形态。聊天、写作、翻译可能用不到 RAG,但凡是涉及私有数据、实时数据、专业文档的场景,RAG 几乎都是第一选择。
原因在于 LLM 本身有三大硬约束。
第一,知识截止。预训练模型的知识停留在训练数据截止时间,训练之后发生的事情它不知道。如果你问它某个产品上周发布的新功能,它大概率会一本正经地编一个答案。
第二,私有数据不可见。企业内部的规章制度、产品手册、客服工单、专利文档,模型在训练时根本没有见过。直接让模型回答这些内容,它只能靠“合理猜测”,而合理猜测在专业场景里往往就是事故。
第三,幻觉问题。即使模型不知道答案,它也会因为语言模型的本质而生成一个看起来通顺的回复。这不是模型“坏”,而是概率生成机制导致的必然结果。解决幻觉最有效的工程手段之一,就是用检索到的真实内容去约束生成范围。
没有 RAG 的时候,要让模型掌握新的领域知识,主流方案是微调(Fine-tuning)。但微调的成本高、周期长,每次知识更新都要重新训练一轮。更关键的是,微调擅长改变模型的行为方式和表达风格,却并不擅长记忆大量新事实。让一个 7B 模型通过微调背下一本 500 页的产品手册,既不经济,也容易过拟合。
RAG 的思路是把“记忆”从模型内部搬到外部:给模型一本可以随时翻阅的参考书,这个参考书就是你的知识库。模型回答之前,先从参考书里找出与问题最相关的几个片段,再把这些片段作为上下文交给模型生成答案。用“开卷考试”来类比 RAG 和普通 Prompt 生成的区别,非常直观。
但在真实项目中,这套“开卷考试”的工程复杂度远高于想象。很多人以为 RAG 就是“向量数据库 + Prompt”,实际做下去才发现,文档怎么拆、按什么粒度拆、检索用什么向量、top-k 怎么设、重排怎么做、效果用什么指标来衡量,每一步都可能让最终效果出现数倍的差距。
2. RAG 完整链路与核心概念
2.1 RAG 的标准流程
一个标准的 RAG 系统,由四个环节组成。
第一,数据准备阶段。原始文档需要经过加载、解析、清洗、切分,变成适合检索的文本块(chunk)。这个环节经常被低估,但它的质量直接决定了整个知识库的上限。如果原始文档是 PDF,页眉页脚、表格、扫描图片都会成为干扰项;如果切分粒度不对,再好的向量模型也检索不准。
第二,索引构建阶段。每个文本块通过 embedding 模型转换为向量,写入向量数据库。同时可以保留原始文本和元数据(来源、页码、标题等),方便后续溯源。
第三,检索阶段。用户输入问题后,先把问题也转换为向量,然后在向量数据库中做相似度检索,取回最相关的 top-k 个文本块。更复杂的系统会在这个阶段加入关键词检索和重排模型。
第四,生成阶段。把检索到的文本块和用户问题组装成 Prompt,交给 LLM 生成最终答案。为了减少幻觉,Prompt 中通常会明确要求模型只基于检索内容作答,无法回答时要明确说明。
2.2 RAG 和微调不是二选一
很多入门者会在 RAG 和微调之间纠结。我的建议是,先分清楚你要解决的是哪一类问题。
RAG 适合解决“知识更新”和“私有数据接入”的问题。比如 FAQ 问答、产品文档问答、行业报告摘要。它的优势是知识可以随时更新、答案可以溯源,一次数据更新不需要重新训练模型。
微调更适合解决“行为方式”和“输出格式”的问题。比如让模型学习某个业务场景下的固定话术、指令遵循习惯,或者把输出格式严格限定成系统需要的 JSON 结构。微调改变的是模型的“性情”,RAG 给的是模型的“素材”,两者服务的层次完全不同。
生产系统里常见的做法是两者结合:先微调出一个懂业务话术的底座模型,再用 RAG 为它提供实时事实。对大部分团队来说,RAG 的性价比上限更高,因为它不涉及训练资源。不要一上来就微调,先把 RAG 链路跑通。
2.3 从 RAG 到 Agentic RAG 和 OAG
课程第三部分的内容里,进阶方向会从“单次 RAG”延伸到“Agentic RAG”和“OAG”这类新概念。理解这两个概念,有助于看清楚 RAG 演进的脉络。
Agentic RAG 的核心变化是:把 RAG 从“一次检索 + 一次生成”升级为“多轮决策循环”。简单说,就是让 LLM Agent 承担检索规划的角色。它先理解用户问题,决定是否要检索、检索什么;拿到检索结果后判断信息是否足够,如果不够,就改写查询词再检索,或者调用外部工具获取信息;最后综合多轮结果生成答案。
这种设计解决的是复杂问题拆解场景。比如用户问“帮我对比一下 A 产品和 B 产品在三个维度的差异”,单次向量检索很难把三个维度一次找全。Agent 可以把这个问题拆成多个子问题,分别检索,再汇总。这就是 LLM Agent 与 RAG 结合的典型价值。
OAG 指的是 Ontology-Augmented Generation,即本体增强生成。它和 RAG 的区别在于,RAG 检索的是非结构化的自然语言文本,OAG 使用的是结构化的本体或知识图谱。本体会预先定义实体、关系和规则,比如在通信协议文档中,“ACK/NACK”“RRC 状态”这样的概念和它们之间的关联会被显式建模。生成时,系统先从本体中查询符合逻辑约束的事实,再交给 LLM 生成。
在专业领域,比如 3GPP 协议规范、医疗指南、金融监管文件这类文档中,OAG 比普通 RAG 更容易保证语义一致性。普通 RAG 容易把不同版本的协议内容混在一起,而基于本体的检索可以根据版本关系和实体约束做更精确的过滤。RAG 和 OAG 并不互斥,一个成熟的专业知识库系统往往两种手段同时使用。
3. 文档加载解析全流程:容易被低估的一环
如果说 RAG 链路中有一个最容易被低估、但又最决定效果上限的环节,那一定是文档加载和切分。很多团队把大量时间花在调 Prompt 和换模型上,却忽略了知识库的“地基”。实际上,如果输入到向量库的文本本身就是脏的、碎的、无上下文的,再好的检索模型也救不回来。
3.1 不同文档类型的加载难点
先从加载开始。现实项目里遇到的文档远比教科书例子复杂。
PDF 是最常见的格式,但 PDF 内部差异很大。文本型 PDF 可以直接抽取文字,但页眉页脚会污染正文;表格型 PDF 抽取后可能变成一段无结构的字符串;扫描型 PDF 需要先 OCR,而 OCR 又可能引入错字。Word 文档的问题在于样式标记和批注,PPT 的难点在于每页内容太碎片化。HTML 页面则需要抽取正文、剥离导航和脚本。这些工作属于典型的数据工程,听起来不“AI”,但直接决定下游质量。
在这个阶段,可以借助 LangChain 的 document loader 体系。它提供了针对 PDF、Word、HTML、Markdown、PowerPoint 等多种格式的加载器。也可以引入 Unstructured 等解析库来做表格识别和 OCR。不过,不能只依赖现成工具,业务文档的布局规则往往需要自己写解析逻辑。
3.2 文本切分策略
切分是另一个关键决策点。切分的核心矛盾是:块太大,向量化后语义会被稀释,检索不够精准;块太小,上下文不完整,LLM 生成时看到的背景信息太少。经验上的常见范围是 200 到 500 个 token。但这只是起点,具体值要根据文档类型和测试效果调整。
切分方式也分几个层次。
固定大小切分最简单,按 token 数硬切,但会切断句子和段落。递归字符切分是 LangChain 中最常用的方式,它按分隔符优先级递归尝试,尽量保住段落和句子结构,适合通用文档。按 Markdown 标题切分适合结构化文档,能保证每个块对应一个语义完整的章节。语义切分则利用 embedding 的相似度来决定边界,效果最好但计算成本高。
切分时还要设置 overlap,也就是相邻块之间的重叠区域。Overlap 的意义在于,如果一条知识恰好落在切分边界上,没有重叠就会导致它被切断,检索时自然找不到。一般建议 overlap 设置为 chunk size 的 10% 到 20%。
下面给出一段基于 LangChain 的 PDF 加载与切分示例代码。这段示例使用 LangChain 0.x 早期的 API 写法,新版中的导入路径可能有调整,请以你安装的包版本为准。
# document_prepare.py from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 1. 加载 PDF 文档 loader = PyPDFLoader("./data/product_manual.pdf") documents = loader.load() print(f"加载得到 {len(documents)} 页内容") # 2. 递归字符切分 splitter = RecursiveCharacterTextSplitter( chunk_size=400, # 每个文本块的目标大小(token 级别) chunk_overlap=50, # 相邻块重叠大小 separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""], ) chunks = splitter.split_documents(documents) print(f"切分后得到 {len(chunks)} 个文本块") # 3. 查看第一个块的元信息和前 200 字 print(chunks[0].page_content[:200]) print(chunks[0].metadata)在真实项目中,不建议把所有文档类型都统一走同一条切分逻辑。正确做法是先对文档做分类:规章制度类按章节切,产品手册类按功能模块切,FAQ 类按问答对整体保留。切分策略应该被当成一个可配置的参数,允许在评估后反复调整,而不是写死一次就不动了。
4. 环境准备与依赖安装
要动手跑通 RAG 示例,需要先准备一套 Python 环境。下面的环境清单不针对特定版本,建议以“安装时最新稳定版”为准,本文重点演示通用思路。
操作系统推荐 macOS 或 Linux,Windows 也可以运行,但依赖安装时更容易遇到编译问题。Python 版本建议 3.9 以上,最好使用 3.10 或 3.11。
核心依赖拆成三部分:
- 向量化与检索框架:langchain、langchain-community、langchain-openai。
- 向量存储:chromadb 或者 faiss-cpu。
- 文档解析:pypdf、unstructured、pandas。
- 模型调用:openai,如果使用国内大模型则改成对应 SDK。
创建虚拟环境并安装依赖:
python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install langchain langchain-community langchain-openai pip install chromadb faiss-cpu pip install pypdf unstructured如果你要调用 OpenAI 接口,需要提前配置环境变量。如果使用其他模型服务或本地部署的模型,替换对应的 embedding 和 LLM 类即可。
export OPENAI_API_KEY="你的密钥"如果你所在环境无法直接使用境外模型服务,可以选择国内厂商提供的兼容接口,或者使用本地部署的模型。这个选择不影响 RAG 的架构思路,只影响具体调用方式。
5. 向量化与检索:RAG 的核心决策点
5.1 Embedding 模型选择
文本块准备好之后,下一步是向量化。这一步的核心是选择一个合适的 embedding 模型。
Embedding 模型会把一段文本映射成高维向量,语义相近的文本在向量空间中的距离也更近。目前选择很多:OpenAI 提供了 text-embedding-3-small 和 text-embedding-3-large,国内也有 BGE、M3E 等开源中文 embedding 模型,还有各种本地可部署的模型。
选择 embedding 模型时,有三个考量因素。
第一是语言适配。如果你的知识库主要是中文,建议优先在中文语料上表现更好的模型,或者做多语言模型,而不是直接使用以英文为重心的小模型。
第二是维度与成本。维度越高,通常表达能力越强,但存储和计算成本也越高。text-embedding-3-small 默认只有几百维,对于大多数知识库场景已经够用。
第三是推理环境。如果知识库部署在内部网络,无法访问外部 API,那就必须选择可在本地 GPU 或 CPU 上运行的 embedding 模型。开源模型在本地部署完全没有问题。
5.2 向量数据库选型
向量数据库的作用是存储向量并支持快速相似度检索。选型可以从项目规模出发。
FAISS 是 Meta 开源的向量检索库,不是一个完整数据库,不提供数据持久化和权限管理,但它轻量、高效,适合原型验证和小规模项目。Chroma 是一个更完整的本地向量数据库,提供了简单的 API 和持久化能力,适合中小团队快速起步。Milvus 和 Qdrant 是面向生产环境的分布式向量数据库,支持水平扩展、多租户、权限控制和丰富的过滤能力,适合数据量达到几十万甚至上百万条级别的系统。
选型建议:个人学习和搭建原型直接用 FAISS 或 Chroma;企业项目评估 Milvus、Qdrant;如果团队已经引入了 Elasticsearch 8.x 且同时在用 ES 做全文检索,也可以考虑用它内置的向量检索能力,以减少运维组件的数量。
5.3 检索策略:向量检索、混合检索与重排
很多人把 RAG 的检索等同于“向量相似度检索”,但这只是起点。
向量检索适合语义相关性判断,比如“最大负载多少”能找到包含“负载能力”的文档,即使没有“最大负载”这四个字。缺点是对精确关键词不敏感。关键词检索(比如 BM25)恰恰相反,它擅长精确匹配,比如产品型号、错误码、人名,但无法理解同义改写。
真实知识库中,这两类问题同时存在。于是就有了混合检索:同时执行向量检索和关键词检索,再把两部分结果合并,用重排序模型(Rerank)重新打分,选出最终进入 Prompt 的内容。重排阶段通常把候选从 20 条压缩到 3 到 5 条,能显著提升生成质量,但也会带来额外的计算延迟。
对于刚起步的团队,建议先用纯向量检索跑通流程,再加入关键词检索和重排,逐步验证每一步带来的收益,不要一上来就堆复杂组件。
下面是一个基于 Chroma 和 LangChain 的完整检索问答示例。这里使用 LangChain 中常见的RetrievalQA封装。
# rag_qa.py from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings from langchain_openai import ChatOpenAI from langchain.chains import RetrievalQA # 1. 初始化 embedding 模型 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") # 2. 把上一步切分好的 chunks 写入 Chroma 向量库 vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db", ) # 3. 构建检索器 retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) # 4. 初始化生成模型 llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # 5. 构建 RAG 问答链 qa_chain = RetrievalQA.from_chain_type( llm=llm, retriever=retriever, return_source_documents=True, ) # 6. 执行查询 query = "产品手册中,该设备的最大负载是多少?" result = qa_chain.invoke({"query": query}) print("答案:", result["result"]) print("\n--- 参考来源 ---") for doc in result["source_documents"]: print("来源:", doc.metadata.get("source", "未知")) print("内容:", doc.page_content[:100])这个代码里有几个细节值得注意。
search_kwargs={"k": 4}表示只取回 4 个最相关的文本块。k 值太小容易漏答案,k 值太大则会把无关内容塞给 LLM,让答案跑偏。一般从 4 到 6 起步,再根据评估效果调整。
temperature=0是问答场景的常见配置。知识库问答要求稳定、忠实,不需要创造性输出。而写文案、头脑风暴类场景才需要提高温度。
return_source_documents=True一定要保留,否则你很难知道答案是来自知识库还是模型自己编的。生产系统中,来源溯源是知识库功能的基本要求。
5.4 从示例到生产系统
上面的示例跑通之后,要往生产系统走,还有几件事需要补充。
一是索引的增量更新。文档会更新、删除、追加,不能每次全量重建。实践中会引入调度任务和消息队列,按文档哈希或版本号判断是否需要重新向量化。
二是向量数据库的安全性。私有知识库通常涉及敏感数据。向量库本身往往没有严格的权限模型,需要在应用层做好访问控制,对用户查询和检索结果做权限过滤。
三是检索质量监控。线上系统一般会记录每次查询的召回文档、相关性分数和最终答案,并定期抽样做人工评估。这样当某个知识点的检索质量下降时,可以及时定位是数据更新问题还是模型变更问题。
6. LLM 精度问题:FP16、FP32、BF16 在工程中的取舍
在 RAG 示例中,你可能用的是托管模型 API,不需要关心权重存储的精度问题。但如果你要做本地推理、私有化部署,或者用开源模型搭建知识库,那么 LLM 的精度问题就是绕不开的工程决策。
6.1 为什么精度问题值得关注
大模型的权重是一组浮点数。浮点数的表示精度和范围,由格式决定。同一个模型参数,用不同精度存储和计算,会直接影响三件事:显存占用、推理速度、输出质量。
先给一个粗略的显存估算。一个 7B(70 亿参数)参数的模型,以 FP32 存储,参数大小约为 7 × 4 字节等于 28GB。FP16 或 BF16 是半精度,每个参数 2 字节,所以约 14GB。如果再算上推理过程的中间激活值,实际占用会更高。这也是为什么 8GB 显卡几乎跑不动 7B 模型的全量推理,通常需要配合 4bit 量化。
6.2 三种精度格式背后的设计差异
FP32 是单精度浮点数,宽度 32 位,其中指数位 8 位,尾数位 23 位。它是 CPU 和 GPU 上最通用的标准格式,精度最高。模型预训练和科学计算常用 FP32 作为基准。缺点是显存占用大、计算速度相对慢。
FP16 是 IEEE 半精度浮点数,宽度 16 位,其中指数位 5 位,尾数位 10 位。相比 FP32,它节省一半显存,计算速度也更快。但指数位只有 5 位,导致它能表示的数值范围变小。当某个权重数值很大时,会被表示成无穷大;当数值极端小时,又容易变成 0。这就是“溢出”和“下溢”问题。在训练大模型时,如果直接用 FP16,梯度很容易在反向传播中丢失。
BF16 是 Brain Floating Point,也是 16 位,但指数位有 8 位,和 FP32 相同,尾数位只有 7 位。它的巧妙之处在于“牺牲精度,保留范围”。因为和 FP32 拥有相同的指数范围,BF16 在高数值范围下更不容易溢出,训练时稳定性明显优于 FP16。代价是尾数少,同一个数值,BF16 能表示的小数精度比 FP16 低。
6.3 三种精度的对比
| 精度格式 | 位宽 | 指数位 | 尾数位 | 主要优点 | 主要缺点 | 常见用途 |
|---|---|---|---|---|---|---|
| FP32 | 32 位 | 8 位 | 23 位 | 精度最高 | 显存占用大、计算慢 | 预训练基准、科学计算、调试 |
| FP16 | 16 位 | 5 位 | 10 位 | 显存减半、速度快 | 数值范围小,训练易溢出 | 混合精度训练、GPU 推理 |
| BF16 | 16 位 | 8 位 | 7 位 | 数值范围与 FP32 相同,训练稳定 | 尾数少,精度略低 | 大模型预训练、推理部署 |
6.4 实际工程中的选择建议
在本地部署开源 LLM 做知识库推理时,更稳妥的选择通常是 BF16,而不是 FP16。因为大模型推理阶段对数值范围更敏感,FP16 在极端权重分布下偶尔会出现输出质量劣化。而在需要极致推理速度时,GPU 对 FP16 的计算吞吐往往更高,这时可以使用 FP16 并做针对性评测,确认任务指标没有明显回退再做选择。
如果显存实在不够,就需要考虑量化方案,把权重降低到 INT8 甚至 INT4。量化后模型文件更小,推理速度提升,但会有更明显的精度损失。是否可接受,不能拍脑袋决定,必须在业务测试集上做对比评估。
这里真正容易踩坑的地方是:很多人看到模型量化后“看起来回答还挺正常”,就直接上生产,结果在专业术语密集、格式要求严格的场景中,量化模型频繁答非所问。任何精度选择都应该和评估体系绑定,用数据决策,而不是凭感觉。
7. RAG 知识库指标:如何评估一套 RAG 系统
很多开发者在把 RAG 系统搭起来之后,会遇到一个尴尬问题:系统已经能运行了,但到底好还是不好,说不清楚。如果连好坏都无法量化,后续优化就无从下手。RAG 系统的评估,是这部分课程非常强调的内容。
7.1 先从离线评估集开始
在讨论指标之前,先建立一个基本前提:评估需要一套固定的测试集。没有测试集谈指标,都是纸上谈兵。
测试集的构建方式是:从知识库中挑选一批有代表性的文档,针对它们设计 50 到 100 条问题,并为每个问题标注期望答案或至少标注“应该从哪些文档片段中检索”。这个集合称为黄金集。以后每次调整切分参数、更换 embedding 模型、修改检索策略,都在同一套黄金集上跑,看指标变化。
7.2 检索质量指标
RAG 系统的检索环节直接决定了“模型看到了什么”。如果检索结果里根本没有正确答案,生成环节再强也白搭。所以检索质量必须独立评估。
Hit Rate 是最直观的指标,表示在检索返回的 top-k 结果中,是否至少有一条是相关的。如果检索了 100 个问题,其中 85 个问题在 top-5 结果里有正确答案,Hit Rate@5 就是 85%。它反映的是“有没有召回”。
MRR 全称是 Mean Reciprocal Rank,用于衡量第一个相关结果出现在什么位置。如果第一个相关问题排在第 1 位,得 1 分;排在第 3 位,得 1/3 分。MRR 越高,说明相关结果越靠前。它反映的是“召回到什么位置”。
Precision@k 和 Recall@k 则进一步细化了准确率评估。Precision@k 表示返回的 k 个结果中有多少比例是相关的;Recall@k 表示所有相关文档中被召回的比例。
这些指标之间的关系可以这样理解:Hit Rate 是底线,MRR 是排序质量,Recall 和 Precision 是更严格的双向评估。在实际项目中,通常先看 Hit Rate,再关注 MRR,最后结合业务需求看是否要调整 k 值。
7.3 生成质量指标
检索质量好,不代表最终答案好。LLM 可能没有遵循 Prompt 指令、可能遗漏关键信息、也可能在检索内容之外自行发挥。生成质量需要单独评估。
Faithfulness(忠实度)衡量生成内容是否忠实于检索到的上下文。如果模型明明看到资料里写“最大负载 50kg”,却在答案里说“最大负载 80kg”,就是不忠实。这个问题本质上就是幻觉。
Answer Relevancy(答案相关性)衡量生成内容是否切题,是否回答了用户的问题。有时候模型复述了一堆原文,但用户问的是“怎么解决”,模型回答的是“是什么”,就是不相关。
这两个指标在生产系统中都可以自动计算,也可以抽样人工评分。自动计算的典型工具是 RAGAS,它利用 LLM 作为评测器,输入问题和答案以及检索文档,返回上述指标的分数。但要注意,用 LLM 评 LLM 会有偏好偏差,关键业务场景建议保留人工抽检环节。
7.4 一个最小化的 MRR 计算示例
理解 MRR 最快的方式是亲自动手实现一次。下面的伪代码演示了 MRR 的计算逻辑。
# eval_mrr_example.py def reciprocal_rank(relevant_rows, k): """ relevant_rows: 检索结果列表,按相关性分数降序排列 k: 只看前 k 条结果 返回该查询的 reciprocal rank 值 """ for rank, row in enumerate(relevant_rows[:k], start=1): if row["is_relevant"]: return 1.0 / rank return 0.0 # 示例:一次查询的检索结果,按相关性从高到低 query_result = [ {"doc_id": "doc_1", "is_relevant": False}, {"doc_id": "doc_2", "is_relevant": True}, {"doc_id": "doc_3", "is_relevant": False}, ] k = 3 rr = reciprocal_rank(query_result, k) print(f"本次查询的 reciprocal rank: {rr}") # MRR 就是多次查询 reciprocal rank 的均值 all_queries = [ {"doc_id": "doc_1", "is_relevant": False}, {"doc_id": "doc_2", "is_relevant": True}, {"doc_id": "doc_3", "is_relevant": False}, ] def mean_reciprocal_rank(queries, k): total = 0.0 for query in queries: total += reciprocal_rank(query, k) return total / len(queries) mrr_value = mean_reciprocal_rank([query_result, query_result], k=3) print(f"MRR@3: {mrr_value}")在实际项目中,你可能不会手写这些指标,而是借助 Ragas 或其他评测框架。但理解计算逻辑非常重要。只有理解每个指标在奖励什么、惩罚什么,你才能在调优时判断“指标上升是否真的意味着系统变好”。
8. 常见问题与排查思路
RAG 系统在开发阶段的问题非常多,很多现象看起来相近,但原因完全不同。下面整理了一份常见问题排查表,可以收藏备用。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动时依赖安装失败 | Python 版本不兼容或缺少编译环境 | 查看 pip 错误日志,确认 Python 版本 | 升级到 Python 3.10+,或使用 conda 创建隔离环境 |
| 知识库检索不到相关内容 | 文档切分过大或过小,语义被稀释或截断 | 打开向量库,查看文本块语句是否完整 | 调整 chunk_size 和 chunk_overlap,重新向量化 |
| 答案和用户问题不相关 | 检索返回了无关内容,且进入了 Prompt | 打印 source_documents 查看召回文档 | 调整 top-k、加入混合检索或重排 |
| 模型回答出现幻觉 | 检索结果为空,模型被迫自行补全 | 检查检索结果的相似度分数是否过低 | 设置相似度阈值,明确要求模型不知道就说不知道 |
| 检索到正确内容但答案反而混乱 | 塞进 Prompt 的上下文太多或相互矛盾 | 检查 source_documents 的数量和内容 | 降低 k 值,或对文档做去重和版本过滤 |
| 专业术语大量出现时效果变差 | embedding 模型对专业领域理解不足 | 在黄金集上对比不同 embedding 模型的指标 | 替换为领域微调过的 embedding 模型,或引入关键词检索 |
| 向量库持久化后重启数据丢失 | 未配置持久化目录或使用了纯内存模式 | 检查向量库初始化参数 | 配置 persist_directory 或使用服务端向量数据库 |
| 本地部署模型显存不足 | 精度选了 FP32/FP16,模型参数过大 | 查看显存占用日志 | 切换到 BF16 或量化到 INT8/INT4,验证效果 |
| 答案经常引用过时文档 | 知识库没有增量更新机制 | 检查文档更新时间戳 | 引入数据版本管理和索引重建任务 |
排查 RAG 问题有一个通用顺序:先确认检索阶段有没有正确召回,再确认 Prompt 组装有没有问题,最后再怀疑生成模型。很多人第一反应是“模型不行”,结果花了很多时间换模型,问题依旧。实际上,RAG 系统的大部分效果问题都出在数据和检索环节。
9. 最佳实践与工程建议
RAG 项目做到最后,拼的往往不是某个环节的极致优化,而是工程体系是否完整。下面这几条最佳实践,来自常见生产项目的经验总结。
9.1 从最小可行 RAG 开始
不要在一开始就引入 Agentic RAG、混合检索、知识图谱等复杂组件。第一步应该用最快的速度跑通“文档加载 + 向量化 + 检索 + 生成”的最小链路,让业务方看到效果,也让自己对数据情况有感知。然后再用评估数据驱动优化。否则复杂系统一旦出问题,定位成本会非常高。
9.2 把文档预处理当成第一优先级
知识库项目的天花板往往在文档预处理阶段。建议对文档格式做一次全面盘点,把“哪些文档适合直接切分”“哪些文档需要 OCR”“哪些文档需要专门解析表格”列成明细表。对经常出现的固定版式,可以写专门的解析函数,而不是寄希望于一个通用解析器搞定所有文档。
9.3 检索环节先保证召回,再做精排
在检索链路设计上,可以用两级思路:第一级尽量放宽条件,保证相关文档能被召回;第二级用重排模型或更精细的过滤逻辑,把真正有用的内容排到前面。先优化 Hit Rate,再优化 MRR,最后才看生成质量。这个顺序不能反。
9.4 Prompt 要明确约束生成行为
生成阶段的 Prompt 至少应该包含三部分:系统指令、检索上下文、用户问题。系统指令要明确告诉模型“只能依据检索内容回答,不要使用训练时学到的知识补充;如果检索内容不足,回答不知道”。同时要求模型在回答时引用来源编号。这个设计能显著降低幻觉率,也让溯源成为可能。
9.5 记录完整的运行日志
线上 RAG 系统建议记录每次请求的完整链路:原始问题、改写后的问题(如果有)、召回的文档列表、相似度分数、送入 Prompt 的最终上下文、模型输出、耗时。这些日志既是调试故障的依据,也是后续搭建自动化评测集的数据来源。很多团队忽略了这一步,等到上线后效果变差,才发现没有任何线索可以回溯。
9.6 安全与权限是最后的底线
如果知识库包含了内部资料或用户隐私数据,在架构设计之初就要考虑权限隔离。不要让一个用户可以检索到另一个用户私有范围的内容。向量数据库层的过滤能力很重要,应用层也要做身份认证和操作审计。涉及敏感数据的项目,务必在测试环境验证权限过滤逻辑后再上线,并准备好回滚方案。
10. 总结:LLM 工程师的核心能力模型
回到这门 Udemy 课程第三部分的整体定位,它真正想训练的能力不是“会调用 API”,而是“会设计一套完整的 GenAI 系统”。从 RAG 链路到 Agent 概念,从精度问题到评估指标,这些知识点串联起来,指向的是 LLM 工程师的四种核心能力。
第一是数据工程能力。知道如何从真实业务文档中提取可用知识,如何处理 PDF、Word、HTML 等复杂格式,如何设计切分策略。
第二是检索系统设计能力。知道如何选 embedding、如何选向量数据库、如何设计混合检索和重排,而不是只会调用一个现成接口。
第三是模型部署与成本控制能力。理解 FP16、BF16、量化的取舍,知道在有限的 GPU 资源下如何平衡速度和质量。
第四是评估能力。能建立测试集,能看懂指标变化,能通过数据定位瓶颈。这是区分“能跑 Demo”和“能上生产”的分水岭。
如果这篇文章让你有收获,建议马上选一份实际文档,按照第二章到第五章的流程搭一个最小知识库,再做一套 50 条问题的评估集,记录当前指标。然后试着调整 chunk_size、切换 embedding 模型、加入重排,观察指标和答案质量的变化。
做 LLM 应用开发,最大的误区是以为“视频课刷完就是会了”。真正值回票价的方式,是把课程里的评估方法用到自己的知识库里,把系统从“能跑”改到“好用”。RAG 工程中还有很多细节值得深入,比如更复杂的文档解析、Agent 多轮检索、基于本体的约束生成、以及更细粒度的评测体系。每一条都值得再单独写一篇实践笔记。
