RAG 系统上线一个月后,我发现的问题比想象的还多

RAG 系统在 demo 里跑得挺好,一上线各种问题全冒出来了——召回率低、幻觉不止、延迟爆炸。这篇聊聊我在 RAG 项目中踩过的坑和解决思路。

做 RAG 系统之前,我对它的理解很天真:文档切一切,向量存一存,用户问个问题,搜到 top-k 片段塞给大模型,完事。

图样图森破。

系统上线第一周,我就被现实教育了。用户反馈的问题五花八门,但归结起来就三类:查不到、答不准、跑得慢。每一类背后都不是单一原因,而是好多个小问题叠在一起爆发。

查不到:不是 Embedding 的锅

有个场景,用户问「去年的 Q4 财报中研发投入占比是多少」,RAG 系统返回了一段关于「2025 年年度财务概览」的内容,里面确实有研发投入数字,但不是 Q4。

问题出在哪?不是 Embedding 模型不够强,也不是向量检索没找到。而是分块策略:我的文档按固定字数(500 tokens)切块,没做语义边界判断。一段完整的「Q4 财务分析」被切到了两个 chunk 里,前一个 chunk 有标题没数据,后一个 chunk 有数据没标题。向量检索匹配到了那个带数字的 chunk,但缺乏「这是 Q4 数据」的上下文信息。

类似的问题在技术文档里更常见。一篇文章讲「异常处理机制」,前一段讲「错误码规则」,后一段讲「重试策略」,中间一刀切开,两个 chunk 各自都不完整。

解决方案很直接:切块时保留元数据。每个 chunk 带上文档标题、章节标题、页码。检索时不只是返回 chunk 原文,同时把上下文标题也拼接进 prompt。对于结构化文档,按章节边界切块比固定字数靠谱得多。另外一个简单有效的做法是 parent-child retrieval:小块做检索,命中后返回整段父级内容,保证上下文完整。

这个改动不大,但效果立竿见影——用户问「Q4 研发投入」的时候,系统能准确返回 Q4 章节的内容,哪怕这个 chunk 里的关键词本身看起来更像「年度财务」。

答不准:召回率高不等于答案对

查不到的问题解决后,下一个更隐蔽的问题来了:明明搜到了正确的文档块,大模型的回答还是错的。

有一次用户问「A 方案的部署周期要多久」,RAG 搜到了一段写着「A 方案部署周期约 2-3 周」的文字。但大模型回答的是「A 方案部署周期约 1-2 个月」。

这就很让人崩溃了——检索没问题,是生成阶段的问题。排查后发现,大模型的训练数据里包含了大量类似「企业级系统部署周期通常为 1-2 个月」的通用知识,模型的先验压过了检索到的证据。

这个问题没有银弹解法。我做了两件事:

第一,在 system prompt 里加了硬约束——「如果检索到的文档中有明确答案,必须以此为准,不得使用通用知识补充」。同时要求模型在回答中直接引用原文内容,不只是泛泛提及。

第二,对检索结果做了一层置信度判断。不是所有搜到的文档块都值得喂给模型。如果 top-1 和 query 的余弦相似度只有 0.6,说明可能搜歪了,强行塞给模型反而坏事。我设了一个阈值,低于阈值的文档块丢弃,同时降低 top-k 数量,用更少但更相关的片段。准确率提升的同时,prompt 变短了,Token 消耗也降了。

跑得慢:延迟从天而降的分解

最难受的是延迟问题。一次完整的 RAG 查询链路:Query Rewrite → Embedding → 向量检索 → Rerank → Prompt 构建 → LLM 生成。每一步都在加延迟。

系统刚上线时,端到端延迟普遍在 5-8 秒。拆解后发现瓶颈在两个地方:LLM 生成和 Reranker。

LLM 生成时间占了总延迟的一半以上。换了更快的推理模型之后,生成时间从 3 秒降到了 1 秒。另一个容易被忽略的优化点是输出长度——如果你只需要一个简短结论,把 max tokens 设小一点,生成速度能快不少。

Reranker 的问题更典型。当时用的 Cross-Encoder,对 query-doc pair 逐对打分,一次 20 对要跑 20 次前向传播。后来改成两阶段策略:先用 lightweight bi-encoder 初筛到 top-10,再用 Cross-Encoder 精排 top-5。速度翻了 3 倍,精排效果没有明显下降。

还有一个隐藏的延迟来源:Reranker 模型的加载和初始化。如果在每次请求时都加载模型,光是加载就要几百毫秒。正确的做法是把 Reranker 做成常驻服务,或者至少用连接池复用推理进程。

还有个没人提的坑:数据漂移

文档库不是静止的。每周都有新文档入库、旧文档更新。但很多人只做一次离线索引,之后就再也不管了。结果就是:用户问的是最新版本的内容,RAG 搜到的却是过时的旧版本。

这个问题的解法是建立增量索引机制——文档更新时自动触发重新索引,而不是全量重建。更简单一点的方案是在元数据里加时间戳,检索时按时间降序排列,优先返回最新版本。

最大的教训

回头看,RAG 系统最大的坑不是技术选型,而是对「检索质量」的盲目自信。很多人(包括我)以为向量检索能解决一切语义理解问题,但实际是:分块方式决定了检索的上限,元数据质量决定了检索的精度,prompt 设计决定了生成的质量。这三者是串联关系——任何一个环节出问题,最终答案都会崩。

把这三层都想清楚,RAG 才能从「能跑」走到「好用」。

评论

此博客中的热门博文

我写了半年 prompt,最后发现最好的技巧就三个