ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RAG准入控制实战:Jev判断引擎如何拦截脏chunk、降低幻觉

RAG准入控制实战:Jev判断引擎如何拦截脏chunk、降低幻觉 最近两个月我一直在收拾自家 RAG 系统的烂摊子。检索结果五花八门大模型每次都能用一本正经的语气把错误信息讲得头头是道。后来我给整套流程加了一道名叫 Jev 的闸门情况才有根本性好转。Jev 是我们内部的代号全称是 Judgment Engine for Validation一个专门负责“检索内容准入控制”的判断引擎。这篇文章记录我从设计到上线的完整过程不写花活只讲实际操作适合正在做 RAG 落地、被召回质量和幻觉问题折磨的工程师参考。1. RAG 的瓶颈到底卡在哪我先拆了几个真实案例1.1 检索质量差不是因为向量模型不够好最开始我也把问题归结为 embedding 模型不给力前前后后换过好几个号称更强的向量模型。不能说没效果但远没到解决根本问题的程度。举个线上最常出现的例子用户问“这笔订单能不能退”检索回来的 top5 里面有 3 个 chunk 是“热门商品介绍”因为语义向量把“退”和“商品退款”混在了一起把“能不能退”理解成了“退款相关的问题”。这种情况在业内有个指标叫 hit rate就是 top-N 检索结果里真正包含正确答案的比例。我当时的 hit rate 低得没眼看。后来把日志翻了个底朝天才发现问题根子根本不是向量模型而是文本拆解太粗糙。知识库里的 PDF 有标题、页眉、表格、图片说明全被不分青红皂白地切成一段段连续字符。表格只剩半行列表被腰斩图片下面的文字注释单独成了 chunk这种数据喂给再好的 embedding 也是白搭。所以我认为RAG 的检索质量差第一步要排查的不是模型而是知识库的前处理流程。很多人问“有没有本地的 rag 文本拆解工具”我的建议是别迷信一个工具通吃所有格式必须针对你的文档类型单独调。1.2 生成端扛了太多不该扛的锅就算 top5 里面有一个正确答案大模型也会被另外四个不相关内容带偏。我有一次测试售后知识库两个 chunk 分别来自不同版本的退款政策一个说“7 天内可退”另一个说“15 天内可退”模型最后直接随机挑了一个还煞有介事地补了一句“根据最新政策”。这不是生成模型的问题是喂进去的上下文本身有毒。市面上的常见方案是加一层 rerank 模型但这只能把相关的内容排到前面并不会把不合格的内容淘汰掉。Top-k 截断也只是在数量上做限制解决不了“chunk 之间互相矛盾”的问题。我踩过这个坑之后想明白一件事RAG 缺的不是排序而是一道准入控制。生成端扛不住脏上下文那就应该在上下文进入模型前先把不可信的东西拦在门外。1.3 Jev 闸门的定位它到底管什么我最终的方案是给 RAG 装一道叫 Jev 的闸门。它不负责召回也不负责生成只做一件事判断一段检索内容有没有资格进入大模型的上下文窗口。打个比方rerank 是推荐官把所有候选人按匹配度排个序Jev 是门卫直接拦下不够格的让后排的顶上来。Jev 的评分不是单一分数而是从三个维度看相关性即这段内容和问题到底有没有关系一致性即这段内容和已知知识、和其他 chunk 是否矛盾安全性即是否包含有害、诱导、违规的内容。这三个维度组合起来才能挡住我在线上看到的各种花式翻车。这套设计的最大好处是把失败的边界控制住了。以前 RAG 回答得好不好全看大模型临场发挥现在不合格的 chunk 进不了上下文模型要么拒绝回答要么只基于可信内容作答。哪怕最终效果不完美至少不会“一本正经胡说八道”。2. Jev 的核心设计与模型实现2.1 三维评分机制拆解先说相关性。这个维度不是简单算一下 query 和 chunk 的语义相似度就行的。比如用户问“iPhone 15 电池”检索回来的 chunk 里全是“iPhone 15”的外观介绍语义向量觉得很接近但实际没有回答电池问题。所以我在相关性里加入了实体对齐要求。判断的关键不只是“像不像”而是“需要提到的实体是否真的出现了”。一致性是我认为最值钱的一个维度。我基于业务建了一个轻量本体比如“订单”这个概念应该包含订单号、状态、操作时间等字段。如果 query 期望的是订单信息而回收回来的 chunk 全是商品营销内容一致性分就会被打得很低。同时 Jev 还会对比同一批检索结果里不同 chunk 之间是否有矛盾字段。比如一个说“包邮”另一个说“满 99 包邮”这就是冲突信号。安全性维度则是规则加模型双保险。规则负责直接拦截明显违规词模型负责抓那些绕弯子的表达。举个例子有些用户 query 本身是安全的但检索回来的文档里夹带私货这种内容不在规则词表里就得靠模型在隐晦表达上做判断。三维度加权成一个总分表达式是JevScore α * Relevance β * Consistency γ * Safety我实战调下来常用的权重是 α0.5β0.3γ0.2。阈值则看业务严格场景设 0.85普通问答场景 0.75 就够。你不一定照抄这个值但拆维度这件事一定要做不然后续排查问题会非常痛苦。2.2 为什么选择本地小模型而不是大模型调用最初我也犹豫过要不要直接用一个大模型来当裁判。但算了一笔账就放弃了。一次查询如果检索 20 个 chunk每个 chunk 都要调用一次大模型判断延迟从几百毫秒直接飙到几十秒费用更是按调用次数爆炸。RAG 本来就是高并发场景这个方案完全不可行。后来我选择了本地部署一个小型编码器模型作为 Jev 的底座百 MB 级别用 ONNX Runtime 在 CPU 上就能跑单条前向传播基本在 20 毫秒左右。文档数据完全不出内网隐私问题也顺带解决了。很多人问“jev windows 部署”怎么做其实没那么玄乎一个 Python 环境加一个 ONNX 模型文件就能跑起来。训练方面我用的基座是 6 层 Transformer 编码器输出三个维度得分。训练集从真实 RAG 日志里抽样人工标注相关性、一致性、安全性三个标签正负样本比例控制在 1:2 左右。训练目标用多任务学习三个维度分别算损失最后加权合并。冷启动阶段 500 条人工标注就能出一个能用的初版积累到 3000 条质量就比较稳定了。所谓“Jev 模型”本质上就是一个垂直场景的裁判模型没必要把它神化。2.3 与 RAG 流水线的三种接法Jev 可以放在流水线的不同位置效果不一样。最推荐的是接在检索之后、构建上下文之前这是标准玩法。先从向量库或 BM25 召回一批候选 chunkJev 逐一打分低于阈值的直接淘汰剩下合格的内容再拼进 prompt。这样模型看到的上下文是经过筛选的污染问题基本解决。第二种接在生成之后对最终答案再做一次校验。把模型输出和参考 chunk 交给 Jev 检查如果发现输出里的关键信息在参考 chunk 里找不到依据就触发一次重试。这个方案适合对确定性要求极高的业务比如金融、医疗。缺点是多一次推理开销延迟会增加。第三种接在知识库写入时。入库之前先对每条要写入的 chunk 做一次质检质量差的直接拦掉。这属于前置治理能减少后续检索端的压力。我的经验是先做检索后接入跑通再按需增加生成后校验和写入时校验不要一上来就三层全上。3. 从设计到上线的完整实操记录3.1 第一步定义你的“合格 chunk”标准上线前最重要的一件事不是写模型而是先明确什么叫“合格 chunk”。我以自己做的一个产品知识库为例列了五条硬性规则每个 chunk 必须是一个完整的语义单元。FAQ 问答对必须问题答案成对出现不能只留问题。每个 chunk 必须包含至少一个可标识实体比如产品名、订单号、优惠券 ID。表格内容不能被拆碎。一个表格至少要保留表头加两行数据不然“价格99”这种碎片毫无意义。chunk 与 query 的实体类型必须匹配。问“售后”的内容不能是商品介绍。同批检索结果中两个 chunk 不能出现互相矛盾的关键业务字段。这一步最难但也最值钱。你会发现很多向量检索和重排阶段无法彻底解决的问题在准入规则里就提前消灭了。而且这些规则不是拍脑袋定的是在把线上坏案例看了一圈之后总结出来的。3.2 第二步构造训练集与冷启动训练集的第一来源是线上 RAG 日志。我把用户真实 query 和对应的检索结果捞出来随机抽样交给人工打标。每一条都按相关性、一致性、安全性三个维度分别打。这里有个注意点不要只打正样本和负样本要记清楚到底哪个维度出了问题后面调权重才知道往哪边调。冷启动阶段我还用大模型辅助生成了一批 bad case。具体做法是拿真实 query让大模型故意写一些看起来相关但实际没答到点上的检索片段。但这里必须提个醒大模型造的 bad case 会有明显的模式化倾向比如喜欢用“我们知道”这种开头必须混入线上真实 case 一起训练不然 Jev 会被养出偏见。数据量上我建议不要一开始就追求上万条。先人工标 500 条让 Jev 跑起来看误杀率再针对痛点补样本。500 条够起步3000 条质量明显稳定再往后就是持续运营的事了。顺带一提文本拆解工具我踩了一圈坑。直接把 PDF 按段落切表格碎片满天飞后来换成布局识别加 OCR把图片和表格转成结构化文本问题才缓解。3.3 第三步把它接入 LangChain4j 或自研管道我用 Java 技术栈所以直接接在 LangChain4j 的文档处理管道里。核心就是一个实现过滤器接口的类在文档进入上下文组装前过一遍。代码比想象中简单public class JevGateFilter implements DocumentTransformer { private final JevScorer scorer; private final double threshold 0.75; public JevGateFilter(JevScorer scorer) { this.scorer scorer; } Override public ListDocument transform(ListDocument documents) { return documents.stream() .filter(doc - scorer.score(doc).total() threshold) .collect(Collectors.toList()); } }如果你是 Python 栈逻辑也一模一样class JevGateFilter: def __init__(self, model_path: str, threshold: float 0.75): self.scorer JevScorer(model_path) self.threshold threshold def transform_documents(self, documents, query): result [] for doc in documents: s self.scorer.score(query, doc) if s.total self.threshold: result.append(doc) return result接入之后要算一笔延迟账。假设一次查询检索 20 个 chunkJev 单条推理 20 毫秒线性执行就是 400 毫秒。如果线上端到端要求 1 秒以内还能接受如果要求 500 毫秒以内就必须做优化。我的做法是分两阶段先用 BM25 或快速向量粗筛把候选从 20 条砍到 5 条再让 Jev 精细打分实际延迟能控制在 150 毫秒左右。部署上我用 ONNX Runtime 导出模型Windows 机器 CPU 直接跑内存占用不到 500MB。这点很重要尤其在本地化部署场景不是每家都有 GPU 资源。3.4 第四步灰度上线与指标观测上线不能一把梭我按 query 类型做了灰度。第一周只让“售后类 query”走 Jev 闸门其他类型保持原逻辑这样可以快速比较差异。观测指标我盯五个误杀率即原本能回答的问题被 Jev 拦成“不知道”的比例目标 5% 以内漏放率即 Jev 放行但人工审核判为不合格的比例目标 2% 以内hit rate即检索正确命中的比例理论上会提升端到端回答满意度用点赞和点踩作为代理指标最后一个是被拦截 chunk 的维度分布用来判断权重要不要调。这里最容易被忽略的是反馈闭环。用户点踩的回答要自动进入待标注池每周人工复核一次积累新样本然后定期重新微调。我做灰度时发现第一版模型跑两周之后随着知识库内容更新误杀开始变多。原因很直白业务进来一批新文档表述风格和训练样本差太多Jev 认不出来。没有反馈闭环闸门会跟着业务漂移一起失灵。4. 常见问题与排查技巧实录4.1 误杀太多知识库回答变“我不知道”这是我上线后遇到的第一个大坑。症状很明显原本很多能答的问题Jev 拦完之后都变成了“抱歉我不知道”。排查的时候不要只看通过率一定要看三个维度的分数拆解。我遇到过一次误杀主要是安全性维度造成的。某个产品知识文档里有一些专业术语跟安全规则词表里的词很像结果被打成低分。解决办法是调低安全维度权重同时给特定业务线的 chunk 加白名单。还有一次是一致性维度卡太死本体要求“订单”必须包含订单号但有些售后场景的 chunk 根本没有订单号字段也被误杀了。后来我把本体约束改成按 query 类型动态启用误杀马上降下来。经验是阈值不是拍脑袋定死的建议按 query 类型动态设置。比如“物流查询”类 query 相关性权重拉高“政策法规”类 query 一致性权重拉高。4.2 漏放了冲突或有害内容Jev 为什么没拦住漏放比误杀危险也更难查。有一次线上出了个事用户问某药品副作用Jev 放行了一个看起来语气很客观的 chunk但里面故意漏掉了最重要的禁忌信息。单看这个 chunk相关性和一致性都合格但和知识库里的权威内容一比就是诱导行为。Jev 只处理单条 query-chunk 对看不到这种“单个没问题、组合起来有问题”的场景。针对这类问题我做两件事。一是增加对抗样本专门构造“正常语气但恶意内容”的训练数据提升模型的敏感度。二是对高风险业务增加生成后校验模型输出完答案之后再让 Jev 把答案和所有参考 chunk 过一遍发现关键信息缺失就触发重试。这里必须说清楚Jev 不是万能保险。真正治本的办法是在知识库源头做权限控制和内容审核敏感文档直接不进入 RAG 的检索范围。闸门只是最后一道防线不能当唯一防线用。4.3 延迟压不下来闸门变成瓶颈小型模型在 CPU 上跑单条 20 毫秒确实不快但放到整个 RAG 链路里就不一样了。一次查询如果是 20 条候选线性推理就是 400 毫秒再加上检索和生成很容易超时。我试过两种优化效果实测都很明显。第一种是动态 batch 推理把多条 chunk 拼成一个 batch 同时送入模型吞吐能提升 3 倍以上延迟从 400 毫秒降到 150 毫秒左右。第二种是粗筛加精判的两段式方案先用 BM25 或轻量向量模型把候选从 20 条快速砍到 5 条再让 Jev 在这 5 条上精细判断精度几乎没有损失延迟却大幅下降。更狠的做法是给 Jev 做一个蒸馏版小模型单条推理降到 5 毫秒以内换来一点精度损失。如果业务延迟红线非常紧可以考虑这条路线。4.4 图文混排知识库的特别提醒网上很多人问“rag 知识库能存储图片嘛”我直接说结论图片本身不能进向量库做检索但图片上的文字和表格必须能。如果你把一张含有退款步骤的截图直接传进知识库Jev 大概率会把它判成低质量 chunk 拦掉因为语义不完整。解决方式是用 OCR 加表格识别工具把图片内容转成带结构的 Markdown 文本再入库。同时要给拆解后的文本块做完整性标记比如表格必须包含表头和至少两行数据否则不入库。文本拆解工具要优先选能保留结构的不然后果就是“价格99”和“运费¥10”被拆到两个 chunk 里Jev 还会以为信息冲突。这个问题的本质是RAG 的知识库是给模型读的不是给人看的。图片、PDF、PPT 这些格式最终都要变成干净、独立、语义完整的文本块Jev 闸门才有意义。根据我个人经验Jev 这一层闸门不解决所有问题但它把 RAG 的失败模式从“一本正经胡说八道”变成了“拒绝回答、明确引用、或说不知道”。后者听起来不酷但在生产环境里太重要了。最后再分享一个我踩过几次坑后养成的小习惯所有被 Jev 拦截和放行的 case我都会打日志并且每周用线上坏样本回炉一次模型。时间长了你会发现真正让闸门变聪明的不是初始训练而是这个持续喂养的过程。
返回列表