news 2026/9/5 4:14:43

RAG实战拆解:从Notebook到完整知识库检索增强生成链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RAG实战拆解:从Notebook到完整知识库检索增强生成链路

先跑个题。我以前看 RAG 相关的东西,总有一种“道理我都懂,但让我自己搭一套,完全不知道从哪下手”的感觉。什么 embeddings、向量数据库、检索增强、Chunk 切分,每个词单独拿出来都能聊两句,但合在一起——尤其是当那些概念堆在论文和博客里的时候——就变得特别抽象。直到我逼着自己,把一个端到端的 RAG Notebook 从头到尾、一个单元格一个单元格地跑了一遍,才真正意识到:这东西没什么玄乎的,它本质上就是一个拆解得很清楚的查资料流程,只是把“人查资料”的这个动作,用代码重新实现了一遍。

这篇内容不是论文复现,也不是框架文档的翻译。我会把那一套跑通的 Notebook 拆开,讲清楚里面每一个环节到底在做什么、为什么这么做、以及在真实项目里哪些地方容易出问题。适合谁看?如果你跟我一样,被各路 RAG 概念轰炸过,但还没亲手跑通过一个完整链路,或者跑通了却总觉得隔着一层纱,不确定每一步在自己的数据上为什么有效或者无效,那这篇文章应该能帮你把最后那层窗户纸捅破。

1. 先别急着写代码:RAG 的本质是“查、补、写”三段式

很多人在学 RAG 的时候,第一个误区就是直奔代码,上来就pip install llama-index或者langchain,然后照着文档抄一遍完事。跑通了还好,万一跑不通,或者跑通了但效果很怪,就完全不知道去哪里排查。因为头脑里没有一个清晰的“预期模型”——你不清楚每一步到底应该产出什么。

1.1 一句话说清楚 RAG 在做什么

用最朴素的话讲:RAG(Retrieval-Augmented Generation,检索增强生成)就是给大模型配了一个可以随时翻阅的资料库。模型本身有它的“内存”(训练时学到的知识),但当它面对一个需要特定上下文、或者超出训练时效、又或者需要企业内部知识的问题时,只靠“内存”回答就会开始胡编。RAG 的做法,是在模型开口回答之前,先从外部资料库里把相关的片段“查”出来,然后把问题加上查到的片段,一起扔给模型,让它基于这些材料作答。

那一套经典的 RAG Notebook,不管用的是什么框架,本质上都是在做这三件事:

  1. 查(Retrieval):根据用户问题,从知识库里找出最相关的几段文本。
  2. 补(Augmented):把“用户问题 + 检索到的文本片段”组装成一个新的、带上下文的提示词(Prompt)。
  3. 写(Generation):大模型阅读组装好的提示词,生成最终答案。

我在跑通 Notebook 之前的困惑,很大程度来自于把这三件事混在一起看。一旦拆开,很多问题的定位就变得清楚:比如答案里出现了资料里没有的信息,那问题可能出在“查”(没查准)或者“写”(模型太自由)上;比如答案答非所问,那大概率是“查”这一步返回的内容本身就是错的。

1.2 Notebook 是学习 RAG 最合适的载体

可能有人会问,为什么不直接用现成的框架,或者直接用 Flask/FastAPI 写一个服务来学?我的体验是:学习阶段,Notebook 的交互式执行特性,几乎是为你理解这种多阶段链路量身定做的

原因很简单。RAG 链路每一阶段的产出都是可被“观察”的数据对象:

  • 切分后,你可以直接打印出每个 Chunk 的内容,看看边界是不是合理。
  • 向量化后,你可以检查向量的维度、相似度的分布。
  • 检索后,你可以看看 Top-K 的结果跟问题到底相不相关。

用 Notebook 跑,我可以随时把中间变量打印出来看,可以在任何一个单元格后面插入新代码做验证,发现问题立刻就地调整。你要是直接堆一个 .py 脚本然后python main.py,中间一有不对劲,就得加日志、重新跑,调试效率低太多了。而且 Notebook 天然是分段式的,每一段代码的大小刚好对应 RAG 链路里的一个步骤,这本身就是一种很好的教学分解。

我当时用的就是 Jupyter Notebook 环境,顺手把环境里没装好的依赖补了补,然后一个单元格一个单元格地执行下来。那种“眼睁睁看着每一步输出,逐步拼出完整链路”的体验,比读十篇架构图都管用。

1.3 一套完整的 RAG Notebook 应该包含哪些环节

这里先给大家一个全局图景,方便后面我们一一对照。我跑的那套 Notebook,主线流程如下:

环节核心任务关键依赖/工具典型产出
文档加载读取本地文档内容PyPDF2、python-docx、txt 读取纯文本字符串
文本切分把长文本切成有语义边界的片段字符切分、递归字符切分、句级切分Chunk 列表
向量化将每个文本片段转成向量OpenAI Embedding、HuggingFace Embedding向量列表
索引构建把向量存入数据库并建立索引Chroma、FAISS、Elasticsearch向量索引
用户查询处理把问题转成向量与文档向量化同一模型查询向量
相似度检索找出最相关的 Top-K 个 Chunk向量相似度计算(余弦/点积/L2)相关文档片段
提示词组装把问题和检索结果拼为 PromptPrompt 模板完整 Prompt
生成调用 LLM 生成答案OpenAI、智谱、Ollama 等最终回答

我当时看着这个表格,最大的感受就是:RAG 并不神秘,它就是一个典型的流水线作业,每一个环节都有明确的输入、输出和衡量标准。下面我就按这个链路,把每一步实际跑通时的细节和思考展开聊。

2. 检索(Retrieval)环节的实战拆解:你的知识库到底是怎么被“查”出来的

检索是整个 RAG 里最核心、也是最容易翻车的地方。原因在于:你如果查出来的片段本身就不对,后面 Prompt 拼得再花哨、模型再聪明,也只能基于错误材料作答,属于“垃圾进,垃圾出”。

2.1 切分:文本切分直接影响后续所有环节

很多人上手 RAG,第一个忽略的关键环节就是“切分”,觉得无非就是把长文本按固定长度截断。这个想法害人不浅。

我当时跑的第一版切分,用的是最简单的按字符数硬切:每 500 个字符切一段,段与段之间重叠 50 个字符。结果检索效果惨不忍睹。为什么?因为硬切会把一个完整的句子甚至一个完整的术语拦腰截断。比如一个句子的语义到后半段才展开,但前半段里恰好含有用户问的关键词,那这个片段即使被检索出来了,也是残缺的,模型很难基于它作答。

正确的做法是使用递归字符文本切分器,它本质上是一个“有层次的切分策略”:先尝试用最粗的分隔符(比如段落\n\n)切,如果切出来的块还是太大,就用下一级分隔符(比如句号、问号)继续切,直到块大小满足要求。这样做的好处是,最大程度上保证每个 Chunk 是一个相对完整的语义单元。

我后来在 Notebook 里手动调整了几个关键参数,记录如下:

参数作用我的经验值
chunk_size每个块的理想大小(字符数)300-800,取决于文档类型和模型上下文窗口
chunk_overlap相邻块之间的重叠部分通常为 chunk_size 的 10%-20%
separators切分时按优先级使用的分隔符列表["\n\n", "\n", "。", "!", "?", ". ", " "]

为什么一定要有 overlap?因为很多情况下,一个概念的完整表述可能恰好跨越两个 Chunk 的边界。有了重叠,检索时无论是命中了前一段还是后一段,都不至于丢失掉边界附近的上下文信息。

这里分享一个我在实际使用中的判断技巧:切分粒度需要根据你的问题类型做权衡。如果你的问题是“某某概念的定义是什么”,那稍微偏小一点的 Chunk(300-500字)往往效果更好,因为它是直接命中目标;如果你的问题是“对比一下 A 方案和 B 方案的优劣势”,那需要跨多个段落的上下文,Chunk 就要偏大一点(800-1000字),甚至要考虑用摘要、父子分块这类进阶手段。Notebook 的好处在此刻就体现出来了——我可以把切分参数调一遍,重新跑一遍,马上对比检索结果,来回几次就能找到最适合当前数据集的参数。

2.2 向量化:Embedding 的本质是把文本变成可计算的位置

切分完,下一步是把每个文本片段做向量化。这一步很多人直接调用现成的 Embedding API 就完事了,但有一点值得想清楚:Embedding 模型把文本映射到一个高维向量空间里,语义相近的文本在这个空间里的距离也更近。这才是“语义检索”能成立的基础。

我当时在 Notebook 里用的是 OpenAI 的text-embedding-3-small,其实换别的也同理。调用逻辑非常简单:

from openai import OpenAI client = OpenAI() texts = ["RAG 是检索增强生成", "向量数据库用于存储和检索向量"] embeddings = client.embeddings.create(model="text-embedding-3-small", input=texts) for i, data in enumerate(embeddings.data): print(f"文本 {i} 的向量维度:{len(data.embedding)}")

这里有一个我在学习过程中踩过的坑:Embedding 模型必须保持前后一致。也就是说,你建索引时用的是什么模型,查询时也必须用同一个模型。如果你建索引时用text-embedding-3-small,查询时换成了text-embedding-3-large,那么就算两个模型名字很接近,它们的向量空间分布也不一样,检索效果会急剧下降。这个错误初看不明显,因为代码不会报错,只有检索质量异常差会让你感到蹊跷。

另外,如果你们是企业内网场景,没法调用外部 API,也可以用 HuggingFace 上的开源模型跑本地向量化。比如BAAI/bge-small-zh-v1.5或者moka-ai/m3e-small,在中文场景下表现都不错。用开源模型的好处是数据不出域,坏处是中文语义理解的上限通常比商业 API 差一截,这个需要结合自己的数据体量来选。

2.3 向量检索:Top-K 怎么选,Dense 和 Sparse 怎么配合

查(Retrieval)这一步,目标是“给定一个问题,从向量库里找出最相关的 K 个 Chunk”。常见的做法是:把用户问题用同一个 Embedding 模型转成向量,然后在向量数据库里做相似度搜索,返回最相似的 Top-K 条。

Top-K 的选择是一个有意思的权衡。K 太小的缺点是容易漏——比如你的问题需要跨 3 个 Chunk 才能回答,K=2 就怎么都不够。K 太大的缺点是容易“噪声干扰”——检索结果里的第 8、第 10 条可能跟问题关联度很低,这些低质量的片段混进 Prompt 里,反而会干扰大模型做出正确的回答。我的经验是,如果切的 Chunk 是 300-500 字,Top-K 设置在 3-6 之间是一个比较稳妥的区间,既保证有足够的参考信息,又不至于给模型输入太多无关内容。

不过,“只靠向量相似度”其实有一个天然的盲区:关键词的精确匹配。向量检索是语义层面的匹配,它理解“苹果好吃”和“这种水果口感不错”意思相近,但对于专有名词、编号、代码变量名这类必须逐字匹配的信息,向量检索反而可能不够可靠。比如你去检索一段文档里某个函数的完整签名,向量召回的结果可能包含了很多“函数”相关的泛泛而谈,却没有把那个精确的函数定义捞回来。

这就要提到混合检索的概念了。我跑的 Notebook 里后期有对比实验,就是把向量检索(Dense Retrieval)和基于关键词的稀疏检索(Sparse Retrieval,比如 BM25)结合起来,最后用加权融合(RRF,Reciprocal Rank Fusion)对两组结果做合并排序。用公式解释 RRF 就是:对每个文档,它在不同检索结果中的排名取倒数,把所有倒数加起来作为最终得分,排名越靠前的文档融合后得分越高。

我自己的体会是:如果你们的知识库以技术文档、产品手册、代码注释为主,强烈建议在向量检索之上叠加一层 BM25。加和不加,在有精确术语的查询上效果差距很明显。纯粹的向量检索更适合处理“用大白话问专业问题”的场景,比如“文档里提到了哪些处理并发的手段”,这种问题用关键词可能很难覆盖全。

2.4 关键实操:相似度阈值与打分排序的经验值

在 Notebook 里,我习惯把每次检索出来的结果(片段文本 + 相似度分值)都打印出来,肉眼检查一遍。这一步看似笨拙,却帮我建立起了对“什么算相关”的直觉。

当我打印出相似度分数时,经常会看到:排第一的片段得分 0.82,排第二的 0.78,这两个可以认为是“比较稳的相关结果”;到第 5 名以后,分数可能就只有 0.6 甚至更低,这时候的内容就很容易跟问题跑偏了。所以后来我在自己的代码里加了一个简单的硬性阈值:相似度低于 0.55 的 Chunk 直接过滤掉,不回传给大模型。这个阈值当然不是通用的,而是跟你选的 Embedding 模型强相关,不同模型的分数分布完全不一样。关键是你要建立“用自己的模型在自有数据上跑出来的分数分布”这个基线。

还有一个小技巧:在相似度计算方式上,大部分向量数据库默认返回的是余弦相似度,但如果你用的是某个中文专用 Embedding 模型,它训练时可能推荐的是内积或者 L2 距离,这个一定要看模型的文档说明。选错了距离度量,分数分布会变怪,但更麻烦的是你可能会错误地调整相似度阈值。

这一阶段的实际体会是:检索质量决定了 RAG 的上限,生成质量只是逼近这个上限。后面 Prompt 写得再精妙,也无法弥补检索到的片段就是不对的缺陷。所以在时间分配上,建议大家把至少一半的调试精力花在检索环节,别急着去调 Prompt。

3. 增强(Augmented)环节的实战拆解:上下文是怎么“补”给模型的

检索完拿到一堆相关片段,下一步并不是把它们直接扔给大模型,而是要做一个“组装”动作。这个环节在 RAG 论文里叫 Augmented,中文可以理解为“增强”——把原始问题和检索到的片段融合成一条更有信息量的指令。

3.1 怎么把检索结果拼装成有效的 Prompt

很多人第一次写 RAG Prompt,就是一句话:“请根据以下资料回答问题:{context}。问题:{question}”。跑通是能跑通,但效果往往很一般。因为模型缺少足够的“行为约束”,它有可能会脑补出资料里没有的信息,或者对资料中的内容过度发挥。

我后来在 Notebook 里用的 Prompt 模板大概是这样的:

你是一个知识库问答助手。 请仅基于以下资料片段回答问题。如果资料中没有足够信息,请直接回答“根据现有资料无法回答”。 资料片段: {context} 用户问题: {question}

这里面有两个关键设计,值得展开说一下:

  1. “仅基于以下资料”这个约束。这一句话至关重要,它告诉模型:你的知识背景已经被限制了,不要尝试回忆训练数据里的内容,只需要看上下文里有什么。这能在很大程度上抑制“幻觉”问题,当然不是百分之百,但比完全没有约束要可靠得多。
  2. “没有足够信息就坦承不知道”这个兜底选项。如果检索到的片段确实跟问题不相关,模型有权利说“我不知道”,而不是强行生成一个看似合理但实际错误的答案。对生产级知识库系统来说,“正确说不懂”比“自信地胡说”要重要得多。

把检索结果填充到{context}里时,我建议加上来源编号,比如用 [1]、[2]、[3] 这样的标号对应每个片段,并在 Prompt 里要求模型在引用某个片段的信息时标注来源编号。这样做有两个好处:一是用户可以看到答案是参考了哪几段资料,溯源更方便;二是模型在标注来源的过程中,会更倾向于严格贴近对应片段的内容,间接提升答案的准确性。

3.2 引用片段要不要、怎么给模型

关于引用,业内有一个常见辩论:是把检索到的 Top-K 个片段全部拼在 Prompt 里,还是只挑选其中最相关的部分?

我早期的 Notebook 是“全给”策略:检索出 6 个片段,全部塞进上下文,每个片段前面标注 [1] 到 [6]。但很快我发现,当检索质量不完美时,这 6 个片段里总有一两个跟问题只是“沾亲带故”。模型面对这些无关片段时,有时候会强行把它们编织进答案里,导致答案变得啰嗦甚至跑偏。

后来我调整了一下,改成两段式筛选:

  1. 先用向量检索取回 Top-10 候选。
  2. 再用一条轻量级的规则或者模型重新精排,只保留分数最高的 Top-4,或者按相似度阈值过滤掉明显不相关的片段。

这种做法的本质是“先生成候选,再二次筛选”,效果比我之前“一次性 Top-6 全塞进去”要好不少。代价是增加了一步调用的延迟,但对于回答质量要求高的知识库场景来说,这个取舍非常划算。

3.3 上下文窗口不够用怎么办:滑动窗口和摘要融合

做大模型应用的人,绕不开上下文窗口的物理限制。如果知识库里的内容特别长,比如一个 20 页的产品手册,你很难把整本书都塞进 Prompt 里。即使塞得下,大模型也会被海量无关信息干扰,注意力被稀释。

解决思路有两个方向,我在 Notebook 里各跑了一遍:

  • 滑动窗口切分:前面讲的文本切分,本质上就是为了应对这个问题。把一个长文本切成多个 Chunk,每次只取其中 Top-K 个最相关的,就是最朴素有效的窗口方案。
  • 摘要融合(Map-Reduce):如果问题需要理解文档的整体结构或跨多个章节的综合信息,只靠局部 Chunk 是不够的。Map-Reduce 的做法是:先把每章或每个大段落让模型各生成一段摘要,再把所有摘要汇总成一个全局摘要,把全局摘要和必要的局部 Chunk 一起作为上下文输入。代价是理解更全面、更耗时,还会丢失一些具体细节。

我当时遇到的一个场景是:用户问“这个产品的故障排查步骤都能怎么走”,但单个 Chunk 只覆盖了某一个具体故障的排查步骤,根本没法形成整体方案。后来我用了摘要融合,先生成各章摘要,再让模型结合摘要和几个命中的局部 Chunk 综合回答,效果才像样。

“补”这个环节,很多人以为拖个模板就行,其实它是 RAG 链路里最体现工程经验的地方之一。你给出的上下文长什么样、以什么顺序排列、包含哪些约束,都会直接影响最终答案的质量。

4. 生成(Generation)环节的实战拆解:别让输出环节拖垮整个链路

当 Prompt 组装好后,最后一步就是交给大模型生成。这个环节看似只是“调一个接口”,但其实有不少细节值得琢磨。从我的经验看,生成阶段的问题主要来自三个方面:参数设置不合理、模型约束不到位、输出后处理缺失。

4.1 生成前要确定的几个关键参数

调用大模型时,有四个参数跟 RAG 场景强相关:temperaturetop_pmax_tokenspresence_penalty(部分 API 支持)。我的建议是:在知识库问答场景下,温度要调低

  • temperature:控制随机性,取 0-1 之间的值。对 RAG 场景,我一般设成 0.1 或 0.2,甚至 0。因为我们希望模型尽量忠实于检索到的资料,不需要太多创造性发挥。设太高(比如 0.7 以上),模型很可能会在资料基础上“添油加醋”,产生幻觉。
  • top_p:核采样,与温度类似。实践中一般跟 temperature 一起调,我习惯保持默认 0.9 左右,或者干脆设为 1,只靠 temperature 控制。
  • max_tokens:控制生成长度。要结合你预期的答案长度来设,太短会被截断,太长会增加延迟和成本。
  • presence_penalty:控制话题重复度。在知识库问答里,一般设 0 就好,不需要模型发散新话题。

我见过不少初学者(包括我自己早期)在 RAG 场景里把 temperature 调到 0.7,模型生成时经常跑偏到“我知道你想问这个,但是让我先给你介绍一下相关的背景知识……”这种无意义的开头。设置低温度是 RAG 最基本的一条经验。

4.2 怎么让模型“不瞎编”:提示词约束和事实校验

除了调参数,Prompt 层面的约束也非常重要。我习惯在 Prompt 里同时加入三个“防御性”设计:

  1. 格式约束:明确要求“直接回答问题,不要输出多余解释”“不要提及‘根据资料显示’这类客套话”。因为模型如果输出了“根据资料显示”,不仅在引用格式上不好看,还容易把它后续生成的推测也包装成“资料显示”的权威口吻。
  2. 态度约束:明确要求“在没有明确证据的情况下,不要做推测”“对信息不足的问题说不知道”。
  3. 事实校验(可选):如果你们的场景对事实准确性要求极高,可以在第一次生成之后,再让模型对答案里的每个核心陈述,回到原始资料片段里找依据,找得到就保留,找不到就删掉或者标注为推断。这相当于加了一层“自我质检”。在 Notebook 里可以很轻松地用两个单元格实现:第一个生成,第二个用一条校验 Prompt 二次处理。

有一种情况值得注意:大模型可能会“过度依赖资料”,比如资料里有一句“苹果是一种水果”,用户问“苹果是什么”,模型可能会把资料原文完整复制出来,而不是组织成更自然的语言。这在技术文档问答里问题不大,但在客户服务类的场景里就会显得生硬。可以接受的折中方案是:Prompt 里加一句“用自己的话组织语言,但不得偏离资料内容”,这看起来简单,实际能明显改善回答的可读性。

4.3 常见生成质量问题与排查思路

我在跑通 Notebook 后整理了一张排查对照表,分享给大家,这张表在后续实际项目中帮了我很多:

现象可能原因排查方向
回答中出现资料里没有的信息temperature 过高 / 检索内容不够相关 / Prompt 约束不足降低 temperature;检查检索 Top-K 结果的相关性;强化“仅基于资料回答”的 Prompt 约束
回答过于简短,信息量不足max_tokens 设置过小 / Top-K 太小 / Chunk 太碎调大 max_tokens;增加 Top-K;调整切分参数,增大 Chunk 大小
回答答非所问检索出来的片段本身不相关检查 Embedding 模型是否一致;检查切分边界;尝试混合检索
回答风格生硬,像在念资料缺少“用自己的话组织”约束 / 模型能力有限Prompt 里加入表达方式要求;考虑换更强的生成模型
回答引用了不存在的来源编号模型幻觉导致生成了错误的 [1][2] 标记在 Prompt 里明确“只能引用标记为 [1] 到 [K] 的资料片段”;生成后程序化校验编号合法性

这套排查表的价值在于:你不用一遇到生成质量差就盲目去调 Prompt 或者换模型,而是先判断问题出在“生成”这个环节还是前面的“检索”环节。判断方法很简单:把中间检索到的片段打印出来,自己读一遍,如果片段都不对,那后面生成再怎么高端也救不回来。

5. RAG 效果怎么衡量:知识库指标拆解与实测方法

把链路跑通后,下一个自然而然的问题就是:我的 RAG 到底做得好不好?这个问题看着简单,实际挺棘手。很多人——包括当时的我——会陷入一种“好像还行”的模糊感觉里。但做了几个测试问题之后,才发现不同问题的效果参差不齐,却说不清楚到底哪里差了。这就必须回到指标上来。

5.1 你以为好的 RAG 到底是什么样子

好的 RAG,不是“回答得漂亮”,而是“回答得准确、有据可依”。我对知识库 RAG 的效果判断,一般从三个维度出发:

  1. 回答的忠实度(Faithfulness):答案里的每一个关键论断,是否能在检索到的资料片段里找到支持依据。不能凭空捏造。
  2. 回答的相关性(Relevance):答案是否准确回应了用户的问题,而不是答非所问或者只沾了个边。
  3. 检索的完整性(Recall):回答这个问题的必要信息,是否都被检索到并放进了上下文。这对应的是检索环节的召回率。

这三个维度是层层递进的。如果你检索出来的片段不完整,哪怕生成得再流畅,也不可能覆盖到正确答案,这是第一条链路的问题。如果你检索到了但生成时没用上,那就是生成环节的忠实度问题。

5.2 指标背后的两层:检索质量与生成质量

知识库 RAG 的指标,本质上是两个评价对象的指标:检索质量生成质量

检索质量层面,核心指标是召回率(Recall@K)和精确率(Precision@K)。它们的含义是:针对一个标注好的测试问题,我们事先知道答案应该来自哪几个文档片段,然后跑一遍检索,看看 Top-K 结果里是否包含这些“正确答案片段”。召回率是“正确答案被捞回来的比例”,精确率是“捞回来的结果里有多少是对的”。RAG 场景里更看重召回率一些,因为如果正确的片段根本没进上下文,后面生成环节再神也回答不了。

生成质量层面,核心指标是忠实度(Faithfulness)和答案相关性(Answer Relevance),现在也有直接用大模型做裁判来打分的做法。

这里要吐槽一个很多人容易忽略的点:指标不是只看一个数就行,要让指标可解释。当时我在 Notebook 里加载了几个测试问答题集,跑完后没有只看平均分,而是逐个查看“未通过”的样本,发现有一类问题特别容易挂:当用户的问题包含精确的数字或型号时,比如“X 型号的待机功耗是多少”,向量检索很容易被语义近义词带偏,召回不到那个精确型号。这个发现直接推动我去实验了混合检索。指标的价值在于定位问题,而不只是打分。

5.3 一套可落地的评测流程

如果你也想快速评估自己的 RAG 系统,我分享一个我在 Notebook 里实现的轻量评测流程,不需要引入复杂的评测框架:

  1. 准备测试集:整理 20-50 个真实高频问题,覆盖简单查询、复杂推理、精确匹配等不同类型。每个问题,人工标出在知识库中对应的标准答案片段或参考文档范围。
  2. 评估检索质量:写一个循环,对每个问题做向量检索,统计 Top-5 结果里包含标准片段的个数,算出 Recall@5。
  3. 评估生成质量:写一个循环,用组装好的 Prompt 让大模型生成答案,然后人工或者再用一条“裁判 Prompt”对答案打分,主要看是否忠实于资料、是否相关。
  4. 分类汇总:把每个问题的各项打分结果放在一个 DataFrame 里,按问题类型做分组汇总。

这个过程在 Notebook 里跑起来非常顺滑,而且可以反复迭代。每次改完切分参数、Embedding 或者 Prompt 模板,就重跑一遍,肉眼可见评分曲线的变化。

6. 把 Notebook 从“能跑”升级到“能用”:进阶方向与个人体会

跑通一套基础 RAG Notebook 是一个里程碑,但离“能用”还有一段距离。随着对链路理解的加深,我开始关注 RAG 生态里的各种进阶方向,比如 Graph RAG、Agentic RAG,以及生产环境里的那些“脏活累活”。这节我聊点实在的,讲讲我对这些概念的理解,以及实际跑代码时积累的避坑经验。

6.1 Graph RAG 与 Agentic RAG 到底是什么

Graph RAG(基于知识图谱的 RAG)。传统 RAG 把文档切块后,块与块之间是相互独立的(向量空间里它们只是位置不同)。但真实世界的知识是有关联的,比如一份公司制度文档里,“请假流程”和“考勤管理”和“加班补偿”其实是互相引用的概念。Graph RAG 的思路是:先把文档里的实体(比如“请假”“考勤”)和关系(比如“属于”“关联”)抽取出来,构建一个知识图谱,检索时不仅做向量相似度匹配,还能沿着图谱结构展开“一跳、两跳”的关联查询。

我自己的理解:Graph RAG 不是要替代向量检索,而是补充关联关系层面的召回能力。当问题涉及“A 影响了哪些环节”这种多跳关联时,传统 RAG 可能只能召回提到 A 的局部片段,而 Graph RAG 能顺着关系把 A 触达的其他节点一起拉回来。代价是构建图谱的成本很高,需要调用 LLM 做实体抽取、关系抽取,而且图谱质量直接影响检索效果。

Agentic RAG(智能体式 RAG)。如果说传统 RAG 是一条固定的流水线(查-补-写),Agentic RAG 就是把这套流程交给了 AI Agent 来自主决策。Agent 可以根据用户问题,决定是否需要查询知识库、查询哪部分知识库、用一次查询还是多次查询、如果第一次查询效果不好是否需要改写问题再查一次。它的本质是把 RAG 从“一套固定流程”升级为“一套动态策略”。理解 Agentic RAG 的前提,是先把基础 RAG 的每一步吃透——这个前提,恰恰是跑通基础 Notebook 带给我们的最大价值。

我在实际体验 Agentic RAG 时最大的感受是:能力确实更强,但调试复杂度也上了一个量级。固定的流程出问题,出错点就那么几个;Agent 自主决策时,你很难判断它在哪个决策节点上偏了。所以我的建议是:先别急着上 Agent,先把基础 RAG 的效果做到稳定,再逐步引入动态决策能力。

6.2 从简单 RAG 到生产级 RAG 要做哪些事

Notebook 里跑通的链路是“可用”而非“生产可用”。从 Notebook 里的实验代码,到能线上服务真实用户的知识库系统,中间还隔着很多工程问题。我根据自己的项目经验,列几个最常见的差距:

  • 索引更新策略:Notebook 里是一股脑把文档全部灌进向量库,但生产环境里文档是持续更新的。你需要考虑增量索引:新增文档怎么进来、过期文档怎么删除、更新文档怎么替换。如果文档更新频繁,向量库里的旧向量清理不及时,检索质量会持续劣化。
  • 多用户与权限隔离:不同角色看到的文档范围可能不同。这要求检索阶段就要根据用户身份做范围过滤,而不是所有用户共用同一个向量库。
  • 缓存设计:高频问题的高质量回答,应该加缓存,否则每次都走完整链路,延迟和成本都吃不消。
  • 监控与回滚:线上的 RAG 服务需要记录每次检索和生成的日志,定期抽取样本评估效果,一旦指标下滑能定位到是切分、Embedding 还是 Prompt 变化导致的。很多团队上线 RAG 之后没有这套监控,出了问题基本靠用户反馈,这是拖延战。
  • 多路召回与精排:之前提到过混合检索,生产环境中更复杂,可能还会加一些规则过滤(比如时间过滤、标签过滤),再经过精排模型重新排序。Notebook 里可以用简单的 RRF 融合,线上则可以引入更大的重排模型(比如 bge-reranker)。

6.3 我在跑这套 Notebook 时踩过的五个坑

最后分享几个我在实际跑通和调试过程中踩过的坑,这些属于“代码能跑但效果不对”的类型,不亲自踩一遍很难意识到:

坑一:Embedding 模型不一致。前面提过,建索引和查询用的 Embedding 模型必须一致。我曾经在两个不同的 Notebook 单元格里,一个用的 OpenAI Embedding,另一个用的是本地模型,结果检索基本是“玄学召见”,查出来的结果跟问题一点不搭。那次排查花了很长时间,最终打印出两个向量维度才发现不一致。

坑二:切分时把代码块和表格切碎了。如果知识库里包含 Markdown 格式的文档,里面经常有代码块、表格这类结构化内容。如果按普通文本切分,经常会把一段代码从中间切开,或者把一个表格的行散到不同 Chunk 里。后来我做了自定义切分逻辑:代码块内部尽量整体保留,表格按行切但每一行要带上表头信息。这类自定义规则对检索质量提升极其明显。

坑三:Prompt 里给资料,但没有给“边界”。早期我在 Prompt 里放了{context}后,没有明确告诉模型这个 context 的边界在哪里。模型有时会把资料里的内容,和自己训练时学到的东西混在一起。后来我改成用明确的 XML 标签包裹资料内容,比如<context></context>,同时告诉模型“资料内容在两个标签之间,超出标签的信息不得使用”。这个做法看似简单,实际效果提升很明显。

坑四:Top-K 里混进了太多低分片段。我之前觉得 K 越大越好,结果 K=8 时,检索结果中的第 6、7、8 名都是低分噪声。这些噪声进入 Prompt 后,经常把答案带偏。后来我加了两道防线:一是相似度阈值过滤(低于阈值直接丢弃),二是从 8 个候选里再精排挑出前 4 个。这两道防线加上之后,回答质量稳定了不少。

坑五:对“空检索结果”没有预案。如果用户问的问题完全在知识库范围之外,检索结果可能是空的,或者相似度分数极低。早期我的 Notebook 在这种情况下,仍然会把一堆不相关的片段拼进 Prompt 里,让模型硬答。模型只能硬着头皮从无关内容里“强行提炼答案”,效果当然是灾难性的。后来我加了判断逻辑:如果最高相似度低于阈值,就不调用大模型,而是直接返回“未找到相关资料”。让 RAG 学会说“不知道”,是让它变可靠的关键一步。

总的来说,跑通这套 Notebook 让我受益最大的,不是掌握了某个具体的框架调用,而是建立起对 RAG 整条流水线的“体感”。每一个环节的输入输出、常见故障、调优方向,都在代码的调试过程中变成了肌肉记忆。如果你也想真正搞懂 RAG 在做什么,我的建议很简单:不要只在文档里看概念,拿起一套 Notebook 实测一下,尤其是要动手调一调切分参数、换一换 Embedding、观察一下检索分数的分布,这些“动手”带来的认知,比任何架构图都来得深刻。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 4:07:07

Solana MEV 数据采集复盘

Solana MEV 数据采集复盘 本文记录一次完整的 Web 数据采集服务搭建全过程:接口逆向 → 结构分析 → 采集架构 → 管线实现 → 稳定性加固 → 反爬对策 → PyInstaller 打包 → 落地运维。 目标数据源:Solana MEV 数据平台 (原子套利实时流 + 常规/宽幅三明治快照),展示页 sand…

作者头像 李华
网站建设 2026/9/5 4:06:15

看完STM32教程≠能找到工作:工程能力差距与补强路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 4:03:29

AI编程可控性实战:构建开发者主导的代码生成拦截机制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 4:03:06

校园物联网门锁技术落地:重构宿舍教室安全管控与节能运维体系

智慧校园建设已从基础的教学数字化&#xff0c;逐步延伸至宿舍、教室、实训功能室等线下物理场景的精细化治理。传统校园管理依赖机械门锁、人工巡查、固定时段通断电的粗放模式&#xff0c;长期存在学生身份准入松散、开锁方式单一、外来人员混入、违规用电频发、空载能耗浪费…

作者头像 李华
网站建设 2026/9/5 4:02:51

电动方程式VCU整车控制算法初探:从踏板解析到扭矩控制

不用刻意“从零开始”得像是重新发明轮子。大学生电动方程式大赛&#xff08;FSEC&#xff09;的“算法”听起来像是很高深的自动驾驶或者AI&#xff0c;但实际上&#xff0c;对于一支刚刚组建、或者想系统化提升的车队来说&#xff0c;我们第一年要做的事情就是把整车的控制逻…

作者头像 李华
网站建设 2026/9/5 4:01:04

企业AI Agent定制中的负反馈:用户纠正为何没被回流

一家连锁零售企业的客服智能体上线后&#xff0c;客服团队发现用户在对话里频繁纠正它——"我们店早就取消七天无理由了""这款商品已经下架了"。可这些纠正散落在成千上万条会话里&#xff0c;既没有被结构化地采集下来&#xff0c;也没有经过验证回流到知…

作者头像 李华