大模型应用评测基线建设的工程实践

很多团队在做 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. 自动化回归评测:把评测接进发版流程

基线建好后,真正产生价值的地方在自动化。否则每次都靠人工点几个样本,最后还是回到“感觉不错”。

我常见的流程是这样的:

  1. 拉取固定评测集版本
  2. 按当前候选版本批量执行推理
  3. 保存原始输出、日志、耗时、模型配置
  4. 跑规则校验与自动打分
  5. 抽取需要人工复核的样本
  6. 生成版本对比报表
  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 条样本跑起来,再慢慢扩,不要等“系统稳定了再做评测”。真到那时候,通常已经来不及了。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐