RAG 不是一个技术选型问题,而是一个渐进式的问题发现过程------你只有上线后被用户怼了,才知道自己漏了什么。
翻车现场:纯 Dense 检索漏了什么?
用户问"这个型号的排水管多长"。Dense 检索信心满满地返回了 Top-3 文档:① 洗衣机产品说明书前两页(介绍面板和功能,cosine=0.91)② 同系列另一款洗衣机的对比表(cosine=0.88)③ 安装注意事项(cosine=0.85)。三篇里都用到了"管"字------"进水管安装""排水管连接"------向量空间里和用户的 query 确实近。但用户要的是排水管长度的具体数字,这个信息藏在说明书的最后两页"技术规格"表里,而这个表的文本全是数字和单位("长度:1.5m""直径:30mm"),embedding 模型面对这种"数字表"几乎无能为力------cosine 只有 0.62,排在检索结果的第 14 位。
用户很生气。排查过程里我们意识到这不是个例------Dense 检索天然偏好语义丰富但可能无关的长段落(前三页),排斥信息密度高但语义稀疏的数字表格(最后一页)。这是 embedding 模型的固有缺陷,不是调参数能解决的。
顺着这个 case 深挖,纯 Dense 检索暴露了三个盲区:
-
精确匹配失效:原始 query 里的"排水管"是一个具体名词,BM25 查"排水管"三个字就能命中。但在向量空间里,"排水管"被 embedding 映射到一个和"进水管""排水口""水管配件"混在一起的区域------语义上确实近,但用户要的不是这些。关键词精确匹配在这种场景下比语义搜索更直接、更准。
-
关系型盲区:"比这个便宜的同品牌机型"→ Dense 找到"便宜""折扣",但找不到"同品牌"这个关系。"同品牌"不是文本内容,是实体之间的图关系------纯文本 embedding 永远解不了这种题。
-
噪音放大:语义相近但事实无关的文档(Top-1 是洗衣机功能介绍,不是规格表),LLM 拿到后当原材料编造回答------"您的洗衣机排水管长度为标准尺寸,约1.2-1.5米"。答案听起来合理,但不是那个型号的具体数据。这是最危险的失败模式:看起来对,实际上是编的。
混合检索怎么搭?
两条并行路线:
- Dense:BGE-small 512 维向量 → HNSW 索引 → 语义相似文档
- Sparse:原始文本 → BM25 分词 → 关键词精确匹配
- RRF 融合:两条路按排名位置加权合并
典型效果:"这个型号的排水管多长"→ Sparse 命中"排水管",Dense 命中洗衣机相关,融合后同时有了精确性和相关性。精确匹配类问题的覆盖率从 70% 提升到 95%。
图增强:向量永远解决不了的问题
问题定义:"这个洗衣机有什么配件""同品牌更好的有吗"→ 纯向量搜不出来。建一个商品关系图谱(同品牌/兼容配件/替代品),图查询与 Milvus 检索并行执行,结果注入 prompt。
图增强消融实验:
这 4 个用例在向量库中根本没有对应文档------没有图增强就是"未查询到"。图增强是唯一的解法:
| 测试问题 | 无图 | 有图 |
|---|---|---|
| IPHONE_15 有什么兼容配件? | "未查询到" | AirPods Pro 2, Apple Watch S9, MagSafe 无线充电器 |
| 苹果品牌有什么热销产品? | "未查询到" | AirPods 4, AirPods Pro 2, Apple Pencil USB-C, Apple Watch S9/Ultra 2 |
| AIRPODS_PRO2 有什么替代品? | "未查询到" | Galaxy Buds 3 Pro + 同品类对比 + 同品牌搭配 |
| MAGSAFE_CHARGER 同品类有哪些? | "未查询到" | Apple Pencil USB-C, 华为 M-Pencil 第三代, USB-C 编织数据线 |
量化指标(本地启发式评估,4 用例):
| 指标 | 无图 | 有图 | 提升 |
|---|---|---|---|
| 答案相关性 | 0.2500 | 0.5327 | +113% |
| 答案完整度 | 0.1889 | 0.5308 | +181% |
| 忠实度 | 0.2642 | 0.2579 | -2%(基本持平) |
| 平均延迟 | 9,495ms | 11,250ms | +1,755ms(主要为 LLM 处理图数据增加的生成时间) |
关键发现:
- 图数据是"无替代方案"类问题的唯一解法------向量检索在纯关系型问题上完全无效
- 答案完整度提升 181% 是本次实验最显著的指标------图数据弥补了向量库缺失的商品关系信息
- 忠实度几乎持平说明图数据没有引入新的幻觉风险
- 延迟增加可接受(+1.7s)------图查询本身毫秒级,增量主要来自 LLM 需消化图查询返回的额外结构化信息,而非图查询执行本身阻塞主链路
图增强落地的三类工程陷阱(没有这些修复,图增强就不 work):
- Prompt 表达与 nGQL 语义陷阱 :LLM 看不懂
【兼容配件】这样的简短标签,需要完整自然语言描述;Brand/Category 查询中的 nGQL 管道和ORDER BY在 nebula3 上触发 Thrift 断言,只能退回 Python 端做排序和过滤 - 图边方向缺失 :
COMPATIBLE_WITH和SUBSTITUTE两条核心边都只建了单向------用户问"这个配件能用在什么商品上"时完全查不到,补齐REVERSELY方向后覆盖翻倍 - 异常处理缺失 :Thrift 协议断言失败未捕获导致整个服务 hang,捕获
AssertionError并重置连接池后才能自愈
HNSW 参数调优:默认值不一定最优
在 Milvus 上做完整消融发现 efSearch 的默认值 50 不是最优------甜点在 32。这个发现颠覆了"默认即最优"的惯性假设,完整实验设计与四条结论见第四篇。
Reranker 的意外:为什么"精排"反而让回答变差了?
Reranker 的阈值设置极其敏感------调整一档就能让答案相关性剧烈波动。学术界声称的 +17% Faithfulness 在现有数据上几乎没有复现。完整消融数据与最佳实践见第五篇。
BM25 兜底:Embedding 挂了还能回答吗?
模拟 Embedding 完全不可用(只剩 BM25),88% 查询仍有结果。RAGAS 实测 Faithfulness 下降温和,远非预期中的"幻觉翻倍"------BM25 并非让回答不可信任,而是"忠实度微降、语义理解略损"。真正代价在 Context Recall 和语义召回细节上。BM25 的真正定位不是"替代 Dense",而是"降级的最后一道防线"。完整兜底验证与量化数据见第五篇。
图增强撬动了什么:端到端小结
图增强消融实验的结论很直接:对于纯关系型问题("这个商品有什么配件""同品牌有什么"),向量检索完全无效------Milvus 里根本没有这些文档。图查询是唯一的解法。4 个用例全部从"未查询到"变成完整推荐,答案相关性 +113%,完整度 +181%,忠实度持平。代价是端到端延迟增加约 1.7s,主要由 LLM 生成阶段处理额外的图数据 token 所致,图查询自身为零秒级开销。
但真正花时间的不是模型选型或参数调优,而是图数据的工程落地:边方向缺失(REVERSELY)、nGQL 语义陷阱(管道在 nebula3 上触发 Thrift 断言)、prompt 格式误解(LLM 看不懂 【兼容配件】 标签)------这些"工程魔鬼"每一个都能让图增强通道静默失效。把图数据喂对 LLM,花的精力远超建图本身。
完整消融数据(HNSW efSearch 曲线、Reranker 阈值敏感度、BM25 兜底验证)分别在第四篇 和第五篇展开。
4-Step RAG 流水线
#mermaid-svg-s5MiwNmkGNp8hLtR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s5MiwNmkGNp8hLtR .error-icon{fill:#552222;}#mermaid-svg-s5MiwNmkGNp8hLtR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s5MiwNmkGNp8hLtR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s5MiwNmkGNp8hLtR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s5MiwNmkGNp8hLtR .marker.cross{stroke:#333333;}#mermaid-svg-s5MiwNmkGNp8hLtR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s5MiwNmkGNp8hLtR p{margin:0;}#mermaid-svg-s5MiwNmkGNp8hLtR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR .cluster-label text{fill:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR .cluster-label span{color:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR .cluster-label span p{background-color:transparent;}#mermaid-svg-s5MiwNmkGNp8hLtR .label text,#mermaid-svg-s5MiwNmkGNp8hLtR span{fill:#333;color:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR .node rect,#mermaid-svg-s5MiwNmkGNp8hLtR .node circle,#mermaid-svg-s5MiwNmkGNp8hLtR .node ellipse,#mermaid-svg-s5MiwNmkGNp8hLtR .node polygon,#mermaid-svg-s5MiwNmkGNp8hLtR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s5MiwNmkGNp8hLtR .rough-node .label text,#mermaid-svg-s5MiwNmkGNp8hLtR .node .label text,#mermaid-svg-s5MiwNmkGNp8hLtR .image-shape .label,#mermaid-svg-s5MiwNmkGNp8hLtR .icon-shape .label{text-anchor:middle;}#mermaid-svg-s5MiwNmkGNp8hLtR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s5MiwNmkGNp8hLtR .rough-node .label,#mermaid-svg-s5MiwNmkGNp8hLtR .node .label,#mermaid-svg-s5MiwNmkGNp8hLtR .image-shape .label,#mermaid-svg-s5MiwNmkGNp8hLtR .icon-shape .label{text-align:center;}#mermaid-svg-s5MiwNmkGNp8hLtR .node.clickable{cursor:pointer;}#mermaid-svg-s5MiwNmkGNp8hLtR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s5MiwNmkGNp8hLtR .arrowheadPath{fill:#333333;}#mermaid-svg-s5MiwNmkGNp8hLtR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s5MiwNmkGNp8hLtR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s5MiwNmkGNp8hLtR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s5MiwNmkGNp8hLtR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s5MiwNmkGNp8hLtR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s5MiwNmkGNp8hLtR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s5MiwNmkGNp8hLtR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s5MiwNmkGNp8hLtR .cluster text{fill:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR .cluster span{color:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s5MiwNmkGNp8hLtR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s5MiwNmkGNp8hLtR rect.text{fill:none;stroke-width:0;}#mermaid-svg-s5MiwNmkGNp8hLtR .icon-shape,#mermaid-svg-s5MiwNmkGNp8hLtR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s5MiwNmkGNp8hLtR .icon-shape p,#mermaid-svg-s5MiwNmkGNp8hLtR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s5MiwNmkGNp8hLtR .icon-shape .label rect,#mermaid-svg-s5MiwNmkGNp8hLtR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s5MiwNmkGNp8hLtR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s5MiwNmkGNp8hLtR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s5MiwNmkGNp8hLtR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Step 2: 混合检索
图数据注入
Step 4: LLM 生成
通义千问 Qwen-Max
System Prompt + Context + Query
→ 最终回答
Step 3: Reranker 精排
BGE-Reranker CrossEncoder
Top-20 → Top-5
阈值 0.1
Step 1: 问题改写 & 安全审查 (并行)
LLM 问题改写
拼写纠错 / 补全上下文
ContentFilter
L1 规则 (<1ms)
用户问题
'这个型号的排水管多长'
S1
S2
Dense 检索
BGE-small 512维
Milvus HNSW efSearch=32
Sparse 检索
BM25 关键词
精确匹配
图查询
NebulaGraph
同品牌/配件关系
RRF 融合
排名加权合并
S3
S4
用户回答