Skip to content

04 评测与迭代 ​

同一个问题问三次,得到三个看起来都合理的回答,不能证明系统可靠。本课把前三课拆成可以分别检查的环节,建立一份可以重复运行的小型评测集。

源码是 examples/doc_assistant/evaluate.py、cases.json 与 test_assistant.py。评测目标先限定为检索,不把离线样例通过率包装成模型质量。

1. 先说明什么叫成功 ​

对于“如何构建文档”,成功至少包含:

  1. 检索结果包含 build。
  2. 答案给出 npm run docs:build,并没有把开发服务器命令混进来。
  3. 如果谈到产物位置,写的是 docs/.vitepress/dist。
  4. 引用 build,且该引用确实支持上述结论。
  5. 对“构建成功是否已部署”回答“不代表”,没有多做未经请求的执行动作。

这五条分别涉及检索、事实、引用和行为边界。只有第一条能由本课的检索脚本直接判断。定义目标、准备数据、明确指标,再比较改动,是官方评测指南强调的基本流程。评测方法说明

2. 建立有答案与无答案样例 ​

cases.json 包含五条有答案问题和三条无答案问题:

json
[
  {"question": "如何构建文档", "expected_id": "build"},
  {"question": "怎么本地预览", "expected_id": "preview"},
  {"question": "明天天气怎样", "expected_id": null}
]

这里只展示三条,文件中有完整八条。null 表示教学资料无法回答该问题。不要删掉无答案样例来提高命中率:对于文档助手,知道什么时候缺少证据同样重要。

这些问题用于教学和回归,不是独立测试集。你已经知道它们,也可能据此调过参数,因此不能用它们的得分宣称真实业务效果。真实迭代应保留一份不参与调参的测试集,并加入不同说法、冲突资料、过期资料及误导性输入。

3. 三个指标分别表示什么 ​

指标分母分子本例含义
top1_hit_rate有答案问题数首条结果是预期文档的问题数最相关结果是否排第一
top2_hit_rate有答案问题数前两条包含预期文档的问题数生成前能否拿到必要证据
no_evidence_accuracy无答案问题数检索返回空结果的问题数是否错误地把无关资料当证据

每题当前只有一个标准文档 ID,所以这里称“命中率”;需要多份证据联合回答时,应记录完整的标准证据集合,再计算集合级召回率。分母为零时输出 null,而不是伪造一个 100%。

py
"""第四课:只评测检索,明确区分命中和无答案拒答。"""
import json
from pathlib import Path
from common import show
from retrieval import search


def evaluate(cases):
    answerable = [case for case in cases if case["expected_id"] is not None]
    unanswerable = [case for case in cases if case["expected_id"] is None]
    top1 = top2 = correct_empty = 0
    failures = []
    for case in cases:
        ids = [doc["id"] for doc in search(case["question"])]
        expected = case["expected_id"]
        if expected is None:
            correct_empty += not ids
            passed = not ids
        else:
            top1 += bool(ids) and ids[0] == expected
            top2 += expected in ids
            passed = bool(ids) and ids[0] == expected
        if not passed:
            failures.append({**case, "retrieved_ids": ids})
    return {
        "scope": "retrieval_only", "cases": len(cases),
        "answerable_cases": len(answerable), "unanswerable_cases": len(unanswerable),
        "top1_hit_rate": top1 / len(answerable) if answerable else None,
        "top2_hit_rate": top2 / len(answerable) if answerable else None,
        "no_evidence_accuracy": correct_empty / len(unanswerable) if unanswerable else None,
        "failures": failures,
    }


if __name__ == "__main__":
    cases = json.loads(Path(__file__).with_name("cases.json").read_text(encoding="utf-8"))
    report = evaluate(cases)
    show(report)
    raise SystemExit(1 if report["failures"] else 0)

运行:

bash
python -X utf8 evaluate.py

当前随附样例的离线输出:

json
{
  "scope": "retrieval_only",
  "cases": 8,
  "answerable_cases": 5,
  "unanswerable_cases": 3,
  "top1_hit_rate": 1.0,
  "top2_hit_rate": 1.0,
  "no_evidence_accuracy": 1.0,
  "failures": []
}

一旦出现失败,报告会保留问题、预期文档与实际 ID,进程退出码为 1。方便把结果用于本地检查,而不需要先搭建评测平台。

4. 答案质量需要另一张表 ​

真实模型输出没有在本次内容更新中运行。开启 --live 后,可以先人工记录以下字段,再考虑自动化打分:

字段记录内容
样例 ID、问题可以准确重跑的输入
资料版本本次 documents.json 的版本或提交
模型与提示词版本实际模型 ID、代码提交、关键参数
检索文档 ID区分检索失败和生成失败
事实正确命令、路径与关键条件是否正确
引用支持引用是否真正支持答案的断言
拒答恰当有依据时是否误拒答,无依据时是否猜测
耗时与 usage同一任务下比较响应耗时和 token 用量
错误原因资料缺失、检索漏召回、格式错误、错误引用、工具失败等

不要只用“答案包含某个关键词”判断正确,也不要只让同一个模型给自己打分。可以先让模型辅助分类或找可疑答案,再用人工标注对齐评分标准。命令答对但对部署状态作出错误承诺,仍应算失败。

5. 代码测试与效果评测有什么不同 ​

bash
python -X utf8 -m unittest -v

当前 11 项测试覆盖无效输入、无证据时不调用模型、拒答与未完成响应、伪造引用、未知工具、额外参数、reasoning 条目与调用 ID 回传、请求次数上限。

这些测试使用可控的协议响应,因此可重复、免费且适合检查程序边界。它们不能验证真实模型是否会选择正确工具、是否理解中文问题、是否引用了支持答案的句子。

6. 一次可解释的迭代 ​

  1. 先保存原始报告和代码版本。
  2. 新增“如何打包站点”这类可能漏召回的问题,标注正确资料。
  3. 只修改一个因素,例如增加同义表达处理。
  4. 重新跑所有样例,比较命中率和无答案判断是否一起变好。
  5. 对真实调用抽查答案,确认没有通过增加无关证据换来表面命中。
  6. 保留失败样例和改动原因,之后每次调整继续回归。

完成标准:能解释每个指标的分母;失败报告可以重现;不会把八条教学样例的通过率当成生产质量;能说明一次改动改善了哪类错误、又可能引入哪类错误。

后续阅读:评测体系、失败样例分桶、评测门禁与版本基线。返回教程目录。