模型回答“看起来不错”,并不等于应用质量稳定。提示词、模型版本或上下文稍有变化,原本正常的样例就可能失败;如果没有固定测试集和明确指标,改动只能靠感觉判断。本篇只聚焦一个核心知识点:如何为 AI 应用建立最小评估闭环。示例使用 Python 标准库和离线模拟模型,不需要密钥,也不会产生 API 费用。

评估到底在测什么

AI 应用评估不是给模型贴一个绝对分数,而是针对一个具体任务,检查输出是否满足预先定义的目标。比如“工单分类”可以检查类别是否正确;“摘要”可以检查是否非空、是否超过长度限制;“结构化提取”可以检查 JSON 能否解析、字段类型是否正确。

一次评估至少包含三样东西:

  1. 测试集:固定的输入,以及可比较的期望结果或检查条件。
  2. 被测函数:提示词和模型调用组成的应用逻辑。
  3. 指标:把每条样例的结果汇总成数字,例如通过率、失败数和平均耗时。

测试集不是随便挑的几个问题。它应该覆盖常规输入、边界输入和曾经失败的输入。每条样例都要尽量短小、可脱敏,并能说明“什么算通过”。

先把检查规则写清楚

最容易开始的是规则型评估。它不判断文风好不好,只检查程序可以稳定判断的条件。下面假设应用要把工单归为 billing(账单)或 technical(技术),并返回一个包含 category 的字典。真实模型接入后,可以把 classify 替换成 SDK 调用;评估器本身不应该和某个供应商客户端强耦合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from dataclasses import dataclass
from typing import Callable


@dataclass(frozen=True)
class Case:
text: str
expected: str


CASES = [
Case("信用卡扣款重复了", "billing"),
Case("页面打开后一直显示加载中", "technical"),
Case("发票上的金额不对", "billing"),
Case("登录页面返回 500", "technical"),
]


def classify(text: str) -> dict[str, str]:
"""离线模拟模型;真实项目中替换为模型调用。"""
billing_words = ("扣款", "发票", "金额")
category = "billing" if any(word in text for word in billing_words) else "technical"
return {"category": category}


def evaluate(
predict: Callable[[str], dict[str, str]],
cases: list[Case],
) -> dict[str, float | int]:
passed = 0
failures = 0
for case in cases:
try:
result = predict(case.text)
ok = result.get("category") == case.expected
except (KeyError, TypeError, ValueError):
ok = False
if ok:
passed += 1
else:
failures += 1
print(f"失败:{case.text!r},期望 {case.expected!r}")

total = len(cases)
return {
"total": total,
"passed": passed,
"failures": failures,
"pass_rate": passed / total if total else 0.0,
}


if __name__ == "__main__":
print(evaluate(classify, CASES))

Case 把输入和期望结果放在一起,避免测试数据散落在循环里。evaluate 只负责运行样例、记录失败和计算指标;classify 只负责产生结果,这种分离让我们可以更换提示词、模型或假实现,而不必重写评估逻辑。

保存为 evaluate_demo.py 后运行:

1
python evaluate_demo.py

这个离线示例可以真实执行,但它的分类规则只是演示评估器,不代表大模型能力。运行结果中的 pass_rate 是当前被测实现对当前测试集的通过率。接入真实模型时,不应预先写死或臆测输出,而应读取实际响应,再让评估器计算结果。

如何理解指标

最常见的指标是通过率:

1
通过率 = 通过的样例数 / 总样例数

如果四条样例中三条通过,通过率就是 0.75。它适合快速比较两个版本,但不能单独代表质量。一个版本可能整体通过率更高,却在“支付失败”这类关键场景上退化。因此评估报告至少还要保留失败样例和按类别统计的结果。

对于分类任务,可以进一步计算每个类别的准确率;对于结构化输出,可以分别统计“解析成功率”“字段完整率”和“字段值正确率”;对于摘要或问答,自动指标只能检查长度、引用存在、禁用内容等可验证条件,语义正确性通常还需要人工抽检或专门的评审模型。评审模型也不是天然正确,应该把它当作另一种有误差的测量工具。

指标必须在评估前确定。先看到结果再挑对自己有利的指标,会把评估变成自我证明。更实际的做法是先定义上线门槛,例如“总通过率不低于 95%,且关键类别不能出现零通过”;修改后同时比较新旧版本,确认总体和关键切片都没有不可接受的回归。

用假模型隔离网络和费用

评估流程不应每次都调用真实 API。可以把模型调用封装成一个函数,通过依赖注入替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def run_app(text: str, call_model) -> dict[str, str]:
prompt = f"请将工单分类为 billing 或 technical,只返回类别。工单:{text}"
raw = call_model(prompt)
category = raw.strip()
if category not in {"billing", "technical"}:
raise ValueError("模型返回了未知类别")
return {"category": category}


def fake_model(prompt: str) -> str:
if any(word in prompt for word in ("扣款", "发票", "金额")):
return "billing"
return "technical"


predict = lambda text: run_app(text, fake_model)
print(evaluate(predict, CASES))

离线假模型适合验证评估器、解析逻辑和错误处理;它不能验证真实模型的理解能力。真实模型评估应使用固定模型标识、固定参数和固定测试集,并记录运行时间、失败原因以及提示词版本。涉及用户数据时先脱敏,密钥只从环境变量读取,绝不把密钥或完整敏感原文写进报告。

常见问题

通过率达到 100% 就可以上线吗? 不一定。小测试集可能没有覆盖真实输入,且自动规则可能漏掉语义错误。应持续加入线上脱敏失败样例,并对关键场景人工抽检。

测试集越大越好吗? 不是。先保证样例有代表性、标签可靠、边界清晰。几百条重复样例不如几十条覆盖边界的样例有价值。

能不能只看最终答案,不记录失败输入? 不建议。没有失败样例就无法定位回归,也无法判断新版本改善了什么。报告中记录样例 ID 和失败类型即可,敏感原文应单独保护。

真实模型每次结果不同怎么办? 固定可控参数只能减少波动,不能消除波动。可以对同一样例运行多次,统计通过比例,并为非确定性任务设置容忍范围;不要把某一次措辞当作唯一正确答案。

小结

  • AI 评估的基本单位是“测试集 + 被测函数 + 指标”,不是凭印象试问。
  • 先用规则检查格式、字段和业务条件,再处理需要人工判断的语义质量。
  • 通过率要和失败样例、关键场景切片一起看,不能只追求一个总分。
  • 用可注入的假模型验证评估流程,把网络、费用和模型波动隔离开。
  • 每次改动都运行同一测试集并比较新旧版本;评估通过后,才有资格进入下一步工程化优化。