Appearance
04 评测与迭代
同一个问题问三次,得到三个看起来都合理的回答,不能证明系统可靠。本课把前三课拆成可以分别检查的环节,建立一份可以重复运行的小型评测集。
源码是 examples/doc_assistant/evaluate.py、cases.json 与 test_assistant.py。评测目标先限定为检索,不把离线样例通过率包装成模型质量。
1. 先说明什么叫成功
对于“如何构建文档”,成功至少包含:
- 检索结果包含
build。 - 答案给出
npm run docs:build,并没有把开发服务器命令混进来。 - 如果谈到产物位置,写的是
docs/.vitepress/dist。 - 引用
build,且该引用确实支持上述结论。 - 对“构建成功是否已部署”回答“不代表”,没有多做未经请求的执行动作。
这五条分别涉及检索、事实、引用和行为边界。只有第一条能由本课的检索脚本直接判断。定义目标、准备数据、明确指标,再比较改动,是官方评测指南强调的基本流程。评测方法说明
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. 一次可解释的迭代
- 先保存原始报告和代码版本。
- 新增“如何打包站点”这类可能漏召回的问题,标注正确资料。
- 只修改一个因素,例如增加同义表达处理。
- 重新跑所有样例,比较命中率和无答案判断是否一起变好。
- 对真实调用抽查答案,确认没有通过增加无关证据换来表面命中。
- 保留失败样例和改动原因,之后每次调整继续回归。
完成标准:能解释每个指标的分母;失败报告可以重现;不会把八条教学样例的通过率当成生产质量;能说明一次改动改善了哪类错误、又可能引入哪类错误。