Skip to content

第 15 章 RAG 系统的评估与优化实战 ​

刚学完第 14 章的读者,多半正处在一种落差很大的状态,头一天信心十足,转眼就挨了现实一记打击。

前一种状态来自测试阶段的成功。工程师搭建了一套看起来很酷的流水线,用上了查询重写,加上了重排序,最后还强制模型进行引用溯源。用几个精心挑选的、最适合的例子一跑,效果很好,答案精准、有理有据。兴奋地截了好几张图,在团队频道里发了个“+1”,然后信心满满地跟老板说:“成了!我们的‘企业大脑’已经初具雏形了!”

后一种状态来自真实用户的检验。把这个系统拿给几个真实用户试用,或者在全部门的大会上进行第一次公开演示,问题很快就会暴露。

想象一下那个场景。会议室里坐满了人,有老板,有其他部门的同事,还有那个一直对 AI 持怀疑态度的产品总监。投影仪上,是那个简洁漂亮的 UI 界面。演示的工程师深吸一口气,开始演示。为了展示系统的“真智能”,特意没用事先准备好的问题,而是现场征集了一个。人力资源部的同事举手,问了一个她最近正头疼的问题:“我们公司对于员工申请远程办公的最新政策是什么?”

工程师微笑着把问题输入系统,心里还有点小得意,觉得这个问题提得很好,正好能展示系统对接内部知识库的能力。

在所有人的注视之下,RAG 系统,那个引以为傲的“企业大脑”,在思考了五秒钟之后,给出了一个让演示者很难堪的答案:“根据《2021 年员工手册》第三版,员工可以申请每周一天的远程办公,需提前一周向直属经理报备。”

现场一片寂静。老板脸上的微笑逐渐凝固。人力资源部的同事皱起了眉头,小声但清晰地对旁边的人说:“不对啊,这个政策上个月刚刚发了全员邮件更新了,现在是每周最多两天,而且需要总监审批。那个 2021 年的手册早就作废了。”

那一刻,演示现场陷入了尴尬。那个看起来逻辑严密、无懈可击的 RAG 系统,在真实世界的第一次检验面前,就不堪一击。它要么就是找不到最新的文档,要么就是找到了好几份相互冲突的文档后,不辨新旧地选了最老的那一份。

这就是 RAG 落地时的真实情况。

这个世界的残酷真相是,搭建一个能跑起来的 RAG 系统,可能只需要一个下午;但要让这个系统变得真正可靠、值得信赖,可能需要几个月甚至更久的持续打磨。

跨过这个从“能用”到“好用”的差距,靠的就是评估与优化。

没有一套科学的评估体系,工程师就不知道每次改动让系统变好了还是变差了。对系统的每一次修改、每一次 Prompt 的调整,都只能凭感觉判断。这种开发方式让迭代没有依据,项目长期停留在说不清好坏的状态。

所以这一章就来聊怎么为 RAG 系统建立一套清晰的评估指标,用数据来指导优化工作,把“玄学调参”变成严谨的“科学实验”。本章会讲如何定义“好”,如何量化“好”,以及在发现“不好”的时候,有哪些对应的优化手段。

这个过程不会轻松,但当所有评估指标都稳定达标时,之前投入的时间和加班就有了确定的回报。

15.1 RAG 评估先定义什么是好 ​

遇到的第一个、也是最根本的问题是,到底该如何评价一个 RAG 系统的好坏。

“好”这个字,太模糊了。老板说“我要一个好的系统”,用户说“你这个不好用”,这些都是情绪,不是指标。工程师最痛恨的就是这种模糊的需求,必须把这些模糊的、感性的评价,翻译成可以量化的、客观的、机器可读的指标(Metrics)。

否则,和产品经理的对话就会永远停留在“我觉得这个效果还行啊”和“我觉得不行,感觉不对”的无尽扯皮之中。这种基于“感觉”的沟通,推不动项目,也定位不了问题。

那么,对于一个 RAG 系统,到底关心什么?从一次成功的问答体验中,可以反推出三个最核心的评估维度。这三项指标共同构成 RAG 评估体系的骨架。

忠实度(Faithfulness):答案是否基于上下文 ​

这是评估的起点,也是 RAG 系统最基本的要求。如果连这一点都做不到,那后续的一切都无从谈起。

忠实度(Faithfulness),衡量的是生成的答案,是否完全基于所提供的上下文信息。换句话说,模型有没有“自由发挥”,有没有在给定的材料之外,自己“添油加醋”,搞一些即兴创作?

费这么大劲做 RAG,最核心的目的就是治模型的“幻觉”。如果模型面对给定的上下文还是不用,自己另编一套答案,那这套 RAG 系统就没有达到目的。

举个例子。用户问:“项目 A 的预算是多少?”

辛辛苦苦检索回来的上下文是:“根据 2023 年 6 月 20 日的项目评审会纪要,考虑到需求范围的扩大,会议决定将项目 A 的预算设定为 150 万人民币。”

一个忠实度高的答案会是:“根据 2023 年 6 月 20 日的项目评审会纪要,项目 A 的预算是 150 万人民币。” 这个答案,每一个字,都能在上下文中找到出处,干净利落。

而一个忠实度低的答案可能会是:“项目 A 的预算大约是 150 万人民币。不过,考虑到近期的通货膨胀以及供应链风险,我个人认为,这个项目最终的实际花费可能会略高于这个数字,大概在 160 万左右。”

有人可能会想:“后面这个答案看起来更智能、更人性化啊!”

打住!这种想法非常危险。在严肃的企业场景里,“智能”的前提是“可信”。后面这半句“考虑到通货膨胀...”,就是典型的、致命的“幻觉”。上下文里根本没提这事,是模型自己“脑补”出来的。虽然听起来好像挺有道理,但在一个需要精确决策的商业环境里,这种未经证实的、看似聪明的推断,是绝对不能容忍的。它可能会误导决策,造成巨大的损失。

所以,忠实度是 RAG 的底线。一个忠实度长期低于 90% 的系统,基本上就可以判定为不及格。对模型的要求是,只说材料里有的,不写推测的。

召回率与精确率:检索质量的两个指标 ​

假设模型对上下文做到了 100% 忠实,这就能保证答案正确吗?

当然不能。

如果检索系统从一开始就拿错了文档,比如问题需要的是最新政策,检索回来的却是过期的旧手册,那就算生成模型再忠实于上下文,也不可能给出正确的答案。

这就引出了第二个维度的核心指标,它评估的是检索质量。而检索质量,又可以细分为两个相辅相成的指标,即召回率(Context Recall)和精确率(Context Precision)。

这两个词,做过搜索或推荐的同学肯定不陌生。但在 RAG 的语境下,得重新理解一下。

召回率,衡量的是检索回来的上下文,是否包含了回答问题所需要的所有信息。通俗点说,回答问题所需的材料,检索有没有全部找回来,有没有漏掉关键信息。

再回到前面那个“远程办公政策”的例子。用户问:“我们公司申请远程办公的最新政策是什么?”

假设知识库里有这么几份相关文档。doc-1 是《2024 年远程办公政策更新 V2.0.pdf》(最新,正确答案);doc-2 是《2021 年员工手册》(已作废);doc-3 是一篇关于“远程办公对团队协作影响”的内部分享文章(相关,但非核心)。

一个高召回率的检索,必须把 doc-1 这份包含了最新政策的文档给找出来。如果它只找到了 doc-2 和 doc-3,而把最重要的 doc-1 给漏掉了,那它的召回率就是 0。

而精确率,衡量的是检索回来的上下文中,有多少是真正和问题相关的。它关心的是,有没有拿回来一堆无关文档。

在上面那个例子里,假设检索系统一次性返回了 5 份文档,分别是 doc-1、doc-2、doc-3,以及一份《公司食堂菜单》和一份《打印机使用指南》。那么,这 5 份文档里,只有前 3 份是相关的,后 2 份是纯粹的噪音。所以,它的精确率就是 3/5 = 60%。

召回率和精确率很多时候是相互制衡的。为了提高召回率而放宽检索条件,结果就是拿回来一大堆不相关的垃圾,精确率就下降了;为了提高精确率而收紧检索条件,结果就是可能把一些重要的信息也给漏掉了,召回率就下降了。

一个好的 RAG 系统,必须在这两者之间找到一个平衡。但如果非要排个优先级,在 RAG 场景下,召回率通常比精确率更重要。因为后续的重排序模块可以过滤掉不相关的文档(解决精确率问题),但如果检索从一开始就没把最关键的文档取回来(召回率问题),后续的所有环节都无力回天。

所以,召回率评估的是 RAG 系统“找得全”的能力,它是答案正确性的基础。没有高的召回率,忠实度再高,也只是“忠实地犯错”。

准确率(Answer Correctness):答案是否回应了问题 ​

这是最终极的追求,也是所有用户唯一关心的、用来评判工作成果的指标。

准确率(Answer Correctness),衡量的是生成的答案,是否准确、完整、并且以一种有用的方式,回答了用户的问题。

有人可能会问:“既然我们已经有了忠实度、召回率和精确率,如果这三个都高,那准确率不就自然高了吗?”

大部分情况下是这样。但现实世界,总比想象的要复杂。有时候,即使召回率很高(找到了所有正确的文档),精确率也很高(没有噪音),忠实度也很高(完全基于文档生成),最终的答案,也可能是错的,或者说,是“不够好”的。

再来看一个例子。用户问:“对比一下 A 方案和 B 方案的优劣。”

检索系统非常给力,把 A 方案和 B 方案的详细文档都找回来了(高召回率),而且没有返回任何无关文档(高精确率)。

生成模型也非常老实,完全基于这两份文档进行总结(高忠实度)。

但它可能给出了这样一个答案:“A 方案的成本是 100 万,周期是 3 个月。B 方案的成本是 80 万,周期是 5 个月。”

这个答案,忠实吗?忠实。信息都来自文档。

但它准确地回答了用户“对比优劣”的问题吗?没有。它只是罗列了事实,没有进行真正的“对比”、“分析”和“总结”,也就是没有提供“洞察(Insight)”。一个更好的、准确率更高的答案,应该是:“在成本方面,B 方案的预算为 80 万,比 A 方案的 100 万低 20%,具有明显的成本优势。然而,在实施周期方面,B 方案需要 5 个月,比 A 方案长两个月,这可能是它的一个劣势,需要评估项目的时间敏感性。”

准确率是一个比前面几个指标更高维度、也更主观的指标。它不仅要求事实正确,还要求答案的组织方式、逻辑结构、详略程度,都真正符合用户的意图(Intent)。

所以,这几个指标共同支撑起一个可靠的 RAG 系统。忠实度是底线,保证 AI 不说谎,不自由发挥;召回率是基础,保证材料齐全,不遗漏关键信息;精确率是效率,保证无关信息干扰不到 AI;准确率是最终目标,保证答案真正回应了用户的问题。

在优化 RAG 系统时,必须同时盯住这几个指标。当系统出问题时,这套指标体系能帮工程师快速定位问题出在哪个环节。

如果准确率低,但其他指标都高,那问题很可能出在最终生成 Prompt 上,是生成 Prompt 没写好,没能激发模型的分析和总结能力。

如果准确率和忠实度都低,但检索质量(召回率和精确率)高,那说明检索系统没问题,但生成模型可能太有“创造力”了,需要在 Prompt 里加强“忠实性”的约束。

如果所有指标都很低,那问题大概率出在检索环节,检索从根上就错了,应该先去优化查询重写 Prompt 或者检索算法本身。

有了这几项核心指标,下一个问题是,怎么才能高效地、自动化地测量它们。手动打分支撑不了频繁的迭代。

15.2 用 RAGAs 做自动化评估 ​

如果要把 RAG 做成一个严肃的工程项目,而不是一次性的演示品,那么手动评估是绝对不可持续的,是通往项目失败和团队崩溃的捷径。

RAG 系统有几百上千个用户,每天产生几万条问答。为了优化一个 Prompt,或者换一个 Embedding 模型,想看看效果怎么样,不可能拉上整个团队花上一个星期,去人工检查几千条问答记录,然后在 Excel 表格里打分,最后凭感觉说一句“好像是变好了一点点”。

这种纯手动的开发方式,效率太低,也太不科学。它会让迭代周期从“天”变成“周”,甚至“月”。辛辛苦苦优化完一个版本,竞争对手可能已经发布了三个新功能。

需要一个自动化评估工具,它能够自动地给 RAG 系统的每一次回答打分。对系统做了任何改动,哪怕只是在 Prompt 里加了一个逗号,都只需要重新跑一遍这个自动化评估流程,几分钟后,一份详细的、量化的结果就出来了。这样就能清楚地看到,这次改动是让“忠实度”提升了几分,还是让“召回率”意外地出现了下滑。

这才叫工程化,也只有这样才能摆脱“凭感觉”做事的窘境。

幸运的是,社区里已经有了一些相当扎实的开源工具与商业平台。下面先用一段把主流选项摆在一起,给每个工具说清它适合用在什么场景,以及它最明显的局限。后续小节再逐项展开。

主流评估工具一览 ​

RAGAs。Exploding Gradients 团队在 2023 年下半年开源的这套框架,覆盖了前面定义的全部核心指标(Faithfulness、Context Recall、Context Precision、Answer Correctness),与 LangChain、LlamaIndex 的集成最深,是当下中文社区里被引用最多的方案。适用场景是从零搭建 RAG 评估体系、需要快速跑通整组指标的中小团队。局限在于所有 LLM 评估任务依赖一个外部评估模型,评估模型本身的判断质量直接决定分数是否可信,且对中文场景的长文本召回支持仍偏弱。

TruLens。TruEra 公司的开源项目,核心抽象是“反馈函数”(Feedback Functions),把每一项指标都写成可编程的小函数,便于二次开发。适用场景是有强定制需求、需要在指标里加入业务规则(例如合规关键词、敏感词校验)的团队。局限是默认指标覆盖面比 RAGAs 略窄,且与 LangChain 之外的框架集成成本偏高。

ARES。Stanford 提出的自动化评估框架(Saad-Falcon 等人,arXiv:2311.09476),特点是会用少量人工标注数据微调出专属于本领域的 LLM 评判器,评估结果与人类判断的相关性通常更高。适用场景是对评估质量要求严苛、有能力投入少量标注成本的企业级项目。局限是首次部署需要准备种子数据集与微调流程,工程门槛比纯 Prompt 的方案高出一截。

DeepEval。开源框架,按 pytest 的写法把评估写成单元测试,可以直接挂在 CI/CD 流水线里。适用场景是 Python 工程团队、习惯测试驱动开发的组织。局限是社区生态比 RAGAs 略小,部分高级指标仍处于实验阶段。

LangSmith Eval。LangChain 官方的闭源评估平台,与 LangChain 的链路追踪、Prompt 版本管理深度绑定。适用场景是已经在用 LangChain 或 LangGraph 构建 RAG 链路、需要在同一控制台里看链路与评估结果的团队。局限是闭源、绑定 LangChain 生态,切换或脱离成本较高,且部分高级评估功能需要付费档位。

下面对 RAGAs 展开细看,因为它覆盖了前面定义的全部指标,又对 LangChain、LlamaIndex 默认友好,是上手最快的入口。

RAGAs(Retrieval Augmented Generation Assessment) 就是目前最流行、也最好用的一个框架。

Exploding Gradients 与 CardiffNLP 联合团队的 Es 等人在 EACL 2024 上正式提出了这套框架(arXiv:2309.15217),其设计哲学是 “无参考”(reference-free),评估时不需要人工标注的标准答案,仅靠问题、检索结果与生成结果本身就能打分(虽然 Context Recall 这一项仍需要金标准)。

别把 RAGAs 想得太复杂。它本质上就是一套用 LLM 来评估 LLM 的方法。这个想法听起来有点循环论证,但实践证明非常有效。它把刚才定义的那几个评估指标,都转化成了一系列 LLM 可以自动执行的、具体的、结构化的 Prompt 任务。

来看看 RAGAs 是如何逐项评估的。

RAGAs 的工作流程:各指标的评估方法 ​

要使用 RAGAs,首先需要准备一个评估数据集。这个数据集的质量,直接决定了评估结果的可靠性,稍后会详细讲怎么构建它。现在先假设已经有了一批高质量的问题。每一道问题,通常包含四样东西。question 是用户的提问;contexts 是 RAG 系统在回答这个问题时,检索回来的那几段上下文;answer 是 RAG 系统最终生成的答案;ground_truth 则是人类专家编写的、这个问题的“标准答案”或“参考答案”。

准备好这些数据后,就可以把它们交给 RAGAs 了。RAGAs 会执行内部的一系列评估流程(本质上就是不同的 Prompt 链),挨个对数据进行打分。

先看忠实度(Faithfulness)是怎么算的。它会先让一个 LLM 对 answer 进行“证据提取”,把答案分解成一系列独立的、不可再分的事实陈述。比如,答案是“项目 A 的预算是 150 万,负责人是李四。” 那么,LLM 会把它分解成两个事实:“事实陈述1:项目 A 的预算是 150 万。”,“事实陈述2:项目 A 的负责人是李四。”

下一步,它会拿着这两个事实陈述,逐一地去 contexts(检索回来的上下文)里进行“交叉验证”。它会再问 LLM:“请判断,‘事实陈述1’ 能否在给定的上下文中得到完全的、直接的支撑?请回答‘是’或‘否’。”

它的算法是,统计所有从答案中提取出的事实陈述里,有多少比例可以判定为“是”。这个比例,就是忠实度分数。这个过程完全基于逐条判断。

再看召回率(Context Recall)的批改方法。评估召回率一直比较麻烦,因为它需要知道“标准答案”覆盖了哪些信息,而检索的时候是不知道的。RAGAs 用了一个近似方法,它利用了提供的 ground_truth。

它会同时看 question 和 ground_truth(标准答案)。它会先让一个 LLM,把标准答案里的每一句话,都当成是回答这个问题所“必需”的关键信息点。

拿着这些“必需的关键信息点”,它会逐一地去 contexts(检索回来的上下文)里检查,看看上下文到底覆盖了标准答案里的多少信息点。它会问 LLM:“请判断,‘标准答案中的这句话’,其核心意思,是否能在给定的上下文中找到?” 覆盖的比例越高,召回率分数就越高。

轮到精确率(Context Precision)时,评估思路类似,但方向相反。

它会看 question 和 contexts。它会把 contexts 里的每一句话,都拿出来,然后反过来问 LLM:“请判断,这句话,对于回答用户的原始问题,是不是有用的、相关的?”

把每一句都判完,它会计算其中判定为“有用”的比例。这个比例,就是精确率分数。

准确率(Answer Correctness)这一项,RAGAs 也提供了自动化的评估方法。

它会同时看 question、answer(系统生成的答案)和 ground_truth(标准答案),然后要求一个能力较强的 LLM 来打分。业界常用的做法是直接挑项目里能力最强的闭源旗舰,或者用 RAGAs 默认配置的那款评估模型,因为评估模型的判断质量会直接决定整套指标的可信度。它会从语义相似性和事实一致性两个维度,综合比较 answer 和 ground_truth。它会问 LLM:“请将系统生成的答案和标准答案进行比较,并从 0 到 1 给出一个相似度分数。你需要考虑它们在核心事实上是否一致?在信息的完整性上,系统生成的答案,是否覆盖了标准答案的所有要点?”

根据 LLM 的判断,它会给出一个准确率分数。

把 RAGAs 集成到工作流里 ​

听完上面这些,可能会觉得:“听起来还是有点复杂,每个指标的评估逻辑都不一样。”

别担心,RAGAs 作为一个成熟的框架,已经把这些复杂的 Prompt 和逻辑都封装好了。在代码里,使用它可能就只需要几行:

python
from ragas import evaluate
from ragas.metrics import (
 faithfulness,
 context_recall,
 context_precision,
 answer_correctness,
)
from datasets import Dataset

# 假设你已经从你的测试日志里,准备好了这个数据集
# 这是最关键,也是最辛苦的一步
dataset_dict = {
 "question": ["你的问题1", "你的问题2", ...],
 "contexts": [["上下文1-1", "上下文1-2"], ["上下文2-1"], ...],
 "answer": ["你的答案1", "你的答案2", ...],
 "ground_truth": ["标准答案1", "标准答案2", ...]
}
dataset = Dataset.from_dict(dataset_dict)

# 运行评估,就像调用一个函数一样简单
result = evaluate(
 dataset,
 metrics=[
 faithfulness,
 context_recall,
 context_precision,
 answer_correctness,
 ],
 # 你甚至可以指定用哪个 LLM 来当“考官”
 # llm=ChatOpenAI(model="gpt-4-turbo") 
)

# 查看你的成绩单,它是一个漂亮的字典
print(result)

运行完这段代码,会得到一份清晰的量化结果,比如:{'faithfulness': 0.95, 'context_recall': 0.80, 'context_precision': 0.92, 'answer_correctness': 0.88}。

有了这份结果,优化工作就有了可对比的数据。可以把它集成到 CI/CD 流程里。每次有人提交了一次对 Prompt 的修改,CI/CD 就会自动跑一遍基于黄金数据集的 RAGAs 评估。如果这次修改导致任何一个核心指标,相比于主分支的版本,下降超过了某个阈值(比如 2%),CI/CD 就会给出告警,自动阻止这次代码合入,并通知提交人:“嘿,哥们,你这次提交,把忠实度搞砸了,快去看看怎么回事。”

这就是现代化的、数据驱动的 Prompt 工程。它能让人对 RAG 系统建立起前所未有的信心和掌控感。工程师终于可以拿着一份量化的、可追溯的报告,去跟老板或产品经理说:“老板,你看,经过我一周的优化,我们系统的忠实度有了明显提升,这意味着它胡说八道的概率大幅下降了;召回率也上了一个台阶,这意味着它‘找不到答案’的情况也明显减少了。”

这比一句“我觉得效果变好了”有说服力得多。

如何构建“黄金评估集”? ​

现在心里可能还剩一个最大的疑问:“这个神奇的 RAGAs 框架,听起来很棒。但它需要一个‘评估数据集’,特别是那个 ground_truth(标准答案),这玩意儿从哪来?难道还是要我们自己一个一个地去手写吗?”

这确实是整个评估流程中最辛苦,也是最无可替代的一环。评估数据集的质量,直接决定了所有评估指标的意义。如果评估数据本身有问题,那评估流程再规范,得出来的分数也没有意义。

构建这个“黄金评估集”,没有捷径,但有一些科学的方法。

最真实的来源,是从真实用户日志中挖掘。可以从生产日志里捞出那些用户真实问过的问题,特别是用户“踩”过(给了差评)的那些问题,或者那些高频问到的问题,它们是最好的评估题来源。

对于一些专业的知识库,还可以请领域专家来出题。邀请公司里的领域专家(比如资深工程师、法务、HR),让他们根据自己的经验,提出一些最常见、最关键、也最容易出错的问题。

也可以反过来,利用 LLM 来辅助生成。把文档喂给一个能力较强的 LLM,让它扮演一个“好奇的学生”或“挑剔的用户”,根据文档内容,自动地生成一批问题和对应的标准答案。这可以极大地提高构建数据集的效率,但生成后,仍然需要人工进行审核和修正。

构建和维护这个黄金评估集,是一项持续的、需要投入资源的工作,但这是 RAG 项目上最值得的一笔投资。这项工作前期投入大,可它是后续所有评估和优化的基础,基础不牢,后面的优化就没有依据。

15.3 迷失中间现象与重排序 ​

现在手上有了一套评估工具。对 RAG 系统进行第一轮全面评估,跑完 RAGAs,可能拿到一份让人困惑的结果。

报告显示,召回率(Context Recall)很高,比如有九成左右,这说明检索系统工作很出色,每次都把正确的文档找回来了。精确率(Context Precision)也不错,八成以上的检索结果都是有用的。

但是,忠实度(Faithfulness)和准确率(Answer Correctness)却出奇地低,可能只有 60%。

这是怎么回事?材料找齐了,答案却错了,看起来不合逻辑。第一反应可能是:“是不是 RAGAs 评估错了?还是这个 LLM 太笨了?”

先别急着下结论。这很可能就是 RAG 系统中一个非常经典、也非常反直觉的问题,也就是“迷失中间(Lost in the Middle)”现象。

这个现象指的是,当给大语言模型的上下文窗口里塞入大量文档时(比如 5 个、10 个甚至更多),模型并不会对所有文档一视同仁,给予同等的“注意力”。它对放在最开头和最结尾的内容利用得更好,而容易忽略那些夹在中间的大段内容,即使那些中间的段落,包含了回答问题的最关键信息。

这就解释了那份令人困惑的评估结果。检索系统确实把包含答案的那个“关键文档”给找回来了(所以召回率高),而且找回来的大部分文档都还算相关(所以精确率高)。但是,在默认的、基于向量相似度分数的排序下,这个“关键文档”可能不幸地排在了检索回来的 5 个结果中的第 3 位或者第 4 位。

把这 5 个文档按照 1、2、3、4、5 的顺序全部交给生成模型时,模型容易略过中间位置的文档。它可能仔细看了第 1、2 份文档,然后又跳着看了第 5 份文档,而把中间那份最重要的第 3 份文档草草略过。最后,它根据它“认真读过”的那些不那么相关的文档,生成了一个答案。所以,这个答案相对于提供的全部上下文来说,是“不忠实”的(因为它没有使用最重要的那部分上下文),更是“不准确”的。

解决方案:重排序(Re-ranking) ​

面对位置带来的利用率差异,总不能去改造模型本身。

办法很直接,就是调整候选文档的顺序。

在把上下文交给生成模型之前,先对候选文档重新打分、排序,把最相关的放到开头或结尾。这个过程,就叫重排序(Re-ranking)。

重排序,是在完成初步的向量检索之后,但在把上下文交给最终生成模型之前,增加的一个重新打分和排序的步骤。

两者的分工是这样的。向量检索负责快速找出候选。它根据查询算出向量,按相似度分数从库里快速取出 10 篇可能相关的文档,并按默认顺序排好。这个过程速度很快,召回率高,但这个顺序不一定是最优的。

重排序负责精确排出先后。它对这 10 篇候选逐一做更细致的相关性判断,重新打分。最相关的排到前面,只有参考价值的排到中间,基本无关的排到最后或直接去掉。

经过这道重新打分和排序,最终交给模型的就是最有价值的内容,顺序也最有利。

在技术上,实现重排序有一种常见且高效的方法,就是使用专门的重排序模型(Re-ranker Models)。像 Cohere 这样的公司,就提供了非常强大的、开箱即用的重排序 API。还有一些开源的、基于交叉编码器(Cross-encoder)架构的模型(比如 BAAI 发布的 bge-reranker-large,对应论文 arXiv:2309.07597,以及基于 bge-m3 的较新成员 bge-reranker-v2-m3),也可以用来做这个事。

这些重排序模型的工作原理,和用于检索的向量模型(它用的是双编码器 Bi-encoder)有本质的不同,这也是它为什么更精准的原因。

用于检索的双编码器,把“问题”和“文档”分开进行编码,得到两个独立的向量,然后再去计算这两个向量的距离。这个过程把问题和文档分开处理,速度飞快,适合从百万、千万级别的文档里做第一轮筛选。

用于重排序的交叉编码器则不同,它把“问题”和每一个候选文档拼接在一起,作为一个整体,输入到一个更复杂的、通常是 Transformer 架构的模型里,让模型去进行深度的、交互式的语义理解,然后直接输出一个单一的相关性分数。这个过程让模型直接对“问题 + 文档”做联合理解,计算量更大,速度更慢,但精度也高得多。

所以,一个典型的“检索+重排序”流程是这样的。先用速度飞快的向量检索,从海量的文档库中,“粗筛”出 Top-K(比如 K=20 或 50)个候选文档;再用精度极高的重排序模型,对这批候选文档,进行“精筛”,重新打分并排序;最终只取重排序后得分最高的 Top-N(比如 N=3 或 5)个文档,作为最终的上下文,交给生成模型。

而且,为了进一步对抗“迷失中间”问题,还可以做一个工程处理,把重排序后得分最高的那份文档,在最终构建给 LLM 的 Prompt 里,放一份在最开头,再原封不动地复制一份放在最结尾,用开头结尾两个位置同时放置关键文档的方式,提高模型利用它的概率。

实施了重排序策略后,再回去跑一遍 RAGAs 评估,会发现在召回率几乎不变的情况下,忠实度和准确率可能会有 20% 甚至 30% 的提升。

RAG 优化很多时候拼的不是用了多牛的算法,而是对细节的处理有多扎实。模型对长文本中间的内容利用率低,关键信息放错了位置就容易忽略;把这个规律摸清,用工程手段补上,往往比换更强的模型管用得多。

15.4 成本优化 ​

经过一番艰苦的努力,RAG 系统现在终于变得既准确又可靠了。评估指标全部达标,用户好评如潮,老板在全公司的会议上点名表扬了项目负责人。

这个时候,人很容易觉得最难的部分已经过去了。

月底,财务部门的同事,拿着一张金额很高的云服务账单,找到了老板。老板把负责人叫进了办公室,脸上的表情,不再是欣赏,而是凝重。

账单显示,那个备受好评的 RAG 系统,每个月的 API 调用费用,高达几十万,正在持续消耗公司利润。老板用一种平静但有力的声音问:“这个成本,我们能优化一下吗?”

这是每一个成功的 LLM 应用工程师,都必须面对的现实问题,即成本。

那么,该如何降低 RAG 系统的调用成本呢?

降本增效的两个方向:缓存与模型分级 ​

RAG 系统的成本,主要来自两个方面,即Embedding 模型的调用(用于文档索引和查询向量化)和LLM 生成模型的调用(用于查询重写、重排序、知识融合、最终生成等)。其中,后者的开销,通常是前者的几倍甚至几十倍,因为它们处理的 token 数量要大得多。

所以优化也要从这两方面入手。

一个方向是缓存。这是所有性能和成本优化中,最古老,也最有效的手段。在 RAG 系统里,可以应用多种层次的、由浅入深的缓存策略。

最简单的、也是最应该先做的,是查询缓存(Query Cache)。可以用一个 Redis 或者任何内存数据库,把用户的查询字符串作为 key,把它对应的最终答案作为 value,都缓存起来,设置一个合理的过期时间(比如 24 小时)。当下一个用户问了一模一样的问题时,系统应该先查缓存,如果命中,直接从缓存里返回答案,根本不用去走后面那一整套昂贵的 RAG 流程。

更高级、更智能的,是语义缓存(Semantic Cache)。因为用户很少会问一模一样的问题,但可能会问意思非常相近的问题,比如“如何申请年假?”和“请问申请年假的流程是怎样的?”。这时,可以对用户的查询也进行向量化。当一个新问题进来时,先把它转换成向量,然后去“已查询问题向量库”里,做一次快速的相似度搜索。如果找到了在向量空间里跟它极其相似的、已经回答过的问题(比如余弦相似度高于一个很高的阈值,像 0.98),也可以大胆地认为,它们是同一个问题,然后直接返回那个问题的缓存答案。

在检索环节,还有文档片段缓存(Chunk Cache)。对于那些检索频次很高、几乎不变的“热点”文档片段(比如公司的报销政策、产品的核心参数),可以直接缓存在内存里,减少对向量数据库的访问压力。

到了最终生成环节,则可以上生成结果缓存(Generation Cache)。如果输入给生成模型的 Prompt(包含了上下文和问题)是完全一样的,那么生成的答案也应该是确定的(在 temperature=0 的情况下)。也可以对这个最终的 Prompt 进行哈希,然后缓存它的生成结果。

一个设计良好的缓存系统,对于那些问答场景有明显“头部效应”(即提问高度集中在少数问题上)的应用来说,可能省掉相当可观的一部分 API 调用成本。这可都是真金白银。

另一个方向是模型分级。真的需要用那个最强大、最昂贵的旗舰模型,去执行 RAG 链条上的每一个任务吗?

当然不需要。不管是海外的旗舰,还是 DeepSeek、通义千问 Qwen、豆包这类国产旗舰,都犯不着包办链条上的每一个环节。用旗舰模型做简单任务,就是性能过剩,成本浪费。

在 RAG 系统里,不同的任务,对模型能力的要求,是完全不一样的,可以构建一个分级模型(Cascading Models)的使用策略。

查询重写、意图分类这些任务,相对简单,它们需要的是快速的语言理解和重组能力,而不是深刻的推理或创造力。完全可以交给一些更小、更快、更便宜的模型,比如各家的 Mini 或 Haiku 档位(GPT-4o mini、Claude Haiku),甚至是本地部署的开源小模型(像 Mistral、DeepSeek、Qwen 系列或 Llama 系列的小参数量版本)。它们的成本,可能只有旗舰模型的一个零头。

重排序、知识去噪这些任务,需要一定的细致理解和判断能力,但也不需要顶级的创造力。一些中等规模的模型,在这里性价比最高。

而最终答案生成,才是整个系统里最需要强模型的地方。因为这个环节,直接面向用户,决定了最终答案的质量、流畅度、逻辑性和可信度。在这个环节,可以毫不吝啬地使用能力范围内最强大的闭源旗舰。这个环节值得用最好的资源。

通过这种精细化的、基于任务复杂度的模型选择策略,可以在保证最终用户体验几乎不降低的前提下,把整个 RAG 链条的平均调用成本,显著降下来。

15.5 重建一个企业知识库 ​

最后,把这一章学到的所有理论、工具和技巧,放到一个从头到尾的完整案例里检验。这个案例浓缩了 RAG 落地的典型问题,也映射出很多工程师正在或即将经历的一切。

故事的开端,是一个“不可能完成的任务”。想象一下,一位工程师刚刚加入一家快速发展的、有点“野蛮生长”的科技公司,接到的第一个任务,就是解决一个已经上升到公司战略层面、困扰所有部门已久的“知识孤岛”问题。公司的知识分散在几万个飞书知识库页面、几十万份腾讯文档以及几百万条钉钉群聊历史消息里,缺乏统一组织。这些知识,很多已经过时,相互冲突,而且没有任何统一的索引。一个新员工入职,光是找到如何配置本地开发环境的文档,就得花上三天,问遍了身边的同事,最后拿到的是一个两年前的版本。

任务是明确的,在三个月内,构建一个统一的、智能的企业知识库问答系统,代号“文曲”。员工可以用自然语言,提出任何关于公司技术、产品、流程的问题,系统需要给出准确的、有来源的答案。

头一个月,是天真的构建与惨痛的失败。工程师和团队信心很足。团队快速地学习了 RAG 的基本原理,用 LangChain 搭建了一个最基础的流水线,用一个开源脚本,粗暴地把所有文档下载下来,按固定 1000 字符的大小切块,用当时最流行的 OpenAI 的 text-embedding-ada-002 模型(在 2023 年底是最主流的嵌入选项,自 2024 年起逐步被 text-embedding-3-small 与 text-embedding-3-large 替代)做嵌入,一股脑地存进一个向量数据库。在后端,用一个简单的 Prompt 模板,把检索到的 Top 5 上下文和问题拼接起来,交给 GPT-3.5-Turbo 来生成答案。

两周后,团队加班加点,做出了第一个 Demo。

在内部小范围演示时,问题集中暴露,而且场面很难堪。市场部的同事问:“我们 Q2 的主打产品‘星尘’的 Slogan 是什么?” 系统回答了 Q1 的产品“晨曦”的 Slogan。技术部的同事问:“如何解决我们自研框架中‘循环依赖’的编译错误?” 系统一本正经地胡说八道了一段看起来很像那么回事,但实际上完全错误的代码,如果新人照着做,可能会搞崩整个项目。HR 问:“公司的陪产假有多少天?” 系统找到了三份不同时期的文档(一份是旧手册,一份是邮件通知,一份是会议纪要),然后给了一个极其不负责任的、模棱两可的答案:“根据不同的资料,公司的陪产假可能是 7 天、10 天或 15 天。”

演示会,在尴尬的沉默中结束。团队的士气,跌到了谷底。工程师意识到,团队是在没有任何度量手段的情况下盲目开发。

第二个月,团队建立评估体系,开始评估驱动的、痛苦的迭代。团队负责人做的第一件事,就是在白板上用红色的马克笔,写下两个大字:测量。然后对团队说:“我们现在停止所有新功能的开发。我们唯一的任务,就是建立一套评估体系。我们必须先知道自己错在哪,错得有多离谱,才能知道该往哪走。”

团队花了整整一周的时间,做了一件最枯燥,也最伟大的事,手动标注了一个包含 200 个问题的“黄金评估集”。团队发动了各个部门的同事,让他们提供自己最常问、最关心的问题。问题收上来之后,几个成员在那个混乱的知识库里,为每一个问题,都手动找到了它对应的、最准确的“标准答案”和“来源文档链接”。这个过程非常痛苦,但每个人都知道,这是后续所有工作的基础。

评估集建好后,团队把 RAGAs 集成到了开发流程里。CI/CD 系统,现在每次代码提交,都会自动用这 200 个问题去评估 RAG 系统,并生成一份详细的评估报告。

第一份评估结果很难看。faithfulness 只有 0.72,context_recall 是 0.65,context_precision 是 0.78,answer_correctness 更是低到了 0.58。

这份报告虽然难看,但指明了问题出在哪。团队终于知道该改什么了。

接下来是数据驱动的、有条不紊的迭代,一轮接一轮,每一轮都目标明确。团队不再凭感觉修改,而是用 A/B 测试的方式,科学地验证每一次改动。

第一轮优化,目标是提升召回率。团队发现,context_recall 只有 65%,这是所有问题的根源。经过分析,原来的“固定大小切块”策略,问题很大,经常把一个完整的表格或者一段代码,从中间切断。团队花了三天时间,重写了文档解析和切块的逻辑,引入了更智能的策略,比如“按 Markdown 标题和章节切块”、“识别表格并将其作为一个整体”等等。这一轮做完,重新索引所有数据后,context_recall 指标,上升到了 88%。

第二轮优化,对抗“迷失中间”。召回率上来了,但 answer_correctness 提升不明显。团队立刻想到了“迷失中间”这个问题,在流水线里,引入了 Cohere 的重排序模型,作为检索后的二次筛选层,只取 Top 3 的结果。这一改效果立竿见影,faithfulness 提升到了 95%,answer_correctness 提升到了 82%。

第三轮优化,针对最终生成。准确率还是不够顶尖,特别是在一些需要总结和对比的问题上。团队开始密集迭代最终的生成 Prompt,加入了更严格的“强制引用”规则,加入了处理冲突信息的指令(“如果发现冲突,请优先使用日期最新的文档”),甚至为不同领域的问题(技术类 vs. 人事类),设计了不同的 Prompt 模板,通过一个路由来分发。这一轮的结果,是 answer_correctness 终于突破了 90% 的大关。

第四轮优化,削减成本。系统效果达标了,但一看 API 调用账单,开销很高,于是立刻采用模型分级策略,把查询重写和意图分类,都换成了更便宜的小档位模型(具体哪一款不重要,关键是它快、便宜、够用),只在最终生成环节使用昂贵的旗舰模型。同时,团队花了两天时间,上线了语义缓存,经过一周的观察,发现有高达 30% 的查询可以直接命中缓存。这一轮做下来,在核心评估指标几乎不降低的情况下,系统总成本下降了 70%。

第三个月,验收。三个月后,团队再次召集了全公司的演示会。还是那个会议室,还是那些人。

这一次,团队胸有成竹。任何员工提出的任何刁钻问题,系统都能在几秒钟内,给出附带了精准来源链接的、高质量的答案。当公司的 CEO 亲自提问,问了一个关于公司早期融资历史的、只有少数老员工才知道的细节问题,而系统准确地引用了一份 10 年前的董事会纪要 PDF 来回答时,全场响起了经久不息的掌声。

那一刻,看着 CI/CD 系统里,那一片达标的、所有指标都稳定在高位的评估报告,所有人都知道,项目成功了。团队解决的不仅是一个技术难题,还把开发过程从凭感觉变成了有度量、有依据。


本章总结 RAG 系统是评估迭代出来的 ​

这一章没有聊太多光鲜亮丽的新技术、新模型,反而花了大篇幅,在聊一些看起来很“笨拙”、很“辛苦”的东西,即评估、测试、打分、优化、迭代。

因为 RAG 领域有一条最朴素,也最重要的道理,一个好的 RAG 系统,从来不是靠设计或一次性开发得来的,而是靠评估和优化,一点一点“磨”出来的。

第一版系统几乎必然带着各种问题,评估负责把问题量化,优化负责把指标一点点抬上去,每一轮迭代都让系统比上一版好一点。这个过程没有捷径,也绕不开。

这一章聊的评估指标、RAGAs 框架、重排序、成本优化策略,就是这套迭代流程的各个部件。评估指标把“好坏”变成可以比较的数字;RAGAs 负责自动、稳定地跑完评估,替代凭感觉的抽查;重排序和各种优化策略负责把指标一轮轮抬上去。

所以,RAG 系统在初期表现得傻里傻气,并不可怕。失败,是 RAG 开发过程中的常态,是必经之路。真正拉开高手和新手差距的,不是谁能一次性写出完美的系统(那是不可能的),而是谁能建立起一套科学的、高效的、数据驱动的迭代闭环,然后有耐心地把系统的每一个指标,从 60 分,一点一点地,做到 99 分。

这就是 RAG 的工程逻辑。可靠的系统不靠灵感,靠科学的流程和持续的努力。

参考资料 ​

  1. Es, S., James, J., Espinosa Anke, L., & Schockaert, S. (2024). RAGAs: Automated Evaluation of Retrieval Augmented Generation. Proceedings of the 18th Conference of the European Chapter of the Association for Computational Linguistics (EACL 2024): System Demonstrations, pp. 150–158. arXiv:2309.15217. https://arxiv.org/abs/2309.15217 (RAGAs 框架的提出文献,Exploding Gradients 与 CardiffNLP(Cardiff University)联合团队)
  2. Exploding Gradients. RAGAs 开源仓库(faithfulness、context_precision、context_recall、answer_correctness 等指标实现与 evaluate 接口). https://github.com/explodinggradients/ragas
  3. Ragas 官方文档. Available Metrics. https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/
  4. Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., & Liang, P. (2023). Lost in the Middle: How Language Models Use Long Contexts. Transactions of the Association for Computational Linguistics (TACL), 2023. arXiv:2307.03172. https://arxiv.org/abs/2307.03172 (“迷失中间”现象的提出文献)
  5. Xiao, S., Liu, Z., Zhang, P., Muennighoff, N., Lian, D., & Nie, J.-Y. (2023). C-Pack: Packed Resources For General Chinese Embeddings. SIGIR 2024. arXiv:2309.07597. https://arxiv.org/abs/2309.07597 (BGE 系列模型的技术报告,bge-reranker-large 对应文献)
  6. BAAI. bge-reranker-large 模型卡(Hugging Face). https://huggingface.co/BAAI/bge-reranker-large (2023 年 9 月 12 日发布的 cross-encoder 重排序模型,模型卡链接上述技术报告)
  7. Cohere. Rerank Overview(官方文档). https://docs.cohere.com/docs/rerank-overview (Cohere Rerank API 的官方说明)
  8. Saad-Falcon, J., Khattab, O., Potts, C., & Zaharia, M. (2024). ARES: An Automated Evaluation Framework for Retrieval-Augmented Generation Systems. arXiv:2311.09476. https://arxiv.org/abs/2311.09476 (ARES 框架的提出文献,作者来自 Stanford University)
  9. TruEra. TruLens 开源仓库与文档(反馈函数与 RAG Triad). https://github.com/truera/trulens 与 https://www.trulens.org/
  10. Confident AI. DeepEval 开源仓库与文档(pytest 风格的 LLM 评估框架). https://github.com/confident-ai/deepeval 与 https://deepeval.com/
  11. LangChain. LangSmith Evaluation 官方文档(RAG 应用评估与数据集管理). https://docs.langchain.com/langsmith/evaluation-concepts
  12. OpenAI. New embedding models and API updates(text-embedding-3-small / 3-large 的发布说明,2024 年 1 月). https://openai.com/index/new-embedding-models-and-api-updates/