← 全部极客知识库

工程化 / 大模型应用工程化

9. AI 应用评测:代码测试通过,为什么回答仍然可能不可靠

原文备份 · 第 11 / 21 篇 · 查看极客时间原知识库 ↗

本篇目录 · 10 节

来源:大模型应用工程化

笔记里写着“借阅期限为14天”,助手回答“可以借30天”,下面还认认真真附上了那句14天的原文。

这种输出很容易骗过一套看似完整的检查:JSON能解析,问题ID正确,引用的片段存在,引文也确实在原文中。程序没有报错,引用不是伪造的,用户却得到了错误结论。

上一篇,我们通过复现和代码差异修复了历史搜索故障。这一篇面对另一类问题:**代码按设计运行,并不代表它生成的回答达到了产品要求。**我们要为笔记助手建立一个小型评测集,把“找没找到资料”“引用是否合法”“结论有没有依据”“该不该说不知道”分别检查。

本篇的交付是一份机器检查报告和一份人工评审记录。约需40—60分钟,不需要模型账号、API Key或付费调用。用到的响应都是作者构造的教学案例,不是DeepSeek或Kimi的真实输出。

从正确的应用版本开始

下载第09篇完整材料,解压后在lesson-09目录打开终端。也可直接阅读工作区说明。

第08篇的project故意带有“删掉查询内部空格”的错误。本篇的project已经从它的reference正确版本复制,38个应用文件没有改动。若跟着自己的Git仓库学习,先确认上篇修复已经完成;不要用故障快照覆盖当前代码。

应用仍然使用DeepSeek官方API,Kimi Code仍是开发工具。本篇的必做路径完全离线,新脚本只调用本地资料读取、检索与验证函数,不导入模型客户端,也不读取应用运行配置。

先在编辑器中找到这些文件:

文件 本篇作用
eval/notes/library.md 固定的原始笔记
eval/cases.json 问题、检索关键词、人工标注的标准证据
eval/candidates.json 准备送入检查器的构造回答
evaluate_offline.py 调用真实本地函数,生成分层报告
docs/EVAL_REVIEW.md 需要你填写的人工记录模板
docs/EVAL_RUBRIC.md 评分规则与作者参考判定

资料、候选回答、评分规则分开存,是为了防止“用被评对象自己的说法证明自己正确”。标准证据需要人先读原文标注;它不是从候选答案倒推出来的。

先把一次回答拆成四个问题

本项目的资料问答大致经过这条路径:

问题 + 显式关键词
        ↓
从原始笔记切分并检索片段
        ↓
有命中 → 把选中片段交给模型 → 得到候选回答
无命中 → 在生成之前停止
        ↓
检查格式和引用 → 再判断语义及缺失行为

图中最后的语义与缺失评审是本课增加的人工检查,不是应用已经具备的自动裁判。

这里的“显式关键词”是应用目前真正使用的输入。它按关键词子串匹配片段,再排序选取;没有在后台自动做向量搜索或让模型改写查询。评测必须先理解当前系统,不能拿想象中的实现解释结果。

我们分四层看:

层 要回答的问题 典型失败
检索 必要的证据有没有进入所选上下文? 资料有答案,却没搜到
机械检查 格式、ID、引文位置是否符合约定? 引用了一句不存在的话
语义忠实 回答的事实断言是否得到资料支持? 引用14天,却回答30天
缺失判断 有证据时回答、证据不足时承认不足了吗? 编造罚款;有答案却拒答

Ragas原始论文同样强调,RAG评估需要分别考察检索上下文与生成回答等不同维度。本篇借用这种分层思路,使用自己的小题集和人工规则,不安装Ragas,也不把自定义结果称为该框架的指标。Ragas论文

分层的目的,是让下一步行动具体起来。检索没有交付证据时,应该先查资料切分、关键词和排序;证据已经完整交付,回答却改写了数字,才继续查生成环节。把所有失败统称“模型不够聪明”,很容易让你反复换模型,却绕开真正的问题。

用七个案例把盲区显露出来

打开eval/notes/library.md,全文很短:

# 社区书屋

借阅期限为14天。

续借需要在到期前向管理员申请。

按当前应用的切分方式,标题是F1-C1,借阅期限是F1-C2,续借说明是F1-C3。这些ID由文件顺序与切分顺序产生,不是永远不变的知识编号;将来修改资料后,必须重新核对标注。

现在看七个案例。前几项故意重复同一个问题,以便隔离变量:

案例 输入或候选中的关键差异 用来观察什么
E01 搜“借阅”,答14天,引14天原句 正常对照
E02 同样证据,答30天,引14天原句 真实引用掩盖错误结论
E03 答30天,引用捏造的“借阅期限为30天。” 机械检查能抓到什么
E04 问借多久,却搜“贷款” 原文有答案,检索无命中
E05 问罚款,选中续借说明,答每天1元 相关不等于足够
E06 与E05同样问题及上下文,标记资料不足 合理的缺失回答
E07 已选中14天说明,却标记资料不足 过度拒答

E04没有候选响应。现有应用无命中时会在生成前停止;评测脚本也不把这个案例伪装成“模型看完资料后拒答”。

E05更隐蔽:它确实检索到一段关于借书的文字,但这段文字不能回答罚款问题。有资料、资料相关、资料足够支持答案,是三个不同判断。

此时还要区分两个观察位置:评审者能阅读整份固定笔记,生成器在这次任务中只看到选中的片段。若整份资料有答案、所选片段没有,生成器谨慎标记缺失可能是合理的,同时检索系统仍然失败。不能要求模型凭空知道它没有收到的内容。

跑出报告,先不要急着庆祝“通过”

确认终端当前目录为lesson-09,并且Python版本至少为3.11。已有虚拟环境可以复用,但新评测不需要安装依赖。

macOS/Linux:

python3 --version
python3 evaluate_offline.py --report evaluation-result.json

Windows PowerShell:

py -3 --version
py -3 evaluate_offline.py --report evaluation-result.json

脚本把结果打印在终端,同时创建evaluation-result.json。如果同名文件已存在,会拒绝覆盖并退出2;复跑时换新文件名,或者去掉--report只看终端输出。

默认题集的实际离线结果中,summary应包含:

{
  "cases": 7,
  "retrieval_eligible_cases": 5,
  "mean_gold_coverage": 0.8,
  "mechanical_checked": 6,
  "mechanical_pass": 5,
  "skipped_no_context": 1,
  "semantic_pending": 6
}

请用编辑器重新打开生成的文件,找到E02、E03、E04,而不是只读汇总。每项都记录了所选片段及原文、候选内容和机械结果。独立核对以下三件事:

  1. E02的候选是30天,上下文是14天,但mechanical为pass。
  2. E03的mechanical为fail,原因是引文不在对应原文中。
  3. E04的selected_ids为空,候选为null,状态为skipped_no_context。

这才证明你看到的是本课要研究的情况。退出码0仅表示脚本完成了报告;它不表示所有回答合格。本脚本是一个收集评测结果的工具,并没有把全部质量要求配置成发布门禁。

0.8究竟算的是什么

题集里五个“整份资料能够回答”的案例都标了一个必要证据片段。四个成功取到了它,E04没有,因此平均标准证据覆盖是(1+1+1+0+1)/5=0.8。

E05、E06的问题在原始资料中没有答案,标准证据列表为空。它们的覆盖值为null,不加入分母。把“没有标准证据可找”算成检索100%成功,会把指标人为抬高。

这只是本课基于人工标注的覆盖规则。真实任务可能需要两个片段联合回答,也可能有多个不同片段都足以回答;那时必须先定义必要证据与可替代证据,不能机械照搬此处ID集合。

同样,机械检查是5/6通过,不是5/7。E04未进入这一层。一个总百分比很难同时表达这些不同分母,更不能直接拿来判断“用户满意度”。

人工评审要找出结论与依据之间的关系

把docs/EVAL_REVIEW.md另存为docs/my-eval-review.md,先自行填表,再打开评分规则末尾的参考答案。

不要只写“感觉不靠谱”。以E02为例,写清楚:

候选断言:可以借30天。
收到的证据:借阅期限为14天。
机械结果:通过,引用确实存在。
语义结果:失败,期限与证据矛盾。
归因:已有必要证据,问题发生在候选答案的内容层。

对于较长回答,把每一个可核查的事实分别列出。例如“可以借14天,逾期每天罚1元”前半句有依据,后半句没有;不能因为第一句正确就让整段过关。这里关注的是对所给资料的忠实程度,不是让评审者用常识替回答补证据。

也不要要求答案逐字等于参考句子。“借期是两周”在本题语境下可以正确表达14天;字符串完全相等的检查会误伤合理改写。不过金额、数量、否定词、条件与时间范围必须保留原意。

E07则说明“少说就永远安全”也不成立。上下文已经给出14天,候选仍说“笔记未提供,无法判断”,虽然满足现有missing格式,却没完成用户任务。评估资料不足行为时,要同时观察无根据作答和不必要拒答。

按本题集规则,E01、E06合格;E02、E03、E05、E07不合格;E04没有回答可评。机器报告中的六项pending不会自动变成这些人工结论,你的评审文件负责保留判断和依据。

这是六份刻意安排的教学响应,包含重复问题和坏答案。**人工2/6合格不代表DeepSeek的准确率,也不代表真实用户会遇到同样的错误比例。**它证明的是:仅用当前机械验证器,不足以识别全部质量问题。

做一次对照:修改答案,为什么机器分数没变

现在用编辑器把eval/candidates.json另存为eval/candidates-trial.json。只改E02的answer,从“可以借30天。”改为“可以借14天。”,保留原题集和其他字段。

macOS/Linux:

python3 evaluate_offline.py --candidates eval/candidates-trial.json --report evaluation-trial.json

Windows PowerShell:

py -3 evaluate_offline.py --candidates eval/candidates-trial.json --report evaluation-trial.json

预期机械汇总完全相同:仍然5/6通过、6项语义待评。但E02的候选文本已经变化,人工判断应从失败改为通过。对比两个报告,其他案例应保持相同。

原因在project/validation.py中:对于supported回答,它检查引用存在、引文为原文子串等约束,没有实现“30与14是否矛盾”的语义判定。缺少这个能力不会因为换了一个好答案就自动显露为机器分数变化。

再思考一个问题:只把E03的伪造引文改成14天原句,保留30天答案,会怎样?答案是机械检查转为通过,语义仍错。作者已用临时候选副本验证这个反例,它正好回到了E02的结构。

这项练习是在修改构造响应,不是修改提示词,更不是改善真实模型。它帮助你验证评测工具的能力边界。后续若真的调整了提示词,应重新生成回答并保留真实请求与结果,再在相同规则下比较。

可以请Kimi帮忙,但评审标准要握在自己手里

已配置好Kimi Code的读者,可以在本篇目录运行下面的可选步骤。它会消耗开发工具的服务额度;本课没有要求批量生成或让笔记助手调用DeepSeek。

kimi --plan

写作日2026年10月4日本机kimi --version为2.0.2,帮助中确认有--plan;官方文档也提供/plan切换说明。模式用于安排计划与执行流程,不能取代你对实际工具操作的审查。Kimi交互文档

提交一轮范围明确的请求即可:

请阅读docs/tasks/009-evaluation.md、eval/cases.json、
eval/candidates.json、eval/notes/library.md、docs/EVAL_RUBRIC.md
以及project/validation.py。
只评审E02、E04、E07:列出关键断言、直接证据、失败层和理由。
说明为什么机械pass不能代表语义通过。
不改文件,不运行外部请求,不读取配置、密钥或个人data。
不要把构造候选当成真实DeepSeek输出,也不要为了全绿删案例。

这段请求由作者提供,未执行真实Kimi会话。你得到结果后,仍然回到原文逐条核对。如果Kimi说E04是“模型拒答”,就指出它没有候选响应;如果它认为E07很谨慎所以通过,就让它解释已收到的14天证据为何不足。

以后题集变大,模型裁判可以辅助筛查,但要先用人工标注校准、抽查分歧,并记录裁判自身的模型、提示词与消耗。让同一个模型生成回答后再笼统评价“是否优秀”,很难形成独立证据。

把小题集变成持续改进的起点

个人项目开始时,不需要立刻搭建大型评测平台。先积累几类真实失败:同义表达搜不到、只找到部分条件、引用正确却结论错误、资料不足仍编造、有依据仍拒答。去掉个人信息,再为每个案例补上可复核的原文与判断标准。

比较两个版本时,固定资料、问题、关键词、所选上下文或检索设置,以及生成参数。一次只改一个因素,才能解释改进来自哪里。若比较完整应用,允许检索结果变化,但要分别保留检索与回答记录,避免把两层变化混为一谈。

不要一边看结果,一边悄悄删掉失败题或放宽判分规则。开发题集可以用于调试;另外保留一些未参与调参的题,检查修改是否只对熟悉案例奏效。真实生成具有波动,单次成功也不能证明每次都成功;重复次数应结合任务风险与预算确定,不在入门练习里无上限重试。

本篇脚本刻意始终标记候选来源为authored_offline_fixtures_not_live_model。若将来加入真实API评测,应增加明确的运行记录与来源区分,不能把抓来的响应塞进本脚本,却继续冒用“作者构造”的标识。

遇到这些情况先查哪里

现象 优先检查
找不到脚本或资料文件 是否在解压后的lesson-09;保留eval与project目录结构
提示报告已存在 这是防覆盖;改报告名或去掉--report,不必删原记录
机械通过5项,误以为任务失败 先确认E03故意伪造引文;本课不追求全部候选为绿色
出现6项语义pending 正常,脚本没有语义裁判;需要填写人工记录
覆盖率或片段ID变了 对比原文与报告hash;检查空行、标题、关键词、top_k和标注是否被改过
真实项目里标missing太多 分开查看检索漏证据、所选上下文不足、已给足证据仍拒答

作者完成了默认报告、候选改写对照、伪造引文变体、缺少/多余候选、重复JSON键及报告防覆盖检查,并确认38个应用文件与第08篇正确版相同。恢复正确版本后的历史搜索HTTP检查4项通过,包含上篇触发错误的S10。没有真实Kimi或DeepSeek调用,也没有Windows实机或浏览器视觉验证。

完成本篇时,你应留下原始报告、自己的人工评审,以及一次候选对照结果。项目增加的是一套能说明“哪里出了问题”的反馈材料,业务代码还没变化。下一篇将把这种分层观察延伸到日志、追踪和成本:一次失败经历了哪些步骤,花在哪里,又该从哪里恢复。

返回第四部分大纲 · 上一篇:调试与代码审查