Skip to content
H · AI 应用开发进阶第 2 课⏱ 20 分钟

评估:给 AI 应用写测试

学完你能
  • 整理一份 20–50 条的金标准案例集,并先用规则检查过滤明显的错误
  • 写出带评分标准、输出 JSON 判定的模型评审,知道它的几种偏差和应对方法
  • 把评估变成每次改提示词或换模型都要跑的回归测试,并用于线上抽样
Yui和Kai在评估工坊中用金标准、规则闸门和模型评审检查AI回答
Yui和Kai在评估工坊中用金标准、规则闸门和模型评审检查AI回答AI 生成配图

上一课改提示词时,判断效果的办法是「多试几条,看着还行」。这有两个问题:你试的几条往往是自己想出来的、偏简单的输入;而且每次试的都不一样,没法比较。改了一处规则,可能修好了你盯着的那条,却把另外三条弄坏了,你根本看不到。

普通软件靠测试防止改坏。AI 应用的输出不固定,不能用 assert output == "...",但思路一样:固定一组输入,定好什么算对,每次改动都自动跑一遍,看分数。这就是评估(eval)。

第一步:金标准案例集 ​

Yui和Kai从真实案例中整理多样样本,建立覆盖边界情况的金标准案例集
Yui和Kai从真实案例中整理多样样本,建立覆盖边界情况的金标准案例集AI 生成配图

金标准(golden set)是一组你认可「答案应该是这样」的案例。刚开始 20–50 条就够用,关键在于挑得好:

  • 来自真实输入。从日志、客服记录里挑,不要自己编。真实留言的错别字、语序混乱、一句话两件事,是自己想不出来的。
  • 覆盖各类情况。常见情况占大头,再专门留出边界情况:信息缺失、和业务无关、超长、夹带「忽略上面的要求」的输入。
  • 写要点,不写标准答案原文。模型每次措辞不同,逐字比对没有意义。记下「必须包含什么」和「不能出现什么」:
python
CASES = [
    {
        "id": "quality-01",
        "input": "上周买的耳机,订单 20240917-8812,左耳没声音,要么换要么退,周末前解决。",
        "points": ["问题类型是质量", "订单号 20240917-8812", "诉求包含换货或退款", "期限是周末前"],
    },
    {
        "id": "no-order-01",
        "input": "衣服洗一次就掉色,我要退货退款!",
        "points": ["问题类型是质量", "诉求是退货退款", "订单号为空,不能编造"],
    },
    # ……共 20–50 条
]
  • 线上出过错的输入,修好后加进来。这样同一个错误就不会悄悄再出现。

第二步:先做规则检查 ​

很多错误根本不需要模型来判断。下面假设上一课的工单助手已经按建议改成了 JSON Schema 输出:

python
import json

TYPES = {"退款", "换货", "物流", "质量", "账号", "其他"}
BANNED = ["亲", "保证", "100%"]

def rule_check(output):
    """返回问题列表,空列表表示通过。"""
    try:
        data = json.loads(output)
    except json.JSONDecodeError:
        return ["不是合法 JSON"]
    problems = []
    for field in ["type", "order_id", "request", "summary"]:
        if field not in data:
            problems.append(f"缺少字段 {field}")
    if data.get("type") not in TYPES:
        problems.append(f"类型不合法:{data.get('type')}")
    if len(data.get("summary", "")) > 50:
        problems.append("摘要超过 50 字")
    for word in BANNED:
        if word in output:
            problems.append(f"出现禁用词:{word}")
    return problems

规则检查快、免费、结果确定,应该放在最前面。能写成规则的就写成规则,例如还可以检查:订单号是否真的出现在输入原文里。规则没通过的,就不用再花钱让模型评审了。

第三步:用模型当评审 ​

Yui和Kai让模型评审对照参考要点,用评分标准和结构化结果判断回答质量
Yui和Kai让模型评审对照参考要点,用评分标准和结构化结果判断回答质量AI 生成配图

「诉求概括得对不对」「回答有没有漏掉要点」这类问题写不成规则,可以交给另一次模型调用来判断,这叫 LLM-as-judge。要让评审稳定,有三点:

  1. 给评分标准(rubric)。不要问「这个回答好不好」,而是写清楚每个分数对应什么情况。
  2. 给参考要点。让评审对照金标准里的要点检查,而不是凭它自己的理解。
  3. 用 JSON Schema 输出判定。先写理由,再给分数,程序直接读取分数和是否通过。

动手:给一个回答打分 ​

下面是一个评审:系统提示词是评分标准,输入里有问题、参考要点和待评的回答。这个回答漏掉了「大杯加 4 元」,看评审能不能发现。再试试把回答改对,或者改成一个很长但内容错误的回答。

▶ 动手试试
系统提示词(这次请求一起发送,点开查看)
你是 AI 应用的评审员。根据参考要点,评价待评回答是否正确、完整地回答了问题。 评分标准: 5 分:覆盖全部参考要点,结论正确,没有和要点矛盾的内容。 4 分:结论正确,漏掉一个次要要点,或表述略有含糊。 3 分:结论正确,但漏掉多个要点;或有一处不影响结论的小错误。 2 分:结论错误,但部分步骤正确。 1 分:结论错误且大部分内容不对,或没有回答问题。 规则: 1. 只对照参考要点判断,不用你自己的知识补充或修改要点。 2. 不因为回答更长、语气更客气而加分。 3. 先在 reasons 里逐条说明每个要点是否覆盖,再给分。 4. 4 分及以上算通过。
登录后运行登录 HiveGPT 后每天有免费运行次数
示例输出(之前运行的结果)
{
  "reasons": "要点1未覆盖:回答按每杯 28 元计算,漏掉大杯加 4 元。要点2错误:应为 96 元,回答为 84 元。要点3正确:200 积分抵扣 10 元。要点4错误:应付 86 元,回答为 74 元。",
  "missing": ["大杯每杯 32 元", "3 杯共 96 元", "应付 86 元"],
  "score": 2,
  "pass": false
}

Schema 里把 reasons 放在 score 前面,是为了让模型先写出判断过程再给分;先给分再找理由,理由往往只是在为分数辩护。

一个最小的评估脚本 ​

把被测应用、规则检查和评审串起来:

python
import json, os
from openai import OpenAI

client = OpenAI(base_url="https://hivegpt.cn/v1", api_key=os.environ["HIVEGPT_API_KEY"])

JUDGE_SYSTEM = open("prompts/judge.md", encoding="utf-8").read()   # 就是上面运行框里的评分标准
VERDICT_SCHEMA = {
    "name": "verdict",
    "strict": True,
    "schema": {
        "type": "object",
        "properties": {
            "reasons": {"type": "string"},
            "missing": {"type": "array", "items": {"type": "string"}},
            "score": {"type": "integer", "enum": [1, 2, 3, 4, 5]},
            "pass": {"type": "boolean"},
        },
        "required": ["reasons", "missing", "score", "pass"],
        "additionalProperties": False,
    },
}

def judge(question, points, answer):
    points_text = "\n".join(f"{i}. {p}" for i, p in enumerate(points, 1))
    resp = client.chat.completions.create(
        model="gpt-5.5",
        messages=[
            {"role": "system", "content": JUDGE_SYSTEM},
            {"role": "user", "content": f"【问题】{question}\n【参考要点】\n{points_text}\n【待评回答】{answer}"},
        ],
        response_format={"type": "json_schema", "json_schema": VERDICT_SCHEMA},
    )
    return json.loads(resp.choices[0].message.content)

def run_eval(app, cases, out_file="eval_results.json"):
    results = []
    for case in cases:
        output = app(case["input"])                  # app:被测的函数,比如上一课的工单助手
        problems = rule_check(output)
        if problems:
            results.append({"id": case["id"], "pass": False, "score": 0, "why": problems, "output": output})
            continue
        v = judge(case["input"], case["points"], output)
        results.append({"id": case["id"], "pass": v["pass"], "score": v["score"], "why": v["missing"], "output": output})

    passed = sum(r["pass"] for r in results)
    print(f"通过 {passed}/{len(results)},平均分 {sum(r['score'] for r in results) / len(results):.2f}")
    for r in results:
        if not r["pass"]:
            print(f"  失败 {r['id']}: {r['why']}")
    with open(out_file, "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)   # 留档,和下次的结果对比
    return results

输出里不只看总分,更要看哪几条失败了、为什么。总分从 42/50 变成 43/50,可能是修好了两条、又弄坏了一条,逐条对比才能看出来。

对比两版提示词 ​

有时你不需要绝对分数,只想知道新版是不是比旧版好。这时让评审在两个回答之间选一个(pairwise),比分别打分更稳定。关键是交换位置各评一次:

python
def compare(question, answer_old, answer_new):
    first = pick(question, answer_old, answer_new)    # pick 返回 "A" 或 "B",A 是先给的那个
    second = pick(question, answer_new, answer_old)   # 换个位置再评一次
    if first == "B" and second == "A":
        return "new"
    if first == "A" and second == "B":
        return "old"
    return "tie"   # 两次结论不一致:差别不明显,或者评审在看位置而不是内容

pick 就是一次评审调用,Schema 改成 {"reason": ..., "winner": "A" 或 "B"}。对整个案例集统计「新版赢 / 旧版赢 / 平」的条数,再决定要不要上线。

评审也会出错 ​

模型评审很方便,但它有已知的偏差:

偏差表现怎么应对
位置偏好两两比较时偏向第一个或第二个交换位置各评一次,不一致算平
长度偏好回答越长、越客气,分数越高在评分标准里写明「不因长度加分」,给参考要点
自我偏好偏爱和自己风格相近的回答有条件时评审和被测用不同模型
和人的判断不一致评审给 5 分,业务同事觉得不行定期人工抽查

人工抽查的做法:从评审过的结果里随机抽 20–30 条,请懂业务的人独立打「通过 / 不通过」,再算和评审结论一致的比例。一致率低(比如不到八成),先别急着改被测应用,先看分歧在哪,改评分标准或参考要点。评审本身也需要被评估,否则你只是把「看着还行」换成了「模型说还行」。

什么时候跑 ​

  • 每次改提示词、换模型、改检索逻辑,都跑一遍。这是回归测试:确认修好了想修的,也没弄坏别的。把它放进 CI,或者至少写成一条命令。
  • 定期抽样线上日志。每天抽一部分真实请求(去掉手机号、地址等个人信息),跑规则检查和评审。线上请求没有参考要点,评审只按通用标准判断(格式、是否答非所问、是否编造),分数低的交给人看。
  • 把发现的坏例子加进金标准。案例集会随着应用一起长大,越来越接近真实的使用情况。

评估本身也要花钱:50 条案例,每条一次被测调用加一次评审调用,就是 100 次请求。规则检查放前面、评审只跑必要的,成本是可控的。

小结 ​

  • 「试了几条看着还行」不算测试:先整理 20–50 条真实案例,记下每条的要点和禁止出现的内容。
  • 先用规则检查过滤格式、字段、长度、禁用词,再用带评分标准、参考要点和 JSON Schema 判定的模型评审;两版对比时交换位置各评一次。
  • 评审有位置、长度等偏差,要定期人工抽查;每次改动都跑回归,并抽样线上日志,把坏例子补进案例集。

下一课:对话一长,上下文装不下、模型开始忘事。我们来看上下文工程:长对话、记忆和压缩。

代码示例在页面里运行时使用 HiveGPT 的模型接口。延伸阅读来自 JavaGuide(Apache-2.0),版权归原作者。