RAG 答非所问时,先查向量,再怪模型
一次电子书问答项目里,检索回来的 top5 全是 [../Images/9.jpg] 这样的图片引用,相似度 0.08,而模型凭自身知识把问题答得头头是道------日志无报错,答案看似专业,知识库却形同虚设。这类故障最迷惑人,也最常见。
本文的判断:RAG 项目 80% 的故障不在模型,而在数据入库质量与检索质量验证。以下用一套 Milvus + LangChain + LangGraph 的真实链路说明如何定位、修复,以及为什么下一步必须是 Agentic RAG。文中入库与朴素问答部分实际运行通过;路由器部分代码运行未验证。
两条链,一个交汇点
text
入库链:EPUB → 按章加载 → 分块(500字/重叠50) → 向量化 → Milvus(3042 条)
查询链:问题 → 向量化 → 相似度检索 top5 → 增强提示词 → 流式生成
几个决定成败的入库决策:主键用 书_章_块 格式(1_101_13)自带溯源;chunkOverlap: 50 防关键句被边界腰斩;索引选 IVF_FLAT(聚成 1024 个桶只扫目标桶,nprobe:16 控制扫几个)+ COSINE。入库与查询两侧的 index_type、metric_type 必须一致 ------查询侧写 HNSW 会让 SDK 把 ef 参数发给只认 nprobe 的 IVF_FLAT 索引。
破案:相似度分数是唯一的目击者
朴素 RAG 的查询链就是 LangGraph 两个节点:retrieve 调 similaritySearchWithScore 拿 [Document, score] 二元组,generate 把片段拼进提示词流式生成。检索后把每条的分数打印出来,是成本最低的质量观测手段。
问题暴露后,三步定位:
- 库里有没有正文? 按
content like "%阿朱%"直接取样------正文存在,排除"没导入"。 - 向量是不是坏的? 取库中某条的存储向量,与同一段文字现算的向量算余弦。结果 0.0327(正常应接近 1)------那一批入库向量整体损坏。
- API 现在正常吗? 同文本两次现算余弦 = 1.0,且问题 vs 相关正文 = 0.30、vs 无关正文 = 0.24,区分度健康------删集合重灌即可修复。
这组数字值得收藏(COSINE、同模型实测):相关 ≈0.30 / 无关 ≈0.24 / 垃圾 ≈0.08。"平均分低于阈值就判检索失败"这个判据,写进代码就是将来 Agentic RAG 评估节点的雏形。
依赖与类型的暗坑,一次说清
@langchain/community的可选 peer 依赖不自动安装:本项目依次缺@zilliz/milvus2-sdk-node、epub2、html-to-text,外加@langchain/textsplitters,逐个报ERR_MODULE_NOT_FOUND。规律:静态 import 缺包启动即炸 ,动态 require 缺包跑一半才炸。- VarChar 字段传 JS 数字会插入报错(
book_id必须"1")。 model.stream()返回Promise<AsyncGenerator>,漏await直接for await会报stream is not async iterable。new Milvus(单参数)必 TypeError,构造签名是(embeddings, args);连接已有集合应使用Milvus.fromExistingCollection。
为什么朴素 RAG 必须升级
单行道 START → retrieve → generate → END 有五个结构性缺陷(项目 readme 原始记录):简单问题也走检索浪费 token;无检索质量评估;无法多步检索("四大恶人排第二是谁"得先查名单再查排名);纯语义检索对精确实体失灵("高血糖/低血糖"向量很近,方向却相反);库外内容只能靠模型硬编。
Agentic RAG 的解法是往图里插决策节点。第一步是查询路由,用 zod 枚举把 LLM 判断钉死成二选一:
js
const RouteSchema = z.object({
strategy: z.enum(["simple", "complex"]),
reason: z.string(),
})
const router = await model.withStructuredOutput(RouteSchema).invoke(prompt)
// router.strategy 只能是 "simple" | "complex",条件边据此分流
枚举的价值在于封闭值域 :不约束的话模型会回答"相对简单""不算复杂",代码没法 if。初版代码有两个典型错误值得引以为戒------invoke 返回值没接收(拿到 undefined),枚举写成了自由字符串。修复后写法如上,运行未验证。
自检清单
发布一个 RAG 功能前过一遍:
- 字段类型与 JS 传入类型一致(VarChar vs number)
- 两侧索引类型、度量类型对齐
- 抽查存储向量 vs 现算向量余弦 ≈ 1
- top-k 分数低于阈值有兜底,不让 LLM 硬答
- 图片引用等垃圾片段入库前清洗
遗留一个待验证的问题:坏向量批次的根因未定位(推测为 embedding 网关瞬时异常),说明检索质量需要长期监控而非一次性检查。下一步计划:实现评估节点 + 重写查询回环,把五个痛点逐条搬上图。
标签:RAG、Milvus、LangGraph、LLM、向量数据库