☰
三个AI考官:多模型协作的RAG评估框架实战
2026/10/12 18:56:14 网站建设 项目流程

1. 为什么需要多个AI考官来给RAG打分

做过RAG(检索增强生成)系统的人都有一个共同的困惑:明明检索模块的召回率看着不错,生成模块的单测也过了,但上线之后用户就是觉得“答得不对味”。问题出在哪?答案往往藏在评估环节。传统的评估方式要么靠人工标注,成本高、周期长、覆盖窄;要么靠单一指标(比如BLEU、ROUGE或者简单的语义相似度),但这些指标和人类真实感受之间的差距,做过的人都懂。

我最初搭建RAG评估流水线的时候,用的是单模型打分——找一个能力比较强的通用大模型,给它一段问题、一段检索到的上下文、一段生成的回答,让它打个分。跑了几百条测试用例之后,我发现了一个很要命的问题:这个“考官”有明显的偏好。它对语言流畅但事实错误的回答给分偏高,对语言朴实但事实准确的回答反而给分偏低。换句话说,它把“说得好听”和“说得对”混为一谈了。

这个发现促使我开始思考一个问题:能不能用多个不同定位的AI模型来分别负责不同的评估维度,最后综合出一个更可靠的分数?这就是“三个AI考官”这个思路的由来。具体来说,我设计了三个角色:一个负责忠实度(回答是否忠实于检索到的上下文),一个负责相关性(回答是否切题),一个负责完整性(回答是否覆盖了问题的所有关键点)。每个考官只关注自己的维度,最后加权汇总。

这套方案解决的核心问题是:让RAG系统的评估结果更接近人类的真实判断,同时保持自动化的效率和可复现性。它适合已经搭建了RAG系统、正在寻找靠谱评估方案的开发者,也适合对LLM应用评估感兴趣、想了解多模型协作评估思路的技术人员。不管你是刚接触RAG的新手,还是已经踩过不少坑的老手,这套多考官评估框架都能给你提供一个可落地、可调整的参考方案。

2. 三个AI考官的角色设计与选型逻辑

2.1 为什么是三个而不是一个或五个

先说一个我踩过的坑。最开始我想的是“越多越好”,搞了五个模型分别打分,结果发现两个问题:第一,成本线性增长,每次评估要调用五次API,token消耗直接翻五倍;第二,维度之间出现了严重的重叠,比如“相关性”和“完整性”这两个维度,不同的模型给出的分数高度相关,等于花了钱但没买到额外的信息量。

后来我做了相关性分析,发现三个维度之间的相关系数控制在0.3到0.5之间是比较理想的——既有一定的独立性,又不至于完全无关。最终定下来的三个维度是:

  • 忠实度(Faithfulness):回答中的每一个事实性陈述是否都能在检索到的上下文中找到依据。这个维度主要抓的是“幻觉”问题。
  • 相关性(Relevance):回答是否直接回应了用户的问题,有没有跑题或者答非所问。这个维度抓的是“偏题”问题。
  • 完整性(Completeness):回答是否覆盖了问题所要求的所有关键信息点,有没有遗漏重要内容。这个维度抓的是“漏答”问题。

这三个维度基本上覆盖了RAG系统最常见的三类失败模式。而且它们之间的独立性比较好,一个回答可能忠实度很高但完整性不够,也可能相关性很强但忠实度拉胯,组合起来能比较全面地刻画回答质量。

2.2 考官模型的选型标准

选哪个模型来当考官,这里面有讲究。我的选型标准有这么几条:

第一,模型能力要足够强。考官本身如果能力不行,打分就没有参考价值。我一般会选择当前可用的、在推理和指令遵循方面表现较好的通用大模型。具体选哪个就不点名了,大家根据自己手头的资源来定。

第二,三个考官最好来自不同的模型家族。这一点很关键。如果三个考官都是同一个模型,那它们很可能有相同的偏见。比如同一个模型可能对某种表达风格有偏好,三个考官都给高分,但实际上这个回答并不好。用不同家族的模型,可以在一定程度上对冲这种系统性偏见。

第三,成本要可控。评估是一个高频操作,每次迭代都要跑。如果单次评估成本太高,根本跑不起来。我的做法是:忠实度考官用能力最强的模型(因为这个维度最需要推理能力),相关性和完整性考官可以用稍轻量的模型,在成本和效果之间取一个平衡。

第四,输出格式要稳定。考官需要输出结构化的分数,而不是一段自由文本。所以选模型的时候要测试它在给定prompt下的输出稳定性。我试过某个模型,同样的输入跑三次,一次输出JSON,一次输出Markdown表格,一次输出纯文本,这种就不适合当考官。

2.3 评分量表的设计

评分量表的设计直接决定了评估结果的分辨率。我试过几种方案:

最开始用的是1-5分的李克特量表,结果发现模型倾向于给3分或4分,区分度不够。后来改成1-10分,又发现模型对分数的锚定不一致,同一个回答今天打7分明天打8分。

最终我采用的是三级评分加理由的方案:

分数含义判定标准
1不合格存在明显的事实错误、严重偏题或关键信息缺失
2合格基本满足要求,但有轻微问题或改进空间
3优秀完全满足该维度的要求,无明显问题

每个分数段都有明确的判定标准描述,而且要求考官在给出分数的同时附上一句简短的理由。这个理由非常重要,它是我后续做人工抽检和prompt迭代的主要依据。

三级量表的好处是:区分度足够(三个等级对应三种处理策略),模型的一致性也比较好(实测同一输入三次评分的一致率能达到85%以上)。如果你觉得三级不够细,可以在每个等级内部再细分,比如2.1、2.2、2.3,但我不建议一开始就搞太细,先把框架跑通再说。

3. 评估流水线的核心实现细节

3.1 数据准备与测试集构建

评估流水线的输入是一批测试用例,每个用例包含四个字段:问题(question)、检索到的上下文(context)、模型生成的回答(answer)、以及可选的参考答案(reference)。参考答案不是必须的,因为忠实度和相关性这两个维度不需要参考答案也能评,但完整性维度如果有参考答案会评得更准。

测试集的构建有几个原则。第一,覆盖面要广。我一般会从真实用户日志里采样,覆盖不同类型的问题:事实型、比较型、推理型、开放型。每种类型至少要有20条以上,否则统计意义不够。第二,难度要有梯度。简单问题、中等难度、困难问题各占一定比例,这样才能看出系统在不同难度下的表现差异。第三,要包含已知的bad case。我习惯把之前发现的问题案例也放进测试集,用来验证新的迭代有没有把这些坑填上。

测试集的规模方面,我的经验是:如果只是做快速迭代验证,50到100条就够了;如果要出一份正式的评估报告,至少300条起步。数量太少的话,单个case的波动会严重影响整体分数。

3.2 Prompt工程:让考官理解自己的角色

每个考官的prompt都是精心设计的,核心思路是:明确角色、给出标准、要求结构化输出。以忠实度考官的prompt为例,结构大概是这样的:

FAITHFULNESS_PROMPT = """ 你是一个严格的事实核查员。你的任务是判断【回答】中的每一个事实性陈述是否都能在【上下文】中找到依据。 评分标准: - 3分:回答中的所有事实性陈述都能在上下文中找到直接依据,没有添加任何上下文之外的信息。 - 2分:回答中绝大部分事实性陈述有依据,但存在个别细节上的推断或轻微外推。 - 1分:回答中存在明显的事实错误,或者有多个关键陈述在上下文中找不到依据。 请按以下格式输出: 分数:[1/2/3] 理由:[一句话说明打分依据] 【上下文】 {context} 【回答】 {answer} """

这个prompt有几个设计要点。第一,角色定义要具体。“事实核查员”比“评估助手”更能让模型进入状态。第二,评分标准要可操作。不能只说“好/中/差”,要给出具体的判定条件。第三,输出格式要固定。我要求模型先输出分数再输出理由,这样解析起来方便,而且强迫模型先做判断再解释,避免它先写一堆理由然后分数给偏了。

相关性和完整性的prompt结构类似,只是角色定义和评分标准不同。相关性考官的角色是“用户代言人”,判断回答是否真正解决了用户的问题;完整性考官的角色是“质量检查员”,对照参考答案(如果有)或问题本身,检查关键信息点是否覆盖。

3.3 调用与解析:工程实现中的关键细节

实际调用的时候有几个工程细节需要注意。

并发控制。三个考官可以并行调用,这样能把评估时间压缩到原来的三分之一。但要注意API的速率限制,我一般会设置一个信号量来控制并发数,避免触发限流。

重试机制。模型偶尔会输出格式不对的内容,比如分数写成了“三分”而不是“3”,或者漏掉了理由。这时候需要有一个重试逻辑:如果解析失败,就把原始输出和格式要求再发给模型一次,让它重新输出。实测下来,重试一次基本能解决90%以上的格式问题。

结果缓存。同样的输入不需要重复评估。我用一个简单的哈希表做缓存,key是问题+上下文+回答的哈希值,value是三个考官的评分结果。这样在调试prompt或者调整权重的时候,不需要反复调用API。

解析代码。解析部分要写得健壮一些。我的做法是用正则表达式提取“分数:X”和“理由:...”这两部分,如果正则匹配失败,再尝试用更宽松的规则去提取。下面是一个简化的解析函数:

import re def parse_score(raw_output): # 尝试匹配标准格式 score_match = re.search(r'分数[::]\s*([123])', raw_output) reason_match = re.search(r'理由[::]\s*(.+)', raw_output) if score_match: score = int(score_match.group(1)) reason = reason_match.group(1).strip() if reason_match else "" return score, reason # 宽松匹配:找第一个出现的1/2/3 fallback = re.search(r'\b([123])\b', raw_output) if fallback: return int(fallback.group(1)), raw_output.strip() return None, raw_output.strip()

这个函数先尝试严格匹配,失败后再用宽松规则兜底。如果还是解析不出来,就返回None,由上层逻辑决定是重试还是标记为解析失败。

3.4 加权汇总:从三个分数到一个总分

三个考官各自打分之后,需要汇总成一个总分。最简单的做法是等权平均,但实际使用中我发现不同维度的重要性是不一样的。对于大多数RAG应用场景,忠实度是最重要的——回答可以不完整,可以稍微偏题,但绝对不能胡说八道。所以我的默认权重是:忠实度0.5,相关性0.3,完整性0.2。

当然这个权重是可以调整的。如果你的应用场景对完整性要求特别高(比如法律咨询、医疗问答),可以把完整性的权重调高。如果是对相关性要求特别高(比如客服机器人),可以把相关性的权重调高。我建议在评估报告里同时给出总分和三个维度的分项得分,这样即使总分一样,也能看出不同系统或不同版本之间的差异在哪里。

汇总公式很简单:

def weighted_score(faithfulness, relevance, completeness, w_f=0.5, w_r=0.3, w_c=0.2): return (faithfulness * w_f + relevance * w_r + completeness * w_c)

但要注意,这个加权是在归一化之后做的。因为三个维度都是1-3分,量纲一致,所以可以直接加权。如果某个维度用了不同的量表,需要先归一化到同一量纲再汇总。

4. 实操中遇到的典型问题与排查记录

4.1 考官之间“打架”怎么办

多考官评估最常遇到的问题就是考官之间的评分不一致。比如忠实度考官给了3分,但完整性考官给了1分,而且两个考官的理由看起来都有道理。这种情况怎么处理?

我的处理原则是:先看分歧是否合理,再看是否需要仲裁。有些分歧是合理的,比如一个回答确实忠实于上下文但只覆盖了问题的一部分,那忠实度高、完整性低就是正常的。但如果两个考官对同一个维度的判断出现严重分歧(比如都评忠实度但一个给3一个给1),那就需要介入了。

我设计了一个简单的分歧检测机制:如果三个考官中任意两个的评分差距超过1分(在三级量表里就是差了两级),就触发人工复核。在实际跑了几千条数据之后,触发人工复核的比例大概在5%到8%之间,这个比例是可以接受的。

另外,我还试过引入第四个模型作为“仲裁者”,当分歧出现时让它来裁决。但实测下来效果一般,因为仲裁者本身也可能有偏见,而且增加了成本和复杂度。后来我还是回归到人工抽检的方式,虽然慢一点,但更可靠。

4.2 模型输出格式不稳定的处理

前面提到过,模型有时候不按格式输出。除了重试之外,我还做了几件事来降低格式问题的发生率。

第一,在prompt里给出示例。Few-shot示例对格式稳定性的提升非常明显。我在每个考官的prompt里都放了一个完整的输入输出示例,模型照着抄的概率就高很多。

第二,降低temperature。评估任务不需要创造性,temperature设成0或者0.1就行。我试过temperature=0.7的时候,格式错误率明显上升。

第三,用JSON模式。如果调用的API支持JSON输出模式,一定要开启。这个功能能从根本上解决格式问题,输出直接就是结构化的JSON,解析起来零成本。

第四,后处理兜底。即使做了上面这些,偶尔还是会有格式问题。所以解析函数一定要写得健壮,并且要有日志记录,方便排查是哪些case出了问题。

4.3 评估结果与人工判断偏差较大的排查思路

有时候你会发现,自动评估的分数和人工判断的分数差距比较大。比如自动评估给了高分,但人工觉得这个回答很一般。遇到这种情况,我一般按以下步骤排查:

第一步,检查测试用例本身。有时候问题出在测试用例上,比如上下文里本身就包含了错误信息,那模型忠实于错误上下文反而会被扣分。这种情况需要先修正测试集。

第二步,检查考官的prompt。把出问题的case拿出来,单独跑一遍考官的prompt,看看考官的理由是什么。如果考官的理由明显不合理,那就是prompt需要调整。比如忠实度考官可能把“根据上下文推断”也当成了“没有依据”,这就需要把评分标准写得更细。

第三步,检查权重设置。如果分项得分都合理但总分不对,那就是权重的问题。比如一个回答忠实度3分、相关性1分、完整性1分,按默认权重算出来是2.0分(合格),但人工可能觉得这个回答完全跑题了应该给1分。这时候就需要调整权重,把相关性的权重调高。

第四步,考虑引入校准集。如果偏差是系统性的,可以构建一个小的校准集(比如50条人工标注过的数据),用来调整考官的prompt或者权重,让自动评估的结果向人工判断靠拢。

4.4 常见问题速查表

问题现象可能原因排查方法解决方案
三个考官分数高度一致考官来自同一模型家族检查模型名称更换不同家族的模型
格式解析频繁失败temperature过高或prompt不明确查看原始输出降低temperature,增加few-shot示例
评估结果与人工偏差大prompt标准不清晰或权重不合理逐case分析考官理由细化评分标准,调整权重
评估速度太慢串行调用或并发限制检查调用逻辑改为并行调用,设置合理并发数
同一输入多次评分不一致模型随机性过高重复跑同一case设temperature=0,增加评分稳定性
某些维度区分度不够评分量表太粗或太细统计分数分布调整量表粒度,增加判定标准描述

5. 多考官评估框架的扩展与优化方向

5.1 动态权重调整的思路

固定权重虽然简单,但不够灵活。我后来尝试了一种动态权重的方案:根据问题的类型自动调整权重。比如事实型问题,忠实度的权重调到0.6;开放型问题,完整性的权重调到0.4。实现方式是在评估流水线前面加一个轻量的分类器(可以用规则匹配,也可以用一个小模型),先判断问题类型,再选择对应的权重配置。

这个方案的效果还不错,尤其是在混合类型的测试集上,动态权重比固定权重的评估结果更接近人工判断。但缺点是增加了复杂度,而且分类器本身也可能出错。所以如果你的测试集类型比较单一,用固定权重就够了。

5.2 引入参考答案的完整性评估

前面提到完整性评估可以对照参考答案来做。具体做法是:先把参考答案拆解成若干个关键信息点(可以用模型来拆,也可以人工标注),然后检查回答中是否覆盖了这些信息点。覆盖率的计算方式是:命中的信息点数除以总信息点数。

这个方案比让模型直接打分的准确性更高,因为信息点的命中与否是一个相对客观的判断。但前提是你得有参考答案,而且参考答案的质量要过关。如果参考答案本身就不完整,那评估结果也会偏。

我一般会在两种情况下用这个方案:一是做正式评估报告的时候,会人工准备一批参考答案;二是做回归测试的时候,会用之前积累的高质量回答作为参考答案。

5.3 评估结果的可视化与追踪

评估不是跑一次就完了,而是一个持续的过程。我习惯把每次评估的结果都存下来,然后用简单的图表来追踪趋势。比如用折线图看总分随时间的变化,用柱状图看三个维度的分项得分,用散点图看不同问题类型下的表现差异。

这些图表不需要多复杂,用matplotlib或者plotly画一下就行。关键是要能一眼看出:这次迭代之后,哪个维度提升了,哪个维度退步了,整体趋势是向好还是向坏。有了这些信息,后续的优化方向就很清晰了。

我还建了一个简单的“问题案例库”,把每次评估中得分低的case都存进去,标注上问题类型和原因。这个库积累到一定规模之后,就成了优化RAG系统的宝贵素材——你可以清楚地看到系统在哪些类型的问题上容易翻车,然后有针对性地去改进检索策略或生成prompt。

5.4 成本控制的实际经验

多考官评估的成本主要来自API调用。三个考官,每个考官一次调用,那就是三次。如果测试集有300条,那就是900次调用。如果每条还要重试,那可能上千次。这个成本对于个人开发者或者小团队来说,是需要认真考虑的。

我的成本控制经验有这么几条:

第一,缓存复用。前面说过,同样的输入不重复评估。这个能省掉大量重复调用,尤其是在调试阶段。

第二,分级评估。不是所有测试用例都需要三个考官全上。我一般会先用一个轻量模型做初筛,把明显有问题的case筛出来,只对这些case做完整的三考官评估。这样能省掉大概60%到70%的调用量。

第三,批量调用。如果API支持批量请求,尽量用批量模式,通常比单条调用便宜。

第四,选择合适的模型。忠实度考官需要强模型,但相关性和完整性考官可以用轻量模型。我实测下来,轻量模型在这两个维度上的表现和强模型的差距并不大,但成本可能只有十分之一。

第五,控制测试集规模。日常迭代用50到100条就够了,不需要每次都跑全量测试集。全量测试集只在发版或者做正式报告的时候跑。

这套多考官评估框架我从最初的想法到落地跑通,大概花了两周左右的业余时间。中间踩了不少坑,但也积累了很多一手经验。最深的体会是:评估不是越复杂越好,而是越稳定、越可解释越好。三个考官的分工明确、评分标准清晰、结果可追溯,这比搞一堆花哨的指标但没人看得懂要强得多。如果你也在做RAG系统的评估,不妨从这三个维度开始试试,根据自己业务的特点调整评分标准和权重,跑上一批真实数据,你会发现很多之前被忽略的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询