之前做文本摘要项目时,我一直有个困惑:ROUGE 分数明明很高,但人工一看,摘要里把关键信息漏了不少,甚至还有一两句和原文意思相悖的话。ROUGE 反映不了这个问题,因为它只看字面重合;人工评估又贵又慢,几十条还能接受,到上千条就完全跑不动了。最近一年我逐步把 LLM-as-a-Judge 引入摘要评估流程,发现它虽然不能完全替代人工,但在规模化评估、维度拆解、回归测试这些场景下,性价比确实高得多。
本文会从“传统评估为什么不够用”讲起,拆解 LLM 作为评判者的工作机制、Prompt 设计、偏差问题,最后给出一套可以直接改用的 Python 评估器代码。适合正在做 NLP 摘要项目、想优化自动评估流程的读者,也适合刚接触 LLM 评估、想系统理解其原理的新手。
1. 摘要评估为什么难:从传统指标说起
1.1 ROUGE 只能看“字面重合”
摘要评估最早也是最常用的自动指标是 ROUGE,它本质上是在计算生成摘要和参考摘要之间的 n-gram 重合度。ROUGE-1 看单个词,ROUGE-2 看相邻两个词,ROUGE-L 看最长公共子序列。BLEU 则是从机器翻译领域借来的,主要看候选文本相对于参考文本的 n-gram 精确率。
这套思路在机器翻译里相对合理,因为翻译往往需要尽量保留原文的词汇结构。但摘要任务不一样:摘要的本质是“压缩 + 改写 + 聚焦重点”。同一个新闻事件,让三个不同的人写摘要,用词可能完全不同,但表达的核心意思可能都正确。这时候 ROUGE 会给出很低的分数,因为字面没有重合,语义却高度一致。
我再举个例子,源文本讲“某公司发布新款手机,电池续航提升明显”,参考摘要写“电池寿命显著改善”,候选摘要写“续航能力大幅增强”。这两句含义相似,但“电池寿命”和“续航能力”在词面上完全不同,ROUGE 会认为候选摘要“不如”某个字面重合更高的错误摘要。
更麻烦的是,ROUGE 对“信息缺失”和“信息失真”完全不敏感。只要字面重合够多,就算候选摘要把时间、人物、数据说错了,分数照样不低。这在生产环境里是很危险的。
1.2 人工评估的代价与不一致
既然自动指标不靠谱,大家自然会想“那就让懂业务的人来打分”。人工评估确实能识别语义错误、信息遗漏,但问题也很突出:
第一是贵。每条摘要要至少两个人看,涉及专业领域还得找懂行的人,人力成本随时间上升得很快。第二是不一致。不同人对“好摘要”的感知不同,有人看重忠实,有人看重简洁;同一个人在不同时间段打分都可能波动。第三是标准难统一。如果评估前没有写好详细的评分 Rubric,最终得到的分数很难互相比较。
所以实际项目中,人工评估适合做“小样本抽检”和“标准制定”,不适合做大规模回归测试。而回归测试恰恰是模型迭代中最需要的——每次改了 Prompt、换了模型,都得快速知道效果是变好还是变坏。
1.3 LLM-as-a-Judge 是什么
LLM-as-a-Judge 直译是“让大语言模型当裁判”。具体做法是:把源文本、候选摘要、参考摘要(可选)以及一套评分标准写入 Prompt,让 LLM 根据标准输出分数、等级或比较结论。因为 LLM 具备较强的语义理解、指令遵循和推理能力,它可以突破 ROUGE 只看词面重合的限制,在一定程度上模拟人工评估。
在摘要场景里,LLM 评判者能同时拿到三种信息:
- 源文档(原文),用于判断信息是否有依据。
- 参考摘要(可选),作为质量参照。
- 候选摘要(待评对象),真正需要打分的文本。
这也是摘要评估比通用文本质量评估更适合 LLM-as-a-Judge 的原因:它有一个明确的“事实来源”,Judge 不需要凭空判断好坏,而是可以对照原文来核对信息是否忠实。
需要强调的是,LLM-as-a-Judge 不是一种“完美客观”的指标,它本身也是一个模型,会有偏好和偏差。理解它的机制、偏差和设计边界,才能真正把它用起来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LLM-as-a-Judge 的工作机制拆解
2.1 三种基本的评判模式
根据评估目标不同,LLM-as-a-Judge 可以分成三种常见模式:
直接打分模式:让模型按 1 到 5 或 0 到 10 的标尺给摘要打分。适合需要连续分数、做趋势对比的场景。比如评估多条摘要的整体质量变化。
成对比较模式:把两个候选摘要 A、B 放在一起,让模型判断哪个更好,或者是否平局。适合做 Prompt 调优、模型版本对比、消融实验。这种模式不需要绝对分数,对模型来说决策负担更小,稳定性往往更高。
分类标注模式:让模型判断摘要是否存在某类问题,比如是否包含幻觉信息、是否遗漏关键实体、是否偏离主题。适合做问题归因和质量诊断。实际项目中我比较喜欢这种模式,因为产出的是可解释标签,而不是一个孤零零的分数。
三种模式可以组合使用。比如先分类标注判断“有无幻觉”,然后对无幻觉的摘要再打分。这样既保留可解释性,又能计算整体分数。
2.2 摘要评估需要看哪些维度
设计 Judge 之前,先要确定评估维度。不同项目侧重点不同,但对“摘要质量”而言,下面几个维度是通用的:
忠实度(Faithfulness):摘要中的信息是否能在源文本中找到依据,有没有幻觉、事实错误。这是摘要评估里最重要的维度。
相关性(Relevance):摘要内容是否围绕源文本的核心主题,有没有跑题、引入不相关内容。
信息覆盖(Coverage):摘要是否覆盖了源文本的关键信息点,是否漏掉了重要实体、数字、结论。
可读性与流畅度(Fluency):句子是否通顺、逻辑是否连贯、语法是否正确。
简洁性(Conciseness):在不损失关键信息的情况下,表达是否精炼,有没有大量冗余。
如果把所有维度都塞进一个 Prompt,模型打分时会“顾此失彼”,一会儿偏忠实验,一会儿偏流畅度。我的做法是:要么一个 Judge 单独负责一个维度,要么在 Prompt 里对每个维度分别说明权重——但权重不要交给模型自己理解,而是让它按明确的顺序逐项打分。
2.3 一次完整评判的内部流程
从流程上看,LLM-as-a-Judge 的运行过程可以拆成四步:
第一步是输入构建。把系统提示词、评分标准、源文本、候选摘要等按模板拼接成 Prompt。输入顺序、长度控制、是否有参考摘要,都会影响结果。
第二步是模型推理。把 Prompt 发给模型,设置较低的温度(一般是 0 或接近 0)。这个环节要关注最大输出长度,因为模型不仅要给分,还要写理由,输出 token 可能较长。
第三步是结构化输出。要求模型返回 JSON 或固定格式的文本,比如 {"faithfulness": 4, "relevance": 5, "reason": "..."}。如果模型输出不合法,需要做容错解析,甚至让它重新生成。
第四步是后处理与聚合。对同一条摘要多次采样取均值,或者把多个 Judge 的结果汇总,形成最终报告。
了解这套流程后,下面重点看决定评估质量的命脉——Prompt 设计。
3. 关键设计:Prompt 怎么写才靠谱
3.1 基础版 Prompt 的结构
一个可用的摘要评估 Prompt 至少要包含四个要素:角色设定、评估标准、待评数据、输出要求。
角色设定的作用是让模型进入“评审专家”状态,比如:“你是一名资深 NLP 评估专家,负责对文本摘要进行质量评估。”
评估标准要尽量具体,不要只写“请根据质量打分”,而要明确每个分数档位的行为描述。待评数据要完整给出源文本和候选摘要,必要时给参考摘要。输出要求必须明确格式,否则模型可能写一堆废话。
下面是一个可直接运行的基础版 Prompt 模板:
text复制你是一名专业的摘要质量评审专家。
请对候选摘要进行评估,判断它相对于源文本的质量。
评分维度:忠实度
- 4 分:摘要中的所有信息都能在源文本中找到明确依据。
- 3 分:摘要整体忠实,但存在轻微表述偏差或不确定。
- 2 分:摘要包含部分与源文本不一致的信息。
- 1 分:摘要大量存在幻觉或与源文本矛盾的信息。
- 0 分:摘要几乎没有与源文本一致的内容。
源文本:
{source_text}
候选摘要:
{candidate_summary}
请只输出 JSON 格式,字段如下:
{"score": 0-4, "reason": "简要评估理由"}
这里的评分标准写得比较具体,每个分数都有行为描述,模型更容易理解“高到什么程度算高分”。
3.2 引入评分 Rubric 与范例
最高级的 Prompt 会直接把“评估标准”和“行为锚点”写清楚,进一步的做法是加入 few-shot 范例。
few-shot 的作用是给模型展示一个“标答”样例。例如先给一个候选摘要、一个评估结果,再给第二个候选摘要、评估结果,最后才让模型评估第三个。这样模型输出的风格和判断尺度会更稳定。
需要注意的是,few-shot 范例本身可能引入偏差。如果范例里总是给低分,模型可能整体偏低;如果总是给高分,模型又会整体偏松。范例数量建议 1 到 3 个,不宜过多,而且要覆盖“好、中、差”三种情况。
3.3 用 JSON 约束输出结构
在代码层面解析模型输出时,最稳妥的方式是让模型输出 JSON,这样后续可以程序化处理。
如果模型不支持 JSON mode,或者输出不稳定,可以在 Prompt 里加一句:
text复制请确保输出是合法 JSON 字符串,不要包含多余解释。
同时在后端代码里做容错处理:如果解析失败,尝试用正则提取 JSON 片段;如果多次失败,就让模型重新生成一次。
3.4 一个容易踩的坑:评分尺度漂移
评分尺度漂移是 LLM-as-a-Judge 里最常见的现象之一。同一份摘要,第一次评估给 4 分,第二次给 3 分;或者所有摘要都被打到 4 分以上,完全失去区分度。
原因通常有三个:温度不为 0 导致采样随机;评分标准写得太宽,没有行为锚点;模型的锚定偏差——如果 Prompt 里先出现一个高分摘要,它往往会下意识往高分靠近。
对策也很直接:温度设 0;评分标准细化;多次采样取均值;必要时加入“先判断再打分”的强制推理过程,让模型先列出核对要点,再给出分数,而不是凭感觉直接打分。
4. 实战:搭建一个摘要质量评估器
4.1 环境准备
本文的代码以 Python 3.8+ 为例,核心逻辑是“构造 Prompt → 请求模型 → 解析 JSON 结果”,所以对模型部署方式没有硬性要求。你可以使用 OpenAI 兼容接口、国内大模型平台的 SDK,也可以使用本地推理服务。
需要安装的依赖只有一个 HTTP 请求库,如果你用的是常见的 OpenAI 兼容接口,可以安装:
bash复制pip install openai
如果你用的是自家模型服务或者本地模型,只需要将 call_llm 换成你实际能访问的请求方式。本文示例代码会保留一个清晰的请求函数,方便你替换。
这里特别提醒:如果无法访问某些海外模型接口,请不要使用任何非正规手段,改用国内可正常访问的模型服务或本地部署模型即可。评估逻辑完全一样,Prompt 也基本通用。
4.2 整体流程设计
评估器要做的事情很简单:
- 接收源文本、候选摘要、参考摘要(可选)。
- 根据评分维度拼接 Prompt。
- 请求大模型生成 JSON 格式评分。
- 解析 JSON,返回结构化结果。
为了便于扩展,我会把“构造 Prompt”“调用模型”“解析输出”“执行评估”四个步骤拆成独立函数,这样后续替换模型或增加评估维度时改动最小。
4.3 核心代码
下面给出完整的代码示例。文件路径建议为 summary_judge.py。
python复制"""
文件路径:summary_judge.py
功能:基于 LLM-as-a-Judge 的摘要质量评估器
说明:示例使用 OpenAI 兼容接口,请根据实际模型服务修改 call_llm 部分。
"""
import json
import os
from openai import OpenAI
# 如果使用 OpenAI 兼容接口,可以这样初始化客户端
# 请根据你实际使用的模型服务商设置 base_url 和 api_key
client = OpenAI(
api_key=os.getenv("LLM_API_KEY", "your-api-key"),
base_url=os.getenv("LLM_BASE_URL", None), # 设置为你的服务地址
)
def call_llm(prompt: str, temperature: float = 0.0) -> str:
"""
调用大模型并返回文本结果。
这里以 OpenAI SDK 为例,实际使用时请更换为你能访问的模型服务。
"""
response = client.chat.completions.create(
model="your-model-name", # 替换为你实际使用的模型名
messages=[
{"role": "system", "content": "你是一个严谨的摘要评估助手。"},
{"role": "user", "content": prompt},
],
temperature=temperature,
max_tokens=1024,
)
return response.choices[0].message.content
def build_prompt(source_text: str, candidate_summary: str, dimension: str = "faithfulness") -> str:
"""
根据评估维度拼接 Prompt。
这里以忠实度为例,其他维度可以按同样结构扩展。
"""
rubric = {
"faithfulness": """
评分标准(忠实度):
- 4 分:摘要中的所有信息都能在源文本中找到明确依据。
- 3 分:摘要整体忠实,但存在轻微表述偏差或不确定。
- 2 分:摘要包含部分与源文本不一致的信息。
- 1 分:摘要大量存在幻觉或与源文本矛盾的信息。
- 0 分:摘要几乎没有与源文本一致的内容。
""",
}
prompt = f"""
你是一名专业的摘要质量评审专家。
请对候选摘要进行 {dimension} 维度的评估。
{rubric.get(dimension, rubric["faithfulness"])}
源文本:
{source_text}
候选摘要:
{candidate_summary}
请只输出 JSON 格式,字段如下:
{{"score": 0-4, "reason": "简要评估理由"}}
"""
return prompt
def parse_response(response: str) -> dict:
"""
解析模型输出,尝试提取 JSON 字段。
如果解析失败,则退回默认值。
"""
try:
# 尝试直接解析
return json.loads(response.strip())
except json.JSONDecodeError:
# 尝试寻找花括号包裹的 JSON 片段
start = response.find("{")
end = response.rfind("}") + 1
if start != -1 and end > start:
try:
return json.loads(response[start:end])
except json.JSONDecodeError:
pass
return {"score": -1, "reason": "解析失败,请检查模型输出"}
def judge_summary(source_text: str, candidate_summary: str, dimension: str = "faithfulness") -> dict:
"""
执行摘要评估,返回结构化结果。
"""
prompt = build_prompt(source_text, candidate_summary, dimension)
raw_output = call_llm(prompt, temperature=0.0)
result = parse_response(raw_output)
result["raw_output"] = raw_output
return result
if __name__ == "__main__":
source = (
"某公司昨日发布新款智能手机,该手机搭载新一代处理器,"
"电池续航比上一代提升了 30%,售价保持不变。"
)
candidate = "某公司发布新手机,续航提升明显,价格维持不变。"
result = judge_summary(source, candidate, dimension="faithfulness")
print(json.dumps(result, ensure_ascii=False, indent=2))
这段代码的核心有三个地方需要说明。
第一,call_llm 是实现解耦的关键。如果切换到其他模型服务,只需要改这个函数内部的请求方式,judge_summary 和 build_prompt 不用动。
第二,build_prompt 中把评分标准模板集中管理,后续要增加“相关性”“信息覆盖”等维度,只需要扩展 rubric 字典即可。
第三,parse_response 做了 JSON 容错,因为模型输出偶尔会带前后解释文字。实际使用中,如果解析失败频率很高,说明 Prompt 里的输出格式约束不够强。
4.4 运行与结果说明
在命令行运行:
bash复制python summary_judge.py
预期会输出类似下面的 JSON(实际模型输出可能有差异):
json复制{
"score": 4,
"reason": "候选摘要中的'续航提升明显'能够找到源文本中'电池续航比上一代提升了 30%'的依据,'价格维持不变'也与原文一致,信息没有与原文矛盾的地方。",
"raw_output": "{\"score\": 4, \"reason\": \"候选摘要中的'续航提升明显'能够找到源文本中'电池续航比上一代提升了 30%'的依据,'价格维持不变'也与原文一致,信息没有与原文矛盾的地方。\"}"
}
这里演示的是一个理想结果。如果你实际运行得到 3 分,也不用奇怪,因为不同模型对“续航提升 30%”和“续航提升明显”之间的换算理解不同。这也是“忠实度”评估里最常见的争议点:候选摘要做了抽象改写,LLM 需要判断“明显”是否足以概括“30%”。
为了降低这种主观差异的影响,建议在评估时同时让模型产出解释,并在需要精细对比时使用“成对比较模式”,而不是单纯依赖绝对分数。
4.5 批量评估与统计报告
单条评估只能看个别样本,实际项目需要批量运行。下面这个函数负责对一组样本循环评估,并统计平均分:
python复制def batch_judge(samples, dimension="faithfulness"):
"""
samples: list of dict,每个 dict 包含 source_text 和 candidate_summary。
"""
results = []
scores = []
for i, sample in enumerate(samples):
print(f"正在评估第 {i + 1}/{len(samples)} 条...")
result = judge_summary(
source_text=sample["source_text"],
candidate_summary=sample["candidate_summary"],
dimension=dimension,
)
results.append(result)
if result["score"] != -1:
scores.append(result["score"])
avg_score = sum(scores) / len(scores) if scores else 0
return {
"average_score": round(avg_score, 2),
"details": results,
}
运行上面的函数,最终可以得到所有样本的详细评估和平均分。这里有一个工程上容易被忽略的点:如果样本数量大,务必处理限流和失败重试。可以在 call_llm 中加入异常捕获,并设置指数退避重试,例如遇到网络错误或限流时,等待 1 秒、2 秒、4 秒后重试。
5. 常见问题与排查思路
5.1 典型问题表格
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 输出不是合法 JSON | 输出要求不够明确,模型自由发挥 | 增强格式约束,加 few-shot,用正则提取 JSON |
| 分数忽高忽低 | 温度过高或评分标准过宽 | 温度设为 0,多次采样取平均,细化 Rubric |
| 高分但内容有问题 | 只用了单一维度,或者维度定义太模糊 | 拆成多维度评估,加入反面示例 |
| 摘要越长分数越高 | LLM 存在冗长偏差,越长的文本越容易被认为更好 | 加入简洁性维度,或在成对比较时控制文本长度 |
| 模型偏向自己生成的摘要 | 自我偏好偏差,部分模型对自身输出有倾向 | 使用成对比较并交换顺序,只保留两次一致的结论 |
| 调用接口报错 | 网络、鉴权、限流等问题 | 检查 API Key、请求格式,加入重试机制 |
5.2 偏差:LLM 评判者不是完全中立的
刚才表格里提到了几个偏差,这些是使用 LLM-as-a-Judge 时最需要警惕的问题,值得展开讲。
位置偏差(Position Bias):在成对比较中,如果模型经常选择出现在前面的摘要,说明它受到了位置影响。解决办法很简单:把 A、B 顺序交换,各评一次,只有两次结论一致时才采用;如果不一致,可以标记为“平局”或者交给人工。
冗长偏差(Length Bias):模型有时候会对更长的摘要给出更高评价,哪怕它的信息密度更低。这和人类评审的行为有些类似,但作为自动化评估工具,我们不该让长度成为主导因素。对策是在 Prompt 中明确“长度不是评估依据,关注信息覆盖和信息密度”,必要时单独统计候选摘要长度,做横向对比。
自我偏好偏差(Self-Preference Bias):如果 Judge 就是生成摘要的那个模型,它可能倾向于给自己的输出打高分。据说 Claude 作为 judge 评分时对自家输出可能有偏好。遇到这种情况,尽量使用不同的模型作为评判者,或使用成对比较并交换顺序来抵消。
锚定偏差(Anchoring Bias):当 Prompt 中先给出参考摘要再给出候选摘要时,模型的评分尺度可能被参考摘要“锚定”,导致给分偏高或偏低。缓解手段是交叉调整顺序、增加锚定值说明,或者不提供参考摘要,仅要求模型对照源文本判断。
处理这些偏差的通用原则是:不要相信单次调用的结果。一个可靠的 LLM-as-a-Judge 系统,应当具备多次采样、顺序交换、维度拆分、结果仲裁这些机制。
6. 最佳实践与工程建议
6.1 评估器设计层面
先明确评估对象。你是评估“摘要的整体质量”,还是评估“摘要是否忠实于源文本”?这两个任务需要不同的 Prompt。一个 Judge 只做一件事,比让它同时承担多个任务更稳定。
然后定义可操作的评分 Rubric。Rubric 要描述行为,不要只给形容词。例如“3 分:信息基本忠实,但有一处轻微偏差”,就比“3 分:较好”要清晰得多。
在条件允许的情况下,优先使用“成对比较”而不是“绝对打分”。模型在对比两个选项时,判断难度更低,结果也更稳定。绝对打分适合需要连续分数的场景,但要多采样几次取平均。
6.2 生产环境落地的几个注意点
第一,日志和回归。每一次评估调用,都应该记录完整的 Prompt、模型输出、解析结果、使用版本。否则模型升级后评分尺度发生变化,你很难定位原因。建议把评估结果存入数据库或 JSON 文件,并附带模型名称和 Prompt 版本号。
第二,成本控制。LLM 评估最怕的是把所有文本原样塞进上下文。在生产环境,应该先对源文本做截断或抽取,只保留摘要可能涉及的段落。如果源文本太长,可以先做召回,把相关段落喂给 Judge,而不是全文盲塞。
第三,安全与合规。如果业务数据涉及隐私、商业机密或用户信息,不能直接把原文发到第三方模型接口。可以先做数据脱敏、权限申请和合规审批;有条件的话,使用私有化部署模型进行内部评估。任何涉及外部模型调用的场景,都要遵守最小数据原则和合法授权原则。
第四,评估器本身的评估。LLM-as-a-Judge 也需要被验证。建议在项目初期准备 50 到 100 条人工标注样本,用这些样本验证 Judge 的评分和人工评分是否一致。如果一致性差,优先调整 Prompt,而不是调整后处理逻辑。
6.3 混合评估架构
我不建议工程项目只用 LLM 作为唯一评估手段。一个比较成熟的评估金字塔是这样的:
底层是传统自动指标(ROUGE、BERTScore 等),用于快速筛选明显异常和粗粒度回归;中层是 LLM-as-a-Judge,用于大规模语义质量评估,覆盖忠实度、相关性、完整性等维度;顶层是人工抽检,用于周期性校准 LLM 评判者的尺度,及时发现偏差。
三者的定位不同:传统指标便宜但粗糙,LLM Judge 灵活但需校准,人工精确但昂贵。组合使用效果最好。
7. 总结与下一步学习方向
通过本文,你应该已经理解了 LLM-as-a-Judge 在摘要评估中的定位:它不是要取代人工,而是在“规模化 + 语义理解 + 维度拆解”这些方面补足传统指标和人工评估的短板。我也带你拆解了它的三个核心机制——直接打分、成对比较、分类标注,以及 Prompt 设计中真正影响结果的那些细节:评分 Rubric、few-shot、JSON 输出、温度设置。
代码方面,summary_judge.py 已经给出了一个最小可用的评估器,你可以复制后,把 call_llm 替换成你实际能访问的模型服务,再加上批量评估函数,就能应用到自己的摘要项目中。
如果想继续深入,可以关注几个方向:一是评估器自身的可靠性验证,也就是怎么设计“评估的评估”,这需要用人工标注集来持续回归;二是多轮对话摘要、长文档摘要场景下,怎么把源文本分段后交给 Judge,避免超长输入导致的信息丢失;三是如何把成对比较结果聚合成可量化的分数,这在实际工程中比想象中更难。
最后提醒一句:LLM-as-a-Judge 是工具,但要记得它也是“一个有偏见的模型”。不要迷信它的单次输出,不要省略人工抽检。把它放在一个可以被校准、被记录、被质疑的评估流程里,它才能真正帮你省人力和时间。如果本文对你有帮助,可以收藏备用,配置时会省很多踩坑时间。
