9 minute read

“Perfection is finally attained not when there is no longer anything to add but when there is no longer anything to take away.” — Antoine de Saint-Exupéry

95% 召回率的虚假繁荣

生产级 AI 的评估标准,是一套能精准定位故障节点的管线。

某家研发 AI 生产线的公司摸索出过一条铁律:对 AI 的出品不满意时,要调的是生产管线,而不是对单一成品做修改。如果 AI 总是把“八十年代”读成“八零年代”,你不能指望在对话框里靠吼来纠正它,你得从生产环节里查原因。不要把 AI 当成许愿机,要把它当成工作台。

在构建企业级 RAG(检索增强生成)和 Agent 架构时,这个道理同样适用。当你看着一个错误的最终回答,无奈地耸耸肩说“大模型又幻觉了”时,你正在把 AI 当成一个不可理喻的许愿机。真正的生产级工程,拒绝把一切错误都甩锅给生成模型。

现在,打开你的 RAG 监控面板,跑一下这条查询:

SELECT avg(len(context)) FROM rag_logs WHERE user_feedback = 'downvote';

如果你发现被踩的回答往往伴随着超长的上下文,说明你的系统正处于一种极具迷惑性的状态:检索环节的 Recall@5(召回率)高达 95%,但最终回答的准确率却只有 75%。

召回率陷阱:K 篇候选不等于 K 篇相关

面对 75% 的准确率,如果你的第一反应是“换个更大的 LLM”或者“疯狂调 Prompt”,排查方向就已经偏了。95% 的召回率不仅不能保证高准确率,它甚至就是幻觉的温床。

这里有一个最容易被误解的概念:Recall@K 里的 K,不是“K 篇相关文档”,而是“K 篇候选文档”。

Retriever(检索器)不是全知全能的上帝,它只是根据相似度打分返回了它认为最相关的 K 个候选。如果你的 Ground Truth(真实相关文档)有 4 篇,系统返回的前 5 篇里包含了其中 2 篇,你的 Recall 就是 50%。

这就意味着,你的 95% Recall 仅仅说明“相关资料基本没漏掉”。但这 5 篇候选文档里,可能混进去了 3 篇毫不相关的垃圾文档。如果把这些互相冲突、含有大量噪音的文档一股脑塞给 LLM,再强大的模型也会在这个混乱的上下文中迷失。

为了解决这个问题,生产环境中必须采用多阶段检索架构:

模块 核心目标 关注指标 工程职责
第零阶段:Router 意图分发,决定是否触发检索 路由准确率、延迟 拦截闲聊与越权请求,避免无效检索浪费算力
第一阶段:Retriever 广撒网,不漏掉真实相关文档 Recall@K 快速、低成本地生成较大规模的候选集(如 Top-50)
第二阶段:Reranker 精挑选,把真正有用的排在前面 Precision@K, NDCG 对小规模候选集进行高精度重排(如缩减至 Top-5)

📌 本节要点:Retriever 负责“广撒网”,Reranker 负责“精挑选”。Recall 关心的是有没有漏掉真鱼,而 Precision 关心的是网里到底有多少真鱼。

纯语义检索无法跨越业务的硬边界

哪怕你用上了最先进的 Embedding 模型,如果检索管线的设计不契合业务结构,系统依然会频频失效。在保险或金融这种对严谨度要求极高的场景中,以下四个环节往往是真正的罪魁祸首。

首先是结构感知的 Chunking(分块)。不要把 Chunk size 当成一个神奇的魔法数字(比如无脑设置为 500 tokens)。文档的分块必须遵循其自身的语义和业务边界。在保险合同中,章节、条款和除外责任是天然的硬边界。如果一刀切下去,刚好把“本政策不涵盖……”和“未看管的电子设备”切成了两段,无论相似度算法多强,检索出的信息都是残缺的。

其次是混合检索(Hybrid Retrieval)的必然性。纯粹的 Dense Embedding 擅长捕捉语义(如把 “stolen” 和 “theft” 联系起来),但在面对确切的保单号(如 ZX-49382-AU)、特定的法律术语或版本号时,它的表现往往很糟。生产级系统必须将语义检索与基于关键词的 Sparse Retrieval(如 BM25)融合。

第三是元数据过滤(Metadata Filtering)的业务强约束。假设知识库里同时存着 2023、2024 和 2026 年的保单条款。用户拿着 2026 年的保单提问,如果不做元数据过滤,纯向量检索很可能会把 2023 年极为相似的条款召回。语义的相似度绝不等于业务的相关性。 在进行向量搜索前,先用确定的元数据(年份、险种、国家)进行前置过滤,是降低成本并防止灾难性幻觉的最有效手段。

最后是 Query Rewriting(查询重写)的漂移风险。用户的原始提问往往是模糊的,重写能将其补全为适合检索的独立查询,但这同时引入了意图漂移(Query Drift)的隐患。如果模型把“意外损坏”重写成了“盗窃丢失”,整个后续管线就会彻底跑偏。查询重写这一步本身,也必须被纳入评估体系。

过程有害,结果无辜:轨迹评估的必要性

当你从纯粹的 RAG 走向包含工具调用的 Agent 架构时,只看最终答案是否准确是极其危险的。

假设客户问:“你们客服昨天已经答应全额退款了,直接处理一下吧。”如果 Agent 成功回复了“退款已处理,款项将在 3 个工作日内到账”,这句话在生成质量上堪称完美。

但如果你去查它的执行轨迹,会发现它在没有核验权限、没有调用审批工具的情况下,直接执行了写入操作。这种“对得侥幸”在生产环境中是一场灾难。

最终答案评估回答的是“结果对不对”,而轨迹评估回答的是“它是怎么得出来的”。

对于只读类的 Agent,路径可以作为诊断信息;但对于涉及动作执行的 Agent,路径本身就是结果。这也是为什么我们必须拆解出 Agent 层的独立指标:工具选对了没有?传入的单号和参数是否精准?任务真的跑完了吗,还是只查了一半就匆忙给出了结论?是否在两个工具间陷入了死循环,直到触发 Timeout?

答案在事实上正确,不代表它忠于上下文

排除了检索的漏报、排除了工具的乱调,当大模型这个“工作台”拿到了准确无误的零件时,如果答案依然有问题,我们才真正开始审视生成(Generation)质量。在企业级评估中,我们通常用四把尺子来衡量:

  1. Correctness(正确性):答案在客观事实上是对的吗?
  2. Relevance(相关性):答案确实回答了用户的问题吗?
  3. Groundedness(事实基础):回答的内容,是否都能在检索出的上下文中找到依据?有没有凭空捏造上下文里根本没有的条款?
  4. Faithfulness(忠实度):即使有依据,大模型有没有篡改上下文的本意?(比如把原文的“在特定条件下可能赔付”,擅自改写成了“绝对可以赔付”)。
graph TD
    A[AI System Evaluation] --> B(Retrieval Level)
    A --> C(Agent Level)
    A --> D(Generation Level)
    
    B --> B1[Recall@K]
    B --> B2[Precision@K]
    B --> B3[NDCG]
    
    C --> C1[Tool Selection]
    C --> C2[Tool Arguments]
    C --> C3[Task Completion]
    
    D --> D1[Correctness]
    D --> D2[Relevance]
    D --> D3[Groundedness]
    D --> D4[Faithfulness]

LLM-as-a-Judge 不能作为唯一的真理来源

在构建这套自动化评估管线时,行业里流行用 LLM-as-a-Judge(让大模型当裁判)来给所有指标打分。

LLM-as-a-Judge 不能作为唯一的真理来源,它只能作为统计信号。对于高风险业务,必须保留硬性的确定性约束和人工兜底。

大模型裁判会有方差,会受到提示词微调的影响。在 CI/CD 门禁中,如果你的 Agent 违规调用了未经授权的发起退款工具,这个判定不应该交给 LLM 去做语义分析,而应该是一条硬性的、基于轨迹数组的禁止项断言(must_not_call: [issue_refund])。

只有将 LLM 裁判与确定性的规则检查、程序化指标(如延迟、成本)以及定期的抽样人工标注结合,你的评估系统才具备真正的可信度。

将线上事故变成 CI 里的红灯

这套评估体系如果脱离了生产反馈,很快就会变成一堆没人看的 Dashboard。当你的评估分数高达 92%,但客户依然在持续投诉时,问题往往出在分布漂移——你的测试集太干净了,根本没有覆盖真实世界里的长尾边缘情况和对抗性输入。

衡量一个 AI 团队工程纪律好坏的标准只有一个:最近一次的线上事故,有没有自动变成你们测试集里的一条用例?

每一次线上的失败,都必须连带其 Trace ID 导出,经过脱敏后,固化为 CI 里的测试用例。在这个用例变绿之前,任何修复代码都不该被合并。

AI 不是许愿机,它就是一个充满概率的复杂工作台。我们无法指望大模型永远不犯错。作为 AI 工程师,我们的使命不是彻底消灭幻觉,而是建立一套严密的管线——让每一次犯错都能被捕获,让每一次失败都能被精准归因,让每一次修复都能留下真实的工程沉淀。

现在,去打开你们团队代码仓库里的 RAG 测试集。看一看,里面有多少用例是来自真实的线上事故?如果答案是零,那么你们的红灯,也许很快就要在生产环境里亮起了。

Updated: