去年我做过一个问答系统模型跑得很欢结果一查10个回答里有两三个在“一本正经地胡扯”。让人头疼的地方在于它编出来的内容比真实答案还流畅甚至带着标点符号的自信。当时团队的反应是继续用大模型判官来审大模型可试了一轮下来发现这个思路不太对味——成本高不说判官本身也会有自己的幻觉结果就是两模型互相“客套”错版内容就这么糊弄过去了。后来换了一条路尝试用几何方法检测AI幻觉效果反而出乎意料。不依赖第二个LLM评判纯靠向量空间里的距离和几何结构来判断文本是否可信下面把这段实操经验好好拆一拆。这套方案的适用范围很明确凡是基于RAG知识库问答、需要溯源校验、或者对模型输出做二次审核的场景都非常合适。对普通开发者来说它不需要昂贵的模型调用只依赖嵌入模型和向量运算对做AI应用落地的人而言这是成本最低的一道防线。我也会讲清楚它的局限帮大家判断什么时候适合、什么时候还是得老老实实上人工。1. 为什么不用LLM当判官成本和可靠性双输1.1 大模型判官的本质困境先说一个反直觉的事实用一个更大的模型判定另一个模型的输出是否正确本质上是在拿一个概率系统去审核另一个概率系统。这里头不只是钱的问题而是逻辑上就有漏洞。大模型在回答“这段文本有没有幻觉”时其实也是在生成文本它没有在“验证事实”而是在“预测一个合理的评价”——这两件事的区别非常关键。我之前在某平台做过一组评测拿GPT级别的模型当判官对50条问答结果做事实性审核结果它把3条明显内容错误、出处错位的回答标成了“信息正确”理由是“语义上保持一致”。原因很直接判官模型被回答文本的高度流畅性带偏了它在语义空间里看到的是两个有相似风格的文本而不是“事实上的严谨匹配”。大模型判官擅长判断“逻辑是否通顺”“语气是否合理”但在判断“这东西是否对应到真实世界事实”时并不可靠。1.2 成本、延迟和置信度的三重打击预算角度算一笔账就明白了。假设每天有10000条用户问题每条回答约1500字如果全部交给一个强模型判官审核输出token约为1500乘以10000那一千五百万输出token按市面主流模型的计价一天光是判官成本就要几十块钱到几百块钱具体数值取决于选择的模型。这还是在输入文本也计入成本的前提下做的粗略估算。而且每次额外调用都引入一次新的网络延迟交互体验直接打折扣。更要命的是置信度不可控。判官模型给出的“可信度打分”本身就是一个生成值不是经过校准的概率值它跟真实置信度之间没有稳定的映射关系。也就是说判官可能因为某种风格倾向对同样的内容给出不同分数。测试过一个极端场景把回答里几句话的顺序打乱内容完全一样判官给的分数居然明显降低这意味着它评的根本不是“事实性”而是“文本结构的舒适度”。这个发现让我彻底放弃了LLM评判这条路。1.3 几何方法为何能绕开这些问题几何方法的核心立场完全不同。它不生成评价文本只计算向量之间的距离。向量是什么简单理解就是把一句话转换成一个高维空间里的坐标点。语义越接近两个点的几何距离就越近。如果模型输出的内容真是基于某份参考文档生成的那输出向量应该落在参考文档向量附近如果内容是凭空拼接的那它在空间里大概率会“漂”到离所有参考向量都很远的位置或者在几个不相关的语义区域之间来回摆动。这个判断逻辑绕开了文本生成过程不涉及概率预测只涉及数学运算。因为是确定性的同样的输入必然得到同样的结果不会像大模型评判那样每次给出的分数都不同。实际计算成本也低得多用一个中等规模的嵌入模型跑一次向量化再算个余弦相似度耗时几十毫秒成本可以忽略不计。从可靠性和性价比两个角度看几何方法在“基于参考事实的幻觉检测”这个细分场景里是比LLM评判更务实的选择。几何方法适合判断“答案是否偏离了参考事实”不适合判断“答案在人类眼里是否优质”。这两个目标完全不同别混淆。2. 几何检测的核心原理把“幻觉”翻译成空间里的距离问题2.1 嵌入空间的几何直觉要理解这套方法先得接受一个观念文本是可以被“定位”的。传统的文本比较看字面重叠比如“AI是什么”和“人工智能是什么”字面上看起来差很多但语义上一回事。嵌入模型做的就是这件事把包含相同含义、不同字面的文本映射到空间里相近的位置。想象一下整个空间是一张超大地图每一个句子、段落都是上面的一个点。描述机器学习模型的文本聚集在一个区域描述篮球比赛规则的文本聚集在另一个区域中间隔得很远。幻觉检测的几何直觉就在于如果模型是忠实基于某篇文章生成答案的那答案的向量应该落在该文章相关的位置如果模型把从不同文章里摘出来的信息强行缝合在一起那这个向量就会出现在若干个区域的“真空地带”周围没有任何参考点支撑它。这种情况下检测幻觉就变成了两道简单的几何题第一答案离它声称引用的参考材料距离有多远第二答案附近有没有足够多的“邻居”来证明它是从可靠语料里长出来的前者是单点距离问题后者是局部密度问题。2.2 关键指标余弦相似度、欧氏距离和局部密度实操层面常用三个几何指标各有各的脾气。余弦相似度是最常用的。它只关心向量之间的夹角不关心向量的长度适合比较文本语义方向是否一致。比如参考文本说的是“模型在训练中过拟合”而回答说的是“模型在训练中表现良好”这两条向量之间的夹角可能很大余弦值就会很低提示内容产生了明显偏移。实际测试中语义相近文本的余弦相似度通常在0.85以上部分同义改写可以到0.9以上而幻觉输出与参考材料的余弦值可能掉到0.55到0.7区间。欧氏距离对向量的绝对位置和长度敏感。同一个语义内容被写成精简版和详细版两个向量的长度可能不同欧氏距离会偏大但余弦值可能依然很高。这提醒一个重点欧氏距离适合用来判断“局部密度”的问题——比如这个点附近有没有邻居而不适合单纯用来衡量语义差异。局部密度是一个自查信号。把答案向量放到空间里以它为圆心画一个半径R的球数数里面落进了多少个来自参考语料的点。如果参考语料足够丰富忠实答案周围应该有不少邻居而幻觉答案通常落在一个“语言孤岛”上周围稀疏得很。实测中忠实回答的局部密度往往是幻觉回答的3到10倍这个差距非常可观。2.3 降维可视化人眼直接看幻觉有一个提升效率的技巧把高维向量用降维算法压到二维平面进行可视化。虽然降维会丢失一些信息但大致的空间结构还是保留着的。实际操作中会发现正常的忠实回答会落在参考文档聚类附近形成明显的聚集带而幻觉回答会散落在各个簇之间的空白区域像一颗一颗的孤星。这种可视化手段唯一的价值在于快速建立直觉——当肉眼能直观看到“远离任何聚类”的点就自然理解了为什么几何距离能用来替代人工审核。不过要注意降维图只能作为辅助工具来建立认知不能作为自动检测的判据。自动检测必须用原始高维空间的距离和密度因为降维过程本身会扭曲一些局部结构可能把原本很近的点拉开也可能把远的点挤到一起造成误判。一个深刻的体会做这个方向不要先急着做复杂模型先把可视化做好把点画出来一眼看明白哪些点在聚类内、哪些点漂在外面后面做阈值设定会顺利很多。2.4 和统计不确定性方法的核心差异目前业内还有一种思路是通过多次采样看文本之间的不一致性来判断幻觉比如重复问同一个问题多次如果几次结果在语义上差异很大就判断为幻觉风险。这个方法在“模型对答案不确定”的场景下效果尚可但它有个明显的盲区如果模型每次都用同一种非常有信心但错误的方式回答问题多次采样得到的高度一致的结果会被误判为“稳定可靠”。几何方法跟它互补的地方在于几何方法是对比“输出”和“参考事实”的相对位置不是对比“输出”自身的不一致性。即使模型两次输出完全一样只要这个输出漂在远离参考语料的区域照样能被标记出来。说白了稳定性检验管的是自洽性几何方法管的是事实锚定性。实际项目中我更推荐在关键场景下两者结合先用几何距离法筛一遍内容再用多次采样法作为补充信号判断“高水平编造”的情况这样兼顾了精度和召回。3. 实操打造一个轻量的几何幻觉检测管道3.1 选型与环境准备先说说工具选型。嵌入模型优先考虑中等体量、通用性强的开源模型一方面控制成本另一方面保证语义理解的覆盖面。我用过几款主流的通用嵌入模型在事实型问答场景里语义区分度都稳定没有明显高下之分。核心考量点是向量维度维度太低如128维可能损失语义细节维度太高如3072维或更高又会增加计算和存储压力。折中区间在768到1536维之间工程上最顺手。选择“768维”的另一个原因是和常见向量数据库、余弦相似度计算库的兼容性最好不容易遇到后续扩展的问题。开发环境方面没有特别要求Python 3.9以上就可以核心依赖就两个Sentence Transformers负责把文本转成向量和NumPy负责计算度量。如果语料量大到几十万条建议加一个向量索引库不过对于刚开始验证的几万条数据来说暴力计算内积矩阵反而更快没必要过早引入重型组件。3.2 参考语料池的构建策略这是一个决定成败的环节值得多花时间。要检测“模型是否幻觉”首先要定义“什么才是事实”。参考语料池就是事实库它由可靠文档切片构成质量比数量重要。当时我们项目里用到一份约5000条的知识库文档分成了20000多个切块。切片时需要把握好度切得太粗比如整篇文档一块导致每一块都包含多个语义点答案向量跟整块文档的距离会失真切得太细比如一句话一块又会导致缺乏上下文向量表示的语义不够完整。实践中最稳的切片策略是按段落或按语义自然段切然后用滑动窗口做重叠补充保证每个切块在150到300字左右。嵌入模型是按整段语义建模的切块内部内容越纯粹向量越有指向性。如果切块同时包含“模型结构”“训练数据”“性能评测”三个话题那这个向量指向的是一个“混乱混合方向”用它做距离比对会出现严重失真。做索引之前可以抽检几个切块向量化后看看它们之间的相似度矩阵——如果随机两个切块的相似度都超过0.9说明切得太粗需要细分。3.3 核心检测逻辑的代码实现下面展示一套可直接跑的检测核心逻辑。它做的事情是把模型生成的回答向量化在参考语料池里找最相似的K个切块然后计算答案向量到这些切块的距离分数和局部密度分数。import numpy as np from sentence_transformers import SentenceTransformer # 加载嵌入模型选择合适的维度 model SentenceTransformer(某个通用嵌入模型, truncate_dim768) def encode_texts(texts, batch_size64): return model.encode(texts, batch_sizebatch_size, normalize_embeddingsTrue) def find_nearest_chunks(query_vec, corpus_vecs, top_k5): # 余弦相似度 归一化向量的内积 scores corpus_vecs query_vec idx np.argsort(scores)[::-1][:top_k] return idx, scores[idx] def compute_hallucination_score(query_text, ref_chunks, corpus_vecs, top_k5): query_vec encode_texts([query_text])[0] idx, scores find_nearest_chunks(query_vec, corpus_vecs, top_k) # 指标1与最相关参考块的余弦相似度 max_sim float(scores[0]) # 指标2带权重加权相似度避免只盯着单一参考块 weights np.exp(scores - scores[0]) # 相对相似度的指数权重 weighted_sim float(np.sum(scores * weights) / np.sum(weights)) # 指标3局部密度球半径设为0.25余弦空间近似 radius 0.25 # 归一化向量之间的欧氏距离在[0, 2]区间换算成cosine距离需要做变换 cosine_dists 1.0 - (corpus_vecs query_vec) density_count int(np.sum(cosine_dists radius)) return { max_similarity: max_sim, weighted_similarity: weighted_sim, density_count: density_count, nearest_ref: ref_chunks[idx[0]], # 最近参考切片文本 similarities: [float(s) for s in scores] }这段代码的逻辑顺序值得逐行解释一下。normalize_embeddingsTrue意味着所有向量都是单位向量这时候余弦相似度就是内积运算速度非常快。在实际执行时用矩阵乘来替代对每个样本循环算相似度是对大数据量的基本尊重。compute_hallucination_score函数里的top_k5并不是随意定的而是根据经验问答案通常与单一参考块有较高相似度、与其它块相似度急剧下降如果答案出现跨块缝合top_k之间的分数差异会很小这是识别缝合型幻觉的重要信号。至于违规词“半径设为0.25”在单位归一化的向量空间里这个值实际表示余弦距离小于0.25约等于最小余弦相似度0.75密度计数会数出在这个范围内有多少参考切块靠近答案向量。这个数值偏严格实测下来能有效区分聚类内和孤点型输出。如果参考语料非常稀疏0.25半径内可能一个邻居都找不到那说明这个语料池本身密度不足以支撑几何判断。3.4 阈值标定找到适合你的“幻觉分界线”代码只是计算了指标最终落地时必须把指标映射成“是否有幻觉”的判定。阈值标定比大多数人想得更讲究必须用一批已知答案用户反馈、人工复核过来校准而不是拍脑袋定。我的做法是准备100条已确认无幻觉的正常回答以及100条已确认有幻觉的错误回答分别计算出它们各自的max_similarity和density_count分布然后找到让F1分数最大化的那个阈值点。以当时某项目的数据为例正常回答中90%以上max_similarity大于等于0.82幻觉回答中80%以上max_similarity低于0.75所以0.78就成为一个分水岭。这里有个容易踩的坑阈值不能提前拍死因为嵌入模型的更新会让分布发生漂移。每次更换嵌入模型版本必须重新校准阈值。一套一次性成型的检测方案不重新校准上线之后幻觉召回率从90%掉到70%的情况我自己就遇到过。简单校准代码from sklearn.metrics import f1_score def calibrate_threshold(normal_scores, hallucinated_scores): thresholds np.arange(0.60, 0.95, 0.01) best_thres None best_f1 0 for thres in thresholds: y_pred [1 if s thres else 0 for s in normal_scores hallucinated_scores] y_true [1] * len(normal_scores) [0] * len(hallucinated_scores) f1 f1_score(y_true, y_pred) if f1 best_f1: best_f1 f1 best_thres thres return best_thres, best_f1这段代码里前缀是反直觉的需要仔细想一下这里的目的是给每个回答打一个“是否属于幻觉风险”而normal_scores是不应该有问题的正常回答它的similarity通常偏高hallucinated_scores是错误回答它的similarity通常偏低。所以标记时s thres认为是伪造。但要注意这个y_pred定义在批量上是有顺序的先是100条正常回答后是100条幻觉回答所以y_pred的预测顺序必须和y_true的真实顺序一致代码里用normal_scores hallucinated_scores保持了这一点。别看这一个小细节写错顺序会导致阈值校准完全失效。3.5 从离线验证到在线接入的平滑过渡离线阶段验证好阈值之后可以把它封装成一个微服务接口供问答系统在运行时调用。接口传入的是用户的原始问题、RAG检索到的参考切片以及模型生成的最终回答返回的是一个包含几何指标和判定结果的字典。关键点在于必须在问答链路里结合参考来源等信息一起做检测——不能只把最终答案扔进接口因为如果没有来源切片的上下文就无法评判答案是否“偏离了参考事实”。实时测评中这个检测过程的耗时非常稳定回答向量化大约6到15毫秒和5000条参考切片做内积大约2到5毫秒总耗时在20毫秒以内。从用户视角看几乎无感。相比之下调用一次LLM判官的耗时普遍在300到600毫秒不等还要面临输出不稳定导致的重试。单从时效性体验来说几何方法就已经赢了一大截。4. 实际场景中的问题排查与边界处理4.1 幻觉“逃逸”模式一同义改写导致检测失灵有一种幻觉形式会刻意绕过几何检测模型把某段真实内容换一种说法输出让人看起来像幻觉但语义上的相似度依然很高。比如参考文本是“模型采用注意力机制来处理序列依赖”模型输出却是“序列之间的依赖关系是通过注意力模块来实现的”。这两条内容在语义空间里几乎重叠余弦相似度可能达到0.9。但个别情况下模型会在事实上发生悄悄偏移比如“模型在训练时用了注意力”变成“模型天生只依赖注意力”这个性质发生了质变可是语句结构高度相近向量距离也并没有明显拉开。处理这个问题的关键在于做细粒度实体比对。几何方法输出“最近参考块”之后要继续做一层实体抽取与比对把文本中的关键实体、数值、否定词、因果连接词单独拎出来做硬匹配。软语义距离负责衡量“气味对不对”硬实体匹配负责排查“事实元素有没有被篡改”。两种方法搭配起来才算是完整的防线。4.2 幻觉“逃逸”模式二合理但无依据的“常识化编造”还有一种更难处理的场景模型输出的内容虽然没在参考语料里出现但听起来像是常识。比如参考语料里写了“某模型的训练集包含互联网数据”模型延伸出一条“该模型因此具备较强的常识推理能力”——这句话本身听起来合理却在参考语料里找不到直接证据。用纯几何方法看由于常识推理内容和训练集相关的向量靠得很近距离检测很可能判定它“合规”。处理这个问题的思路是引入“反向来源验证”不仅仅看输出与参考库的距离还看输出中的直接断言能否在参考库中找到对应的高相似度支撑句。如果答案中有一半以上的语义单元在参考库里根本没有“锚点”就应该触发告警转给人工复核或要求模型重新生成。可以把这一条设计为“支撑率”指标定义为“答案中的核心语义片段能够在参考库中匹配到高相似度切片的比例”。支撑率低于一定阈值时即使距离指标正常也判定为“低证据置信度”。4.3 参考语料质量对检测效果的制约几何方法的能力上限取决于参考语料的品质。如果参考语料本身内容稀疏、文档重复度高、有大量相互矛盾的表述几何检测的效果就会崩坏。最典型的问题是重复文档污染语料库里同一份文件被切出几十个相似的文本块答案向量无论怎么落附近都能找到几个来自污染源的邻居局部密度指标直接失效。做法上建库之前先对切块做去重计算全库切块间的相似度矩阵把相似度超过0.9的切块合并或移除。这样可以消除大量冗余向量对密度计算的干扰。另一个要注意的是参考语料本身的事实一致性如果知识库本身就是错误信息的混合体几何距离检测会把“忠实于错误语料的回答”判定为“无幻觉”这是原则性问题。因此上线前人工抽检参考语料的事实可靠性这一步省不得。4.4 实时系统的性能毛刺问题在部分低配机器上遇到过几个性能拦截点。一是嵌入模型首次加载耗时较长需要预热处理二是大语料库的内积计算在纯Python环境下容易成为新瓶颈。建议的做法是启动时做一次全量向量预计算放内存里用NumPy矩阵存储这样运行时查询不走前处理链路。实测一套5000条语料的系统全部向量矩阵的存储大约为5000乘以768乘以4字节即约15MB内存占用可以忽略不计。另一个工程细节内积计算要尽可能用矩阵运算不要写for循环也不要为了“看起来清晰”把数据切碎再拼回去。矩阵运算本身由底层BLAS优化对10万条向量做一次内积只需几十毫秒。但循环逐条调用就会慢几个数量级这差别在低配置生产环境里非常明显。4.5 常见问题速查表问题表现可能原因处理建议正常回答被判幻觉误报率高阈值设置过严或参考语料切块过粗扩大参考切块的分块精度重新校准阈值幻觉回答未被识别漏报率高top_k过小导致只看最近块忽略跨块缝合特征将top_k提升到10并检查top_k分数分布方差局部密度指标完全无区分度参考语料被污染重复文本过多执行全库去重清除相似度超过0.9的切块向量化耗时突然变长模型未预热或者意外走了CPU推理启动时预热一次嵌入模型常驻显存改写了同义表达但漏掉事实偏移纯距离法无法捕捉语义等价的微偏配合实体抽取和支撑率检测共同判定不同日期的同一样本判定结果不一致嵌入模型版本被隐式更新锁定模型版本并保存向量矩阵避免变动5. 场景适配与扩展什么时候该用几何方法什么时候该放手5.1 最适配的场景RAG链路的事后校验几何方法最适合的定位是“RAG链路的事后校验员”。在检索增强生成链路中输入有明确的参考文档集合输出必须严格锚定这些文档。这时候用几何方法是最顺手的先把用户问题连同参考文档一起取得让模型基于这些文档生成答案再把答案向量化后与参考文档集合做距离检测就能有效抓取模型“即兴发挥”的情况。在这个场景里几何方法的优势会发挥到最大因为参考库是明确且有边界的输出内容理应在这片区域内。一旦越界大概率是模型动用了自身参数记忆开始自由创作幻觉风险评估的合理置信度就会升高。做过一组对比实验在RAG问答下加入几何检测后1000条人工标注样例中幻觉占比从28.3%降到了4.1%。这绝对不是一个微不足道的提升而是直接让人工复核工作量大幅下降。5.2 不太适配的场景无参考语料的开放问答如果模型是在自由对话、无指定来源的情况下生成内容几何方法就会陷入被动没有参考向量判断距离就无从谈起。此时可以用替代方案比如多次采样敦促模型从不同角度回答问题对结果做语义聚类聚类分布越散表示模型对答案越不确定——这属于统计不确定性方法也是替代方案之一。但如前面所述它解决不了“稳定编造”的问题。对于这类场景我的建议是不要强行套用几何检测而是回到业务本身去设计校验方案。比如金融客服场景对涉及金额、日期的回答单独抽取出来与业务库做硬匹配比如医疗建议场景对症状描述与标准医学知识库做实体链路匹配。总之几何方法在地基清晰的场景里才发挥价值不要指望它万能。5.3 轻量替代方案归一化注意力对齐如果团队觉得嵌入模型太重还有一条更轻量的路线使用编码器模型的注意力矩阵做对齐检查。本质上是先让模型对问题和参考文档分别计算注意力分布然后检查输出答案的注意力是否集中在参考文档的相关片段上。但这个方案依赖模型内部结构改动成本高不如嵌入模型灵活。实际项目里只有需要严格控制额外资源消耗的场景我才会优先考虑这个方向。5.4 更进一步的扩展跨语言事实校验一个值得关注的衍生用法是跨语言事实校验。用多语言嵌入模型把中文和英文参考文档映射到同一个向量空间可以检查一段中文回答是否与英文原始资料在语义上保持一致。在全球化项目里经常存在多语言内容对不齐、翻译内容偏离原始事实的问题这种场景下几何检测可以迅速定位“翻译幻觉”节省大量人工校对时间。实操时有一点要提醒多语言嵌入模型的质量参差不齐在正式用之前先抽一小批双语对照样本手动看它们的余弦相似度分布是否与预期一致。如果某些语义一致的翻译对相似度还低于0.75说明这个嵌入模型在对应语言对上的表现不达标需要寻找更合适的多语言模型。6. 我的反思与实际体会几何方法不是银弹但它是性价比之王做了一段时间的几何幻觉检测之后我的总体判断是它不是万能的但在它适合的场景里它是目前所有方案中投入产出比最高的一个。首先它让幻觉检测从“昂贵且不可复现”变成了“廉价且完全可复现”。同样的输入在任何时刻运行拿到的都是同样的向量、同样的分数、同样的判定结果。这个特性对生产环境来说尤为珍贵。在需要审计的场景比如金融报告审查你能随时拿出“为什么判它为幻觉”的完整数值证据这件事本身就是LLM判官做不到的。其次它迫使团队重新审视“什么是幻觉”——从语义几何角度看幻觉本质上就是“语义位置漂移”是文本在空间里离开了它应有的锚点。这个理解虽然简化了认知的复杂性但确实带来了一个非常清爽的工程框架。团队可以围绕“距离阈值”“局部密度”“支撑率”这三个简单指标来做大量的优化而不再需要层层叠叠的提示词工程和样本调优。最后几何方法其实还给了一个“信任度量表”。当你可以用一个轻量函数在客户端毫秒级判断一次回答是高置信还是低置信时你就不必对所有回答一视同仁地支出昂贵的审核成本。高置信回答直接放行低置信回答进入人工复核队列整个系统的资源使用效率完全换了一个思路。这个实践方案虽然不是银弹但在大多数RAG应用里它确实是最值得先上的那把枪。