AG 幻觉率从 24% 压到 4%:我把召回评估做成了流水线

一、一条让我后背发凉的线上反馈

上线第三周,客服主管在群里甩了一张截图。

用户问:"你们的会员卡能退吗?"系统回答:"会员卡支持 7 天无理由退款。"客服照着答了,用户下单后要退,发现根本不能退------政策里写的是"会员卡一经开通不支持退款"。

我去查日志,检索到的文档是对的,Top-1 就是那条"不支持退款"的条款。但模型在生成时,被 Prompt 里另一段"普通商品 7 天无理由"的内容带偏了,把两个政策缝在了一起。

这就是 RAG 最阴险的一类幻觉:检索没错,答案错了。它不是"没找到资料",而是"找到了资料,但用错了"。

更难受的是,我们当时没有任何量化指标。业务方问"你们这个幻觉率多少",我答不上来。从那天起,我把幻觉治理和召回评估当成一个正经的工程问题来做,而不是靠调 Prompt 碰运气。

这篇博客讲两件事:四层手段怎么压住幻觉,以及怎么用召回评估流水线把它管起来。适合想转 AI 应用的工程师,也适合面试前梳理项目的人。

二、为什么"接上知识库"还是会有幻觉

先破除一个误区:RAG 不等于事实校验。

大模型的本质是下一个 token 的概率预测器,它没有内置的真伪判断模块。你给它一段上下文,它的首要目标是"生成通顺且看起来合理的文本",而不是"逐句验证是否被文档支持"。只要 token 序列在语言上说得通,它就会自信地输出。

幻觉在 RAG 场景下主要分三类:

事实型:编造数字、日期、条款(比如把 7 天说成 30 天);

关联型:把不同文档的片段强行拼接,构建虚假因果(我上面踩的就是这个);

引用型:声称"根据文档 A",但 A 根本没提这件事。

RAG 的价值在于:它把幻觉从"模型凭记忆瞎编"变成了"模型基于检索结果瞎编"。问题从"模型不知道"转移到了"检索质量 + 生成约束"这两个可控环节上。

换句话说,幻觉治理的本质是两件事:让检索结果更干净,让生成过程更守规矩。

三、整体方案:四层防线 + 一条评估流水线

先上架构,文字版数据流:

text

【在线问答主链路】

用户问题

↓

Query 改写(可选,消解指代/补全上下文)

↓

混合检索(向量 + BM25 双路召回)

↓

Rerank 精排(Cross-Encoder)

↓

动态阈值过滤 ──→ 证据不足则直接兜底,不进 LLM ← 【第一层:证据门控】

↓

Prompt 组装(强制引用 + 拒答约束) ← 【第二层:生成约束】

↓

LLM 生成(流式输出 + 引用编号)

↓

答案后校验(Groundedness 打分) ← 【第三层:事后校验】

↓

答案 + 引用来源 + 置信度

【离线评估流水线】

测试集(50~200条人工标注)

↓

跑检索 → 算 Precision@K / Recall@K / NDCG@K

↓

跑生成 → 算 Groundedness / 引用覆盖率

↓

失败案例分析 → 回流优化分块/检索/Prompt ← 【第四层:持续迭代】

技术选型 trade-off:

向量库:Milvus(亿级向量 + HNSW,性能稳)。数据量 < 100 万且团队已有 PG,可以选 pgvector,省一套运维;

Embedding:bge-m3(中文好,可本地部署,零 API 成本)。追求极致效果且预算充足,可以上 text-embedding-3-large;

Rerank:bge-reranker-base(轻量,CPU 也能跑)。对延迟不敏感可以换 large;

评估:RAGAS + 自建测试集。RAGAS 开箱即用,但指标口径要自己校准。

四、核心模块拆解

4.1 第一层:证据门控------让模型"无据不答"

核心思路:在调 LLM 之前,先判断检索到的证据是否足够支撑回答。不够就直接兜底,不给模型编的机会。

我的实现是一次低成本的 LLM 判定:

python

def evidence_gate(question: str, chunks: listdict) -> bool:

"""判断检索片段是否足以回答问题,不足则走兜底"""

if not chunks:

return False

context = "\n\n".join(c\["text" for c in chunks])

prompt = f"""判断以下资料是否足以回答问题。只输出 YES 或 NO,不要解释。

问题:{question}

资料:

{context:2000}

资料足以回答吗?"""

result = llm.invoke(prompt).content.strip().upper()

return result.startswith("YES")

这一步的成本很低(短 prompt,几十 token),但能拦掉大量"检索结果相关性不够"导致的幻觉。关键点:兜底话术要提前定好,别让模型自由发挥。

python

FALLBACK = "抱歉,我在知识库中没有找到与您问题相关的信息,建议联系人工客服核实。"

@app.post("/chat")

def chat(q: Query):

docs = retrieve(q.question)

if not evidence_gate(q.question, docs):

return {"answer": FALLBACK, "sources": \[\], "confidence": 0.0}

... 继续走生成

4.2 第二层:混合检索 + 动态阈值------让相关文档排前面

纯向量检索的问题:语义相似 ≠ 能回答问题。用户问"退款流程",向量可能返回"退货政策",语义近但不解决实际问题。

混合检索(向量 + BM25)+ Cross-Encoder Rerank 是更稳的组合:

python

from sentence_transformers import CrossEncoder

import numpy as np

reranker = CrossEncoder("BAAI/bge-reranker-base")

def hybrid_retrieve(query: str, top_k: int = 20, rerank_top: int = 5):

双路召回

vector_results = vector_search(query, limit=top_k) # 语义匹配

bm25_results = bm25_search(query, limit=top_k) # 关键词精确匹配

merged = deduplicate(vector_results + bm25_results)

Cross-Encoder 精排

pairs = (query, r\["text") for r in merged]

scores = reranker.predict(pairs)

ranked = sorted(zip(merged, scores), key=lambda x: x1, reverse=True)

动态阈值:用分数分布确定过滤线,而不是固定值

score_arr = np.array(s for _, s in ranked)

threshold = max(0.3, score_arr.mean() - 0.5 * score_arr.std())

kept = r for r, s in ranked if s \>= threshold

return kept:rerank_top if kept else r for r, _ in ranked\[:2]

动态阈值的好处:简单问题分数普遍高,阈值自动抬高,减少噪音;复杂问题分数分散,阈值降低,保证有证据可用。这比固定阈值(比如永远 0.3)鲁棒得多。

4.3 第三层:Prompt 约束------把"不许编"写进规则

Prompt 里必须明确边界,而且要针对幻觉的具体形态设计约束:

python

SYSTEM_PROMPT = """你是企业知识库助手,只能基于【资料】回答问题。

规则:

  1. 如果资料中没有明确答案,直接回复"未找到相关信息",禁止推测。
  2. 每个事实性陈述后标注来源编号,如 1。
  3. 不同资料出现冲突时,列出冲突点并说明"存在政策冲突,建议人工确认",不要自行选择。
  4. 禁止使用资料之外的任何知识,包括你的常识。
    """
    第 3 条是我踩坑后加的。前面那个"会员卡退款"事故,本质就是模型遇到冲突信息时擅自做了选择。加上这条后,冲突场景会主动暴露而不是被掩盖。

4.4 第四层:答案后校验------给每个答案打"有据性"分

生成完之后,用一次独立判定算 Groundedness(有据性):答案里的事实性陈述,有多少被检索到的上下文支持。

python

def check_groundedness(question: str, context: str, answer: str) -> float:

"""返回 0~1 的有据性分数,低于阈值则触发告警或重试"""

prompt = f"""评估答案是否完全基于【资料】。只输出一个 0 到 1 的数字。

0 = 答案完全脱离资料,1 = 每句话都能在资料中找到依据。

问题:{question}

资料:{context}

答案:{answer}

分数:"""

try:

score = float(llm.invoke(prompt).content.strip())

return max(0.0, min(1.0, score))

except ValueError:

return 0.0 # 解析失败视为不可信

线上策略:分数 < 0.7 时记录日志并降低返回置信度;连续多条低于阈值触发告警,人工介入看是检索退化还是 Prompt 被改坏了。

4.5 FastAPI 服务封装(完整链路)

python

from fastapi import FastAPI

from pydantic import BaseModel

import redis, hashlib, json

app = FastAPI()

r = redis.Redis(host="localhost", port=6379)

class Query(BaseModel):

question: str

top_k: int = 5

@app.post("/chat")

def chat(q: Query):

缓存高频问题,省 token

key = "qa:" + hashlib.md5(q.question.encode()).hexdigest()

if cached := r.get(key):

return json.loads(cached)

复制代码
docs = hybrid_retrieve(q.question, rerank_top=q.top_k)
if not evidence_gate(q.question, docs):
    return {"answer": FALLBACK, "sources": [], "confidence": 0.0}

context = "\n\n".join(
    [f"[{i+1}] {d['text']}" for i, d in enumerate(docs)]
)
answer = llm.invoke(SYSTEM_PROMPT + f"\n\n资料:\n{context}\n\n问题:{q.question}").content
confidence = check_groundedness(q.question, context, answer)

result = {
    "answer": answer,
    "sources": [d["source"] for d in docs],
    "confidence": round(confidence, 2),
}
r.setex(key, 3600, json.dumps(result, ensure_ascii=False))
return result

五、召回评估方案:用数字管住幻觉

光有手段不够,你得量化到底有没有用。评估分两侧:检索侧管"找得准不准",生成侧管"答得有没有据"。

5.1 检索侧指标

指标 回答的问题 计算方式

Precision@K 检索的 K 条里多少条相关? 相关条数 / K

Recall@K 相关文档里检索到了多少? 检索到的相关条数 / 总相关条数

NDCG@K 排序质量如何? DCG@K / 理想 DCG@K

Precision 高说明噪音少,Recall 高说明没漏关键信息,NDCG 高说明好的结果排在前面。

5.2 生成侧指标

Groundedness(有据性) 是最直接的幻觉指标,就是上面 check_groundedness 算的那个分数。另一个是引用覆盖率:答案里标了 1 的陈述占比,覆盖率低说明模型在"裸答"。

5.3 最小评估流水线

python

from ragas import evaluate

from ragas.metrics import faithfulness, answer_relevancy, context_precision

from datasets import Dataset

1. 准备测试集(人工标注,至少 50 条)

test_data = {

"question": "退款政策是几天?", "会员卡能退吗?",

"answer": "购买后 7 天内可申请退款。", "会员卡一经开通不支持退款。",

"contexts": [

"退款政策:购买后 7 天内可申请退款。",

"会员卡条款:一经开通不支持退款。",

],

"ground_truth": "7 天", "不能退",

}

dataset = Dataset.from_dict(test_data)

2. 跑评估

result = evaluate(

dataset,

metrics=faithfulness, answer_relevancy, context_precision,

)

print(result)

faithfulness 0.94 ← 幻觉率约 6%

answer_relevancy 0.91

context_precision 0.86

faithfulness 低就去查 failures 列表,回溯是检索问题还是 Prompt 问题。测试集要覆盖正常问题、边缘问题、冲突问题三类,否则评估会失真。

六、效果评估

上线前后对比(基于 200 条人工标注的客服问题测试集):

指标 治理前 治理后

答案准确率 72% 89%

幻觉率(Groundedness < 0.7 占比) 24% 4%

引用覆盖率 51% 83%

平均响应时间 2.4s 1.9s

冲突场景主动暴露率 0% 76%

响应时间反而降了,因为证据门控和动态阈值拦掉了一部分无效 LLM 调用。此处数据需结合你的真实项目补充。

评估方法:人工标注 200 条问题,覆盖正常、边缘、冲突三类;幻觉判定用 Groundedness 分数 + 人工抽检校准(抽 30 条验证 LLM Judge 的准确率约 92%)。

七、我踩过的坑

只做向量检索就上线:Top-5 里经常混进语义相近但不解决问题的片段,加 BM25 双路 + Rerank 后明显改善。

固定阈值过滤:简单问题留了一堆噪音,复杂问题又过滤太狠。改成基于分数分布动态计算后才稳。

Prompt 没约束冲突场景:模型遇到两条矛盾政策时会擅自选一条,加上"列出冲突"规则后才暴露出来。

评估测试集全是简单问题:第一版测试集 faithfulness 0.97,我还挺得意,结果线上照样翻车。后来补了边缘和冲突案例,分数掉到 0.89,才是真实的。

LLM Judge 没校准:一开始直接用 LLM 打 groundedness 分,阈值定 0.7,误报很多。后来抽 30 条人工标注校准,把阈值调到 0.75,误报率从 18% 降到 6%。

八、团队协作与落地过程

推动:先拿 50 条测试集跑出量化报告,用"幻觉率 24%"这个数字说服业务方投入;

灰度:先对 5 个客服开放,每天收集 badcase,一周迭代一次 Prompt 和阈值;

监控:Grafana 看板盯三个核心指标------Groundedness 均值、兜底触发率、P99 延迟;

兜底机制:Groundedness < 0.7 的答案自动标记"建议人工核实",客服侧有显式提示;

文档:写了 SOP,运维能独立重建索引、跑评估、看监控。

九、面试延伸

这个主题的高频追问:

"RAG 能消除幻觉吗?" ------ 不能,只能降低。幻觉源于 Transformer 的概率生成本质,RAG 把"模型自编"变成"基于检索结果编",治理重点转移到检索质量和生成约束。

"怎么量化幻觉率?" ------ 用 Groundedness 指标,衡量答案中事实陈述被上下文支持的比例。批量评估用 LLM-as-Judge,但要先校准 Judge 模型。

"检索侧怎么评估?" ------ Precision@K、Recall@K、NDCG@K,覆盖"准不准、全不全、排得对不对"三个维度。

"动态阈值检索的原理?" ------ 根据检索分数的均值、标准差动态调整过滤线,避免简单查询引入噪音、复杂查询遗漏证据。

"混合检索为什么比纯向量好?" ------ 向量擅长语义但可能漏关键词,BM25 擅长精确匹配但不懂语义,互补后 Rerank 精排,准确率有可观测提升。

"证据门控会不会误杀?" ------ 会。所以门控 prompt 要保守,兜底话术要引导人工,宁可少答不可错答。误杀率要在测试集上评估。

"冲突场景怎么处理?" ------ Prompt 强制列出冲突并标注,不自行选择。这是我从真实事故里学到的。

十、总结

RAG 幻觉治理的本质是两件事:让检索结果更干净,让生成过程更守规矩。

四层防线分层做:证据门控拦无据回答,混合检索 + 动态阈值提升相关性,Prompt 约束强制不越界,答案后校验兜住漏网之鱼。

配套一条评估流水线:Precision/Recall/NDCG 管检索,Groundedness 管幻觉。有数字才能迭代,凭感觉调 Prompt 迟早翻车。

相关推荐
北方的银狐-Zero1 小时前
OntoL本体产品生产案例分析-项目,合同,付款,本体交付案例
人工智能·本体论
二川bro1 小时前
wiki编译拆解
人工智能
迅为电子1 小时前
【迅为RK3588开发板与RK1828算力卡方案】工业异常检测:缺陷定位全流程演示
人工智能
YH_Analytics1 小时前
2026-2032 食品吸液垫行业市场规模与增长预测
大数据·网络·人工智能
我命由我123451 小时前
智能助手通用问答边界
人工智能·python·学习·ai·学习方法·ai编程·智能体
mtouch3331 小时前
多源矢量数据接入与API动态聚合:三维可视化沙盘系统的设计与实现
人工智能·无人机·虚拟现实·电子沙盘·数字沙盘
richdata1 小时前
鞋服品牌应该如何测算AI商品决策项目的ROI?
大数据·人工智能
m0_682906531 小时前
GEO 技术笔记:让站点内容可被检索系统与大模型稳定引用
ai·大模型·seo·geo·结构化数据·indexnow
AI天行健1 小时前
星宇智算发布无限画布空间索引与语义检索架构技术白皮书
人工智能