背景痛点:幻觉在客服场景里到底长啥样?
做智能客服的同学都懂,用户问一句“我上个月订单的积分多久到账?”,大模型张嘴就来:“一般 3 个工作日”。
可公司规则写得明明白白——虚拟商品订单压根不给积分。
这种“一本正经地胡说”就是典型的幻觉:模型把训练语料里高频的“3 个工作日”当成万能答案,完全不顾订单类型、活动规则等业务事实。
幻觉在客服里一般有三种面孔:
- 事实性错误:把 A 产品的质保期套到 B 产品上。
- 逻辑矛盾:前一句说“可以退”,后一句补刀“但退不了”。
- 无中生有:用户只问“怎么开发票”,模型把“开票后 7 天不能退货”也顺带塞进去,制造额外客服量。
落到业务指标,就是首答准确率掉 8–12%、人工转接率涨 15%、用户满意度直接跌 0.3 分。
老板一句话:再不管幻觉,客服成本翻倍。于是我们把“让大模型说人话”拆成了一个纯技术工程。
技术方案:RAG、Fine-tuning 之外,还能怎么玩?
1. 路线对比
| 方案 | 幻觉抑制力度 | 开发周期 | 线上维护成本 | 备注 |
|---|---|---|---|---|
| 单纯 Prompt Engineering | ★☆☆ | 1 天 | 低 | 靠“你是一名客服助手,请依据事实回答”这种咒语,基本随缘 |
| Fine-tuning(SFT) | ★★☆ | 3–4 周 | 高 | 需要几千条高质量标注,一换产品就得重训 |
| RAG(检索增强) | ★★★ | 1–2 周 | 中 | 依赖向量库召回,仍可能“编细节” |
| RAG + 知识图谱验证 | ★★★★ | 2–3 周 | 中 | 本文主角,把“可验证”写进架构 |
结论:在已有知识库、又要快速落地的客服场景,RAG+图谱验证是 ROI 最高的组合拳。
2. 核心架构:把“说错”变成“过不了安检”
整个流程像机场安检:
对话状态机负责“人到了哪”,知识图谱验证模块负责“行李有没有违禁品”。
用户提问 ↓ 对话状态机(追踪实体、意图、已确认事实) ↓ RAG 召回候选知识片段 ↓ 知识图谱验证(实体链接→事实一致性检查→置信度打分) ↓ 【置信度 ≥ 阈值】→ 用原答案 【置信度 < 阈值】→ 触发安全回复:“让我为您确认一下……”3. 关键算法:动态置信度阈值
静态阈值一刀切,容易“误杀”或“漏杀”。
我们让阈值随对话轮次、用户情绪、业务类型漂移:
threshold(t) = base_thr − α·turn − β·emotion_score + γ·biz_weight- base_thr:默认 0.72(线上 A/B 跑出来)
- turn:已对话轮次,轮次越深越不能出错,阈值降低(更严格)
- emotion_score:用户情绪负向分数 0–1,越高越严
- biz_weight:业务敏感系数,退货退款类 +0.1
经验值:α=0.02,β=0.15,γ=0.08,在 10 万通对话上验证,幻觉率从 11.4% 降到 3.1%,平均响应只 +180 ms。
代码实现:30 行 Python 搭个“安检门”
以下代码直接拷走能跑,依赖:spaCy、RDFLib、transformers。
重点看注释,完全按 PEP8 来。
# knowledge_guard.py import spacy from rdflib import Graph, URIRef, Literal from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch # 1. 加载 3 元组图谱(主语-谓词-宾语) g = Graph() g.parse("product_kb.ttl", format="turtle") # 公司统一知识出口 # 2. 实体链接:把文本中的产品/规则提到 URI nlp = spacy.load("zh_core_web_sm") def entity_link(text): """返回 List[URIRef],简单字典匹配+NER""" ents = [] for token in nlp(text): if token.ent_type_ in ("PRODUCT", "MISC"): uri = URIRef(f"http://kb.company.com/{token.text}") ents.append(uri) return ents # 3. 事实一致性检查 def fact_check(answer: str, retrieved: list) -> bool: """如果答案陈述能在图谱找到,返回 True""" for triple in retrieved: sub, pre, obj = triple if sub + " " + pre + " " + obj in answer: # 用朴素包含判断,生产可换 SPARQL if (sub, pre, obj) not in g: return False return True # 4. 置信度计算 model_name = "bert-base-chinese-finetuned-sst" tok = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) def confidence(answer: str, question: str) -> float: """把[question+answer]喂给 NLI 模型,取 entailment prob""" inputs = tok(question, answer, return_tensors="pt") with torch.no_grad(): logits = model(**inputs).logits # 0=矛盾 1=中立 2=蕴含 entail_prob = torch.softmax(logits, dim=1)[0, 2].item() return round(entail_prob, 3)使用示例:
ans = "虚拟商品订单不支持积分到账" ret = [("虚拟商品", "支持积分", "False")] if fact_check(ans, ret) and confidence(ans, q) >= threshold(turn=2): send_to_user(ans) else: send_safe_template()生产考量:延迟、内存、异常一个都不能少
响应延迟与准确率平衡
图谱验证走本地 RDFLib,10 万三元组全放内存,延迟 P99 < 50 ms;
置信度模型用 ONNX 量化,GPU 推理 20 ms;
整体链路 250 ms 内,比纯 RAG 只多 80 ms,业务可接受。对话上下文内存优化
状态机只保留实体槽位+已验事实 ID,用 Redis list,每轮过期时间 15 min;
对话超过 20 轮自动截断,防止长文本把显存吃爆。异常流处理
- 图谱查询超时 → 降级到 RAG 分数,阈值 +0.05
- 置信度模型异常 → 直接返回安全回复,埋点告警
- 用户连续两次追问“你确定?”→ 强制转人工,避免死循环
避坑指南:三次上线踩出来的血书
知识图谱版本不一致
商品运营改规则后,TTL 文件凌晨发布,缓存没热,模型按旧数据说“能退”,结果早上 9 点投诉潮。
→ 上线版本号校验:答案里引用三元组必须带kb_version,与当前在线版本比对,差一位就降级。多轮状态泄漏
用户 A 把“订单号”告诉机器人,下一位用户 B 进来,状态机没清,直接说“您的订单 123×× 正在退款”。
→ 用会话级 Redis key,前缀session_id,用户退出立即DEL。置信度阈值“拍脑袋”
初期统一 0.8,结果把大量正确回答也拦下,人工量暴涨。
→ 上线前跑一周影子模式,记录“如果阈值=x 会拦截多少”,画 ROC 找业务可接受误杀点,再定 base_thr。
效果复盘与还能怎么卷?
上线四周后,核心指标:
- 幻觉率:11.4% → 3.1%
- 人工转接:−18%
- 首响时长:+0.18 s(在 1.2 s 基线上几乎无感)
- 研发人效:知识库更新周期从 2 周缩到 3 天,无需重训模型
下一步,我们准备把用户反馈信号(点踩/点赞)接进强化学习,做动态阈值 online RL;
同时尝试用beam search + attention masking把“不可信实体”在解码阶段直接屏蔽,减少“安检”后处理压力。
开放问题:幻觉 A/B 测试怎么做?
传统 A/B 看转化率就行,但幻觉是内容质量指标,需要事实正确性标注。
如果让你设计,你会:
- 如何把用户“沉默成本”纳入指标?
- 实时标注用规则+人工混合,预算怎么砍?
- 多业务混排,样本不均衡咋解决?
欢迎留言聊聊你的脑洞,一起把“让大模型说真话”做成可持续的工程实践。