
AI 幻觉怎么量化用 Python 算 AI 客服与知识库输出的假阳性率做 AI 客服和知识库最容易踩的坑不是模型不会回答而是它编了一条看起来完全合理的话而你没有当场发现。这类错有个专业说法假阳性false positive——报了一个根本不存在的毛病或者给了一个不存在的事实。业务上更常见的三种形态编条款问退货运费谁出保修几年它不说不知道而是给一句措辞规范、数字具体的答复编引用问某条规定怎么写它引用一段原文还带条款编号回原文件一翻不存在依据错位结论对但出处是另一份文档或已作废版本这三种错的共同点是都不像错有格式、有细节、有出处恰好绕过人的下意识判断。所以靠感觉它准不准是不可用的得把它变成数字。这个判断有官方依据第 48 届世界技能大赛2026 年 9 月 22-27 日上海首次设立软件测试赛项。官方对赛题的解释是世界技能大赛中国上海研究中心主任王迪赛题设置盲盒任务要求选手自主确定测试范围、选择工具、设计测试策略。AI 仅用于生成脚本、批量执行、日志分析等重复性工作。裁判核查选手对 AI 输出结果的校验、甄别 AI 生成内容中的错误与假阳性缺陷评估选手对 AI 参数、提示词、自动化流程的自主设计等。也就是说AI 干重复活人负责甄别它编出来的那部分。这是赛题评分点不是感想。度量方法把发现对到 ground truth 上做法很朴素人工先逐条核对原文得到真实问题清单ground truth再把 AI 的每条发现和它比对统计四个数指标含义业务读法命中 TPAI 报了人工确认真实存在有效产出假阳性 FPAI 报了原文里没这回事直接消耗人力与信任漏报 FN人工找到的问题AI 没报风险敞口精确率 / 召回率 / 假阳性率由前三个数算决定这类活能不能放手可跑代码Python 3.13无第三方依赖# -*- coding: utf-8 -*- AI 输出甄别假阳性 / 漏报 度量脚手架 import json from dataclasses import dataclass dataclass class Finding: item: str # 被检查的对象文档名 / 接口路径 / 问答条目 issue: str # AI 声称存在的问题 evidence: str # AI 给出的依据原文片段或行号——空字符串 没给依据 # ── 人工确认过的真实问题ground truth──────────────────────── GROUND_TRUTH [ (docs/refund.md, 退款到账时间写成 7 个工作日与实际 15 天不符), (docs/warranty.md, 整机保修写 2 年售后口径是 1 年), (docs/invoice.md, 未写明专票需要税号), (docs/shipping.md, 偏远地区不发货正文没有说明), (kb/faq-12.md, 答非所问问的是运费答的是时效), ] # ── AI 输出的「发现」清单 ──────────────────────────────────── AI_FINDINGS [ Finding(docs/refund.md, 退款到账时间写成 7 个工作日与实际 15 天不符, 原文第 3 段), Finding(docs/warranty.md, 整机保修写 2 年售后口径是 1 年, 原文第 1 段), Finding(docs/invoice.md, 未写明专票需要税号, 原文第 2 段), Finding(docs/shipping.md, 偏远地区不发货正文没有说明, 原文第 4 段), Finding(kb/faq-12.md, 答非所问问的是运费答的是时效, ), # 真问题没给依据 Finding(docs/refund.md, 退款条款缺失「超过 30 天不予受理」的约定, 原文第 6 段), # 假阳性 Finding(docs/warranty.md, 保修范围未包含人工费, 原文第 9 段), # 假阳性 Finding(kb/faq-30.md, 该条目引用已作废的旧版政策, 原文第 1 段), # 假阳性 ] def norm(f: Finding) - tuple: return (f.item.strip(), f.issue.strip()) def measure(truth, findings, require_evidenceFalse): require_evidenceTrue 时没给依据的发现不计入命中下游要能追溯。 tset {(i.strip(), s.strip()) for i, s in truth} kept, dropped [], [] for f in findings: if require_evidence and not f.evidence.strip(): dropped.append(f); continue kept.append(f) hit [f for f in kept if norm(f) in tset] fp [f for f in kept if norm(f) not in tset] reported {norm(f) for f in kept} miss [(i, s) for (i, s) in tset if (i, s) not in reported] tp, fpn len(hit), len(fp) precision tp / (tp fpn) if (tp fpn) else 0.0 recall tp / len(tset) if tset else 0.0 return { AI 报出总数: len(findings), 计入核查: len(kept), 无依据被拦下: len(dropped), 命中真实问题: tp, 假阳性编出来的: fpn, 漏报人发现AI没报: len(miss), 精确率: round(precision, 3), 召回率: round(recall, 3), 假阳性率: round(fpn / (tp fpn), 3) if (tp fpn) else 0.0, } if __name__ __main__: print(【A】照单全收AI 报什么就信什么) a measure(GROUND_TRUTH, AI_FINDINGS) print(json.dumps(a, ensure_asciiFalse, indent2)) print(\n【B】加一道闸没给依据的发现不进下游) b measure(GROUND_TRUTH, AI_FINDINGS, require_evidenceTrue) print(json.dumps(b, ensure_asciiFalse, indent2)) print(\n【逐条看】) tset {(i.strip(), s.strip()) for i, s in GROUND_TRUTH} reported_b {norm(f) for f in AI_FINDINGS if f.evidence.strip()} for f in AI_FINDINGS: if not f.evidence.strip(): print(f [B 拦下没给依据] {f.item} :: {f.issue}) if norm(f) in tset: print( ↑ 注意这是一条真问题闸门把真问题也拦了) for f in AI_FINDINGS: if norm(f) not in tset: print(f [假阳性原文里没有这回事] {f.item} :: {f.issue}) for i, s in GROUND_TRUTH: key (i.strip(), s.strip()) if key in reported_b: continue if any(norm(f) key for f in AI_FINDINGS): print(f [B 因闸门漏掉] {i} :: {s}) else: print(f [AI 从未报出] {i} :: {s}) print(f\n【结论】A 假阳性率 {a[假阳性率]:.1%}B 假阳性率 {b[假阳性率]:.1%} f但 B 的召回率从 {a[召回率]:.1%} 掉到 {b[召回率]:.1%})运行结果【A】照单全收AI 报什么就信什么 { AI 报出总数: 8, 计入核查: 8, 无依据被拦下: 0, 命中真实问题: 5, 假阳性编出来的: 3, 漏报人发现AI没报: 0, 精确率: 0.625, 召回率: 1.0, 假阳性率: 0.375 } 【B】加一道闸没给依据的发现不进下游 { AI 报出总数: 8, 计入核查: 7, 无依据被拦下: 1, 命中真实问题: 4, 假阳性编出来的: 3, 漏报人发现AI没报: 1, 精确率: 0.571, 召回率: 0.8, 假阳性率: 0.429 } 【逐条看】 [B 拦下没给依据] kb/faq-12.md :: 答非所问问的是运费答的是时效 ↑ 注意这是一条真问题闸门把真问题也拦了 [假阳性原文里没有这回事] docs/refund.md :: 退款条款缺失「超过 30 天不予受理」的约定 [假阳性原文里没有这回事] docs/warranty.md :: 保修范围未包含人工费 [假阳性原文里没有这回事] kb/faq-30.md :: 该条目引用已作废的旧版政策 [B 因闸门漏掉] kb/faq-12.md :: 答非所问问的是运费答的是时效 【结论】A 假阳性率 37.5%B 假阳性率 42.9%但 B 的召回率从 100.0% 掉到 80.0%结果解读一、假阳性率可以量化。8 条发现里 3 条是编的假阳性率 37.5%。这个数字的意义不是给模型打分而是判断某一类活到底能不能交给 AI 单独跑。二、加闸门会连真问题一起拦。B 方案要求每条发现必须给出依据被拦下的那条恰好是真实存在、只是没标依据的问题——召回率从 100% 掉到 80%。阈值不是越严越好收紧的同时必须配人工兜底否则你会把没报出问题误当成没有问题。三、反向指标比正向指标重要。大家习惯统计AI 帮我发现了多少问题很少统计它编了多少。但后者才决定下游还敢不敢信这份交付物。落地建议分档定策略同一类活连跑两周假阳性率稳定在低位例如 5% 以内才考虑减免人工复核高位环节保留人审。按来源分桶把假阳性分编条款 / 编引用 / 依据错位三类计数。编引用通常出现在文档结构混乱、版本多的库里——先去治库比反复调提示词有效。给依据设门槛要求每条发现指出原文位置做不到的不进下游require_evidence一个参数即可切换。参考来源世界技能组织官网竞赛与 Skills 页面worldskills2026.com世界技能大赛中国上海研究中心主任王迪对软件测试赛项的官方解读2026-09-22WorldSkills International《Software Testing — Technical Description》(WSC2026_TD11_en)我们是谁我们做企业知识库与 AI 客服落地日常工作是给 AI 的输出加可核验的环节——让「客户天天问的问题」答得准并且能追溯到原文。