大模型应用评测基线建设的工程实践:从任务分层、样本抽样与标注规范到自动化回归评测和版本对比看板
大模型应用评测基线建设的工程实践
很多团队在做 LLM 应用时,线上效果常常靠“感觉还行”推进。版本一多,Prompt 改了、检索改了、模型换了,结果也跟着漂移。等到用户反馈变差,再回头排查,通常已经很难定位是哪一层出了问题。
我踩过这个坑。一次只是把分类助手的系统提示改了两段,离线抽查感觉回答更稳,结果第二天线上“误拒答”明显变多,工单量直接涨了 17%。问题不在模型本身,而在我们没有一套稳定的评测基线。
这篇文章讲一个可复现方案:怎么给大模型应用搭建评测基线,从任务分层、样本抽样、标注规范,到自动化回归评测和版本对比看板。重点放在工程细节,尽量让你照着就能搭起来。
1. 为什么先做评测基线,而不是继续调 Prompt
很多项目一开始就陷入“调参式开发”:
- 回答不准,就改 Prompt
- 幻觉变多,就加约束
- 工具调用乱了,就补规则
短期能见效。久了就乱。
没有基线时,任何一次优化都像在黑箱里试错。你知道它“好像更好”,但说不清好在哪,也说不清退化了什么。尤其是多模块系统,问题可能出在检索、改写、路由、生成、解析任一环节。
评测基线的作用很直接:把“感觉”变成可比较的结果,把版本变化映射成指标变化。
我通常把基线目标定得很朴素:
- 新版本上线前,能跑一套固定评测集
- 能知道哪类任务涨了,哪类任务掉了
- 线上出问题后,能把坏样本回灌到评测集里
够用了。
2. 先把任务拆开:别拿一个总分糊弄自己
评测最容易犯的错,是只看一个总分。比如“整体准确率 82%”,看起来挺完整,实际几乎没法指导迭代。
我的做法是先做任务分层。不是按模型能力分,而是按业务动作分。
以一个企业知识助手为例,可以拆成下面几层:
2.1 L1:入口任务层
这是用户最直接感知的层,按任务类型分桶:
- FAQ 问答
- 文档总结
- 流程指引
- 表单填写辅助
- 工单归因
- 多轮追问
这一层解决的是:不同任务类型的效果是否均衡。
2.2 L2:能力子项层
每个入口任务下面,再拆成可评估能力:
- 指令遵循
- 事实一致性
- 检索引用正确性
- 结构化输出合规性
- 拒答策略是否合理
- 语气和格式约束是否满足
拆到这里,问题就开始清楚了。
比如 FAQ 问答总分下降,不一定是“回答变差”,也可能只是引用字段漏了,或者拒答阈值变严。
2.3 L3:失败模式层
这一层是工程里最有用的。把常见坏例子单独建桶:
- 漏召回后强行回答
- 命中错误知识片段
- 工具参数拼错
- JSON 可解析但字段语义错
- 多轮对话里上下文污染
- 安全策略误伤正常请求
这一层和线上问题最接近。短句先说结论:没有失败模式分桶,评测集很快会失真。
建议用 YAML 或 JSON 配置任务树,后续评测平台、报表、抽样脚本都读同一份配置。
示例:
task_tree:
faq_qa:
weight: 0.35
capabilities:
instruction_following: 0.2
factual_grounding: 0.4
citation_correctness: 0.25
refusal_appropriateness: 0.15
failure_modes:
- missed_retrieval_but_answered
- wrong_chunk_cited
- over_refusal
form_fill:
weight: 0.2
capabilities:
schema_validity: 0.5
field_semantics: 0.3
missing_field_handling: 0.2
failure_modes:
- invalid_json
- enum_value_error
- missing_required_field
3. 样本怎么抽:别只抽“看起来正常”的数据
很多离线评测集的问题,不是量不够,而是分布太干净。全是高频、标准、容易答的问题,跑出来分数很好看,上线马上露馅。
样本抽样我一般分成四个来源。
3.1 线上真实请求抽样
这是主样本池。优先抽:
- 高频任务
- 高业务价值请求
- 用户投诉相关请求
- 最近一周新增意图
最好保留原始上下文,包括会话历史、检索候选、工具输入输出、模型版本号。后面复盘时很省事。
3.2 风险样本补齐
只靠自然流量不够,因为很多风险场景出现频率低,但代价高。比如:
- 边界条件输入
- 超长输入
- 混合中英文本
- 口语化、省略表达
- 敏感问题绕行表达
这些样本要人工补。可以从历史事故、测试用例、运营反馈里攒。
3.3 失败样本回灌
线上一旦出现严重漏判、错答、误调用工具,先别急着修 Prompt,先把样本进评测集。这个动作我一般要求当天完成。
回灌后,后续每次发版都自动覆盖这批案例,能防止同类问题反复出现。
3.4 对抗样本注入
如果应用带工具调用、知识问答、结构化输出,这部分不能省。
比如:
- 提示注入变体
- 指令冲突样本
- 非法 JSON 干扰文本
- 相似字段混淆
- 多文档相互矛盾内容
这一类样本占比不用高,5% 到 10% 足够,但必须长期保留。
4. 抽样比例怎么定:我常用的一个简单配方
评测集不是越大越好,关键是能稳定反映版本差异。
我常用一个 500 条左右的基线集作为起点,分布大致这样:
- 60% 来自线上真实请求
- 20% 来自历史失败回灌
- 10% 来自人工补齐边界样本
- 10% 来自对抗样本
如果是新项目,先做 200 条也行。别拖。
在每个任务桶内,再做分层抽样:
- 高频意图不要占满
- 长尾任务要留席位
- 高价值任务单独保底
下面是一个简单实现:
from collections import defaultdict
import random
def stratified_sample(records, quota_by_task, quota_by_risk, seed=42):
random.seed(seed)
task_buckets = defaultdict(list)
risk_buckets = defaultdict(list)
for r in records:
task_buckets[r["task_type"]].append(r)
for tag in r.get("risk_tags", []):
risk_buckets[tag].append(r)
sampled_ids = set()
sampled = []
for task, quota in quota_by_task.items():
pool = task_buckets.get(task, [])
chosen = random.sample(pool, min(quota, len(pool)))
for item in chosen:
if item["id"] not in sampled_ids:
sampled.append(item)
sampled_ids.add(item["id"])
for risk, quota in quota_by_risk.items():
pool = [x for x in risk_buckets.get(risk, []) if x["id"] not in sampled_ids]
chosen = random.sample(pool, min(quota, len(pool)))
for item in chosen:
sampled.append(item)
sampled_ids.add(item["id"])
return sampled
实际项目里,我会把抽样结果固定版本号,比如 baseline_set_v2026_04_01,避免今天一套、明天一套,导致分数不可比。
5. 标注规范怎么写:核心不是“详细”,而是“可判定”
标注规范最怕写成产品文档,字很多,评审时还是各判各的。
我建议每个能力项都写成下面四部分:
- 评什么
- 怎么判正负例
- 有争议时优先遵循什么规则
- 典型示例
以“检索引用正确性”为例:
metric: citation_correctness
scope: 回答中引用的知识片段是否支持结论
label_values:
- pass
- fail
pass_rule:
- 引用文档真实存在
- 引用内容可以直接支持回答中的关键结论
- 引用编号与文档片段对应一致
fail_rule:
- 引用了不存在的片段
- 引用内容与结论无关
- 回答结论依赖的关键信息未被引用支持
tie_breaker:
- 若回答部分正确,但关键结论缺少证据,按 fail 处理
examples:
- input: 退款多久到账
answer: 退款通常 3 个工作日到账。[文档2]
evidence: 文档2明确写明原路退款 1-3 个工作日
label: pass
- input: 退款多久到账
answer: 退款通常 24 小时到账。[文档2]
evidence: 文档2写的是 1-3 个工作日
label: fail
短句一句:可判定比全面更重要。
5.1 采用二级标签,不要一上来打 10 分制
我不建议基线早期直接做 1 到 10 分主观打分。主观漂移太大,标注员之间很难对齐。
更稳的方式是:
- 主标签:Pass / Fail
- 辅标签:失败原因枚举
比如:
{
"sample_id": "faq_1024",
"metric": "factual_grounding",
"label": "fail",
"reason": "answer_conflicts_with_context",
"comment": "回答说支持7天无理由,但知识库里只有质量问题退货"
}
后面要算细分指标、做错误分布统计,这种结构很好用。
5.2 做一致性校验
同一批样本里抽 10% 给双人标注,算 Cohen’s Kappa 或简单一致率都可以。只要你真的去算,规范质量会提升很快。
示例:
from sklearn.metrics import cohen_kappa_score
annotator_a = [1, 0, 1, 1, 0, 1]
annotator_b = [1, 0, 1, 0, 0, 1]
kappa = cohen_kappa_score(annotator_a, annotator_b)
print("kappa=", round(kappa, 4))
经验上,Kappa 低于 0.6 时,先别急着扩大标注量,先回头修规范。
6. 指标设计:把业务指标和模型指标接起来
评测不是学术比赛,指标设计一定要跟业务动作挂钩。
我通常分成两组。
6.1 通用质量指标
适合大多数 LLM 应用:
- 任务成功率
- 指令遵循率
- 事实一致率
- 拒答合理率
- 结构化输出解析成功率
- 工具调用成功率
- 平均响应时延
- 单请求成本
6.2 业务定制指标
这部分才真正影响上线判断。举几个例子:
- 客服助手:误导性答复率、可直接发送率
- 审核助手:漏拦截率、误拦截率
- 检索问答:证据支持率、有效引用覆盖率
- 表单助手:字段填充完整率、关键字段正确率
说实话,很多团队只看“回答像不像人写的”,这个指标很容易把方向带偏。线上用户更关心的是能不能解决事,而不是句子是否漂亮。
6.3 指标聚合方式
别简单平均。建议按任务权重聚合。
例如:
def weighted_score(metric_scores, task_weights):
total = 0.0
weight_sum = 0.0
for task, score in metric_scores.items():
w = task_weights.get(task, 1.0)
total += score * w
weight_sum += w
return total / weight_sum if weight_sum else 0.0
scores = {
"faq_qa": 0.84,
"form_fill": 0.76,
"workflow_guide": 0.88
}
weights = {
"faq_qa": 0.5,
"form_fill": 0.3,
"workflow_guide": 0.2
}
print(weighted_score(scores, weights))
如果高价值任务只占 5% 流量,但一旦失败代价很高,就应该拉高它的权重。这点别偷懒。
7. 自动化回归评测:把评测接进发版流程
基线建好后,真正产生价值的地方在自动化。否则每次都靠人工点几个样本,最后还是回到“感觉不错”。
我常见的流程是这样的:
- 拉取固定评测集版本
- 按当前候选版本批量执行推理
- 保存原始输出、日志、耗时、模型配置
- 跑规则校验与自动打分
- 抽取需要人工复核的样本
- 生成版本对比报表
- 根据阈值决定是否允许发布
下面给一个简化版目录结构:
eval/
datasets/
baseline_set_v2026_04_01.jsonl
configs/
task_tree.yaml
metrics.yaml
runners/
run_eval.py
judge_eval.py
reports/
2026_04_30_modelA_vs_modelB/
7.1 跑评测主脚本
import json
import time
from pathlib import Path
def run_model(sample, model_client):
start = time.time()
output = model_client.invoke(
prompt=sample["prompt"],
context=sample.get("context", []),
tools=sample.get("tools", [])
)
latency_ms = int((time.time() - start) * 1000)
return {
"sample_id": sample["id"],
"output": output,
"latency_ms": latency_ms,
"model_version": model_client.version
}
def run_eval(dataset_path, model_client, out_path):
results = []
with open(dataset_path, "r", encoding="utf-8") as f:
for line in f:
sample = json.loads(line)
results.append(run_model(sample, model_client))
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
with open(out_path, "w", encoding="utf-8") as f:
for row in results:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
7.2 自动规则校验
能用规则判定的,优先规则,不要全靠大模型做裁判。
比如 JSON 结构、字段枚举、引用编号合法性、黑名单词、工具参数完整性,这些都该先过规则。
import json
def validate_json_schema(output_text, required_fields):
try:
data = json.loads(output_text)
except Exception:
return False, "invalid_json"
for field in required_fields:
if field not in data:
return False, f"missing_{field}"
return True, "ok"
7.3 LLM-as-a-Judge 的使用边界
有些指标规则判不了,比如“回答是否忠于上下文”“拒答是否合理”。这时可以引入 Judge 模型辅助打分。
但我建议把它放在两个位置:
- 初筛大批量样本
- 给人工复核做候选排序
不要把它当唯一真相。Judge 自己也会漂。
我常用一个做法:规则先过,Judge 再判,争议样本人工兜底。
Judge Prompt 示例:
你是评测员。请根据用户问题、参考证据、模型回答,判断回答是否被证据支持。
只输出 JSON:
{
"label": "pass/fail",
"reason": "...",
"confidence": 0-1
}
判定规则:
1. 若回答关键结论与证据冲突,输出 fail
2. 若回答关键结论无法从证据推出,输出 fail
3. 若回答核心结论被证据直接支持,输出 pass
8. 版本对比看板:别只放总分,要能看到“哪里变了”
很多报表的问题,是看上去很完整,真正做发布决策时却没用。因为它只给你一个分数,没告诉你退化发生在哪。
一个实用的版本看板,我一般会放这些内容:
8.1 总览区
- 评测集版本
- 对比版本 A / B
- 总体加权分
- 关键门禁指标是否过线
- 平均时延、P95 时延、单次成本
8.2 分任务对比区
按任务桶展示:
- 当前分数
- 上个版本分数
- 差值
- 样本量
- 主要失败原因 TopN
8.3 差样本列表
这是我最常看的区域。直接列:
- 新版本由 pass 变 fail 的样本
- fail 原因
- 原回答 vs 新回答
- 检索上下文差异
- 工具调用参数差异
只要这个区域清楚,排查效率会高很多。
如果你们内部有 BI 或 Grafana,直接接进去就行。没有的话,先用 Streamlit 做个轻量版也够用。
示例:
import pandas as pd
import streamlit as st
st.title("LLM Eval Dashboard")
df = pd.read_csv("report.csv")
st.metric("Overall Score", f"{df['score_new'].mean():.2%}")
st.subheader("Regression Samples")
reg_df = df[(df["label_old"] == "pass") & (df["label_new"] == "fail")]
st.dataframe(reg_df[["sample_id", "task_type", "reason_new", "answer_old", "answer_new"]])
9. 发版门禁怎么设:我用过的一套简单规则
评测的终点不是生成报表,而是服务上线决策。
可以先设一套朴素门禁:
- 总体加权分不低于基线版本 -1%
- 高风险任务不得出现显著退化
- 结构化输出解析成功率必须大于 99%
- 误拒答率、误调用率不超过阈值
- P95 时延和单请求成本不能超预算
如果某项没过线,不一定禁止上线,但必须有人工签字和回滚预案。
这一步很工程化。也很现实。
我见过一种情况:效果分涨了 2 个点,时延却从 3.2 秒升到 8.7 秒,客服场景里用户根本等不住。只看质量分会误判。
10. 一个可复现的最小落地方案
如果你现在还没有任何评测基线,可以从这个 MVP 开始:
10.1 第一周要做的事
- 定义任务树和 5 到 8 个核心指标
- 从线上抽 200 条样本
- 整理 30 条历史失败案例
- 写一版标注规范
- 完成首轮人工标注
10.2 第二周接自动化
- 固定数据集版本
- 写批量推理脚本
- 写规则评估脚本
- 输出 CSV 报表
- 加入 CI 或发版前检查流程
10.3 第三周补版本看板和回灌机制
- 展示 pass->fail 样本
- 接入线上 badcase 回灌
- 为每次发版保留评测记录
做到这一步,就已经能支撑多数中小团队的版本迭代。
11. 我实际踩过的几个坑
11.1 评测集太快过拟合
同一批样本反复跑,模型和 Prompt 很容易被“调”到这批题上。解决方法很简单:
保留一份冻结基线集,再维护一份滚动扩展集。冻结集看稳定性,滚动集看新问题覆盖。
11.2 标注标准漂移
不同人、不同阶段,对“可接受回答”的判断会变。这个问题很常见。
解决办法是定期抽样复审旧样本,把规范里的模糊表述改成规则句。比如把“基本正确”改成“关键结论无冲突且核心字段完整”。
11.3 过度依赖 Judge 模型
Judge 能提速,但不能完全代替人工。没想到的是,有次我们换了 Judge 模型版本,主应用分数没怎么变,评测通过率却波动了 6 个点,最后发现是评审标准在漂,不是业务模型退化。
这是唯一一处我会明确提醒的局限性:LLM-as-a-Judge 很方便,但必须定期抽人工校准。
12. 结语
大模型应用真正难的部分,很多时候不是“怎么把第一次效果做出来”,而是“怎么让第 10 次迭代还能稳定变好”。
评测基线解决的是这个问题。它不神秘,也不需要很重的平台建设。先把任务拆开,把样本集固定,把标注规则写清,把回归评测接进发版流程,再做版本对比看板,很多原本说不清的问题就会慢慢变得可定位、可比较、可复盘。
如果你正在做知识助手、Agent、审核系统、结构化抽取这类 LLM 应用,建议尽早补上这块。越晚补,历史债越难清。
我个人的经验是:先用 200 条样本跑起来,再慢慢扩,不要等“系统稳定了再做评测”。真到那时候,通常已经来不及了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)