news 2026/4/24 23:58:19

RAG 原理不难,难的是”检索结果不准”——我踩过的那些坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RAG 原理不难,难的是”检索结果不准”——我踩过的那些坑

半年前我接了一个内部知识库的需求,要求很简单:「把我们的文档喂给 AI,让它能回答用户的问题」。我当时觉得这不就是 RAG 嘛,两天搞定。

结果我花了整整三周,才让它勉强能用。

这篇文章记录的,就是这三周里我被折磨出来的经验。


一、先把原理讲透,再谈为什么会出问题

很多教程上来就贴代码,但如果你不理解每一步在做什么,代码跑通了你也不知道结果为什么不对,更不知道从哪里下手优化。

RAG 的本质是三件事:找到相关内容、把内容给 AI、让 AI 基于内容回答

用更具体的语言描述:

① 建库阶段(离线) 把文档切成小段(Chunk) → 每段文字转成一串数字(向量 / Embedding) → 存进向量数据库② 检索阶段(在线) 用户提问 → 问题也转成向量 → 在数据库里找"数字最接近"的那些段落 → 取出 Top-K 段原文③ 生成阶段(在线) 把检索到的段落 + 用户问题拼成 Prompt → 送给 LLM 生成最终回答

听起来很直观对吧?问题就藏在每一个箭头里。


二、坑一:Chunk 切得像乱刀斩乱麻

最开始怎么写的

刚开始图省事,直接按固定字数切,500 字一刀,干净利落:

def naive_chunk(text, chunk_size=500): return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]

发生了什么

有个文档写的是:

“……综上所述,该方案存在三点主要风险。第一,资金链断裂风险;第二,供应商集中风险;第三……”

切完之后,第一段最后是「第三」,第二段开头是「合规风险,具体表现为……」

用户问「有哪些风险」,检索到第一段,AI 告诉用户「只有两点风险」。

为什么会这样

固定字数切割完全不看内容语义,只认字数。一个完整的知识点很可能被切成两半,检索时只能拿到半截,LLM 当然给出残缺的答案。

怎么解决

改成递归语义切割,优先按段落、句子等自然边界切,同时加上重叠窗口,让相邻 Chunk 之间共享一段内容,防止上下文在边界处断裂:

from langchain.text_splitter import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, # 相邻 Chunk 重叠 80 字,保住边界上下文 separators=[ "\n\n", # 优先按空行(段落)切 "\n", # 其次按换行切 "。", "!", "?", # 再按句子结尾切 " ", "" # 最后才按字切 ])chunks = splitter.split_text(text)

经验值chunk_overlap设成chunk_size的 15%~20% 比较稳。重叠太小上下文断裂,重叠太大引入冗余,干扰检索排名。


三、坑二:Embedding 模型选错,中文像在说火星话

最开始怎么写的

从一篇英文教程直接复制过来的代码,用的是text-embedding-ada-002

from openai import OpenAIclient = OpenAI()def embed(text: str) -> list[float]: return client.embeddings.create( input=text, model="text-embedding-ada-002" ).data[0].embedding

发生了什么

用户问「如何申请年假」,文档里明明有一整节「年假申请流程」,检索结果里没有它,反而出来一堆不相关的段落。

代码没有任何报错,跑得很顺,但结果就是不对。这是最隐蔽的一类坑。

为什么会这样

ada-002以英文语料为主训练,对中文语义理解相当有限。它不太能理解「申请年假」和「年假申请流程」在语义上高度相关。

怎么解决

中文文档必须换中文友好的 Embedding 模型

模型特点推荐场景
BAAI/bge-m3开源、中文效果最强优先考虑,本地或云端均可
text-embedding-3-largeOpenAI 新版,多语言大幅提升不想本地部署、预算充足
moka-ai/m3e-base轻量,中文够用内存资源紧张时

换成bge-m3之后,「年假」那个例子直接从检索不到变成排名第一:

from sentence_transformers import SentenceTransformer# 第一次运行会自动下载模型,约 2GBmodel = SentenceTransformer("BAAI/bge-m3")def embed(texts: list[str]) -> list[list[float]]: # normalize_embeddings=True 后可直接用点积计算余弦相似度 return model.encode(texts, normalize_embeddings=True).tolist()

Embedding 模型是 RAG 效果的地基,地基没打好,后面再怎么优化都是在沙滩上盖楼。换模型这件事,越早做越好。


四、坑三:向量检索不认数字和专有名词

发生了什么

财务同事问:「2024 年第三季度的净利润是多少?」

文档里这个数字明明白白写着,但检索出来的是一堆「公司财务状况概述」和「利润分配原则」,就是没有那个具体的季度数据。

为什么会这样

向量检索的原理是语义相似度,它擅长理解「意思差不多」的表达,但不擅长精确匹配。

「2024 年第三季度净利润」这种查询,语义层面和很多财务类文本都沾边,但「2024」「第三季度」这些精确信息,反而被语义的海洋淹没了。这个问题有个名字叫词汇鸿沟(Lexical Gap)

怎么解决

混合检索(Hybrid Search):向量检索负责语义,BM25 关键词检索负责精确匹配,两个结果加权融合:

from rank_bm25 import BM25Okapiimport numpy as npclass HybridRetriever: def __init__(self, chunks: list[str], embedder): self.chunks = chunks self.embedder = embedder self.bm25 = BM25Okapi([c.split() for c in chunks]) self.vectors = np.array(embedder(chunks)) def retrieve(self, query: str, top_k: int = 8, alpha: float = 0.5): """ alpha: 向量检索权重,(1 - alpha) 为 BM25 权重 alpha 越大 → 越偏语义;alpha 越小 → 越偏精确匹配 """ q_vec = np.array(self.embedder([query])[0]) vec_scores = self.vectors @ q_vec bm25_scores = np.array(self.bm25.get_scores(query.split())) def normalize(arr): mn, mx = arr.min(), arr.max() return (arr - mn) / (mx - mn + 1e-9) combined = alpha * normalize(vec_scores) + (1 - alpha) * normalize(bm25_scores) top_idx = combined.argsort()[::-1][:top_k] return [self.chunks[i] for i in top_idx]

调参建议

  • 对话问答类场景:alpha=0.6,偏语义
  • 财务报告、技术文档等精确查询多的场景:alpha=0.3,让 BM25 更强势
  • 不确定时从0.5起步,跑几条测试用例再调

五、坑四:Top-K 设太大,LLM 被噪音淹没

发生了什么

为了「保险」,我把top_k设成 10,把 10 段内容全塞进 Prompt。

然后 LLM 开始把不相关的内容混进答案,把某段「行业背景介绍」当成依据,给出了完全错误的回答,还给得理直气壮。

为什么会这样

LLM 不是搜索引擎,它不会「忽略」不相关的内容,而是尝试用所有给它的内容来生成答案

这个现象有个研究名字叫Lost in the Middle:LLM 倾向于重点关注 Prompt 开头和结尾的内容,中间的内容容易被混淆。塞的内容越多,相关信号被稀释越严重。

怎么解决

在检索和生成之间加一层Rerank(重排序):先粗检索一批候选,再用专门的重排序模型精准打分,只保留真正相关的 Top-N:

from sentence_transformers import CrossEncoderreranker = CrossEncoder("BAAI/bge-reranker-v2-m3")def rerank(query: str, candidates: list[str], top_n: int = 3) -> list[str]: # CrossEncoder 对每个 (query, candidate) 对单独打分 # 比向量相似度更精准,但速度更慢,所以只用于精排阶段 scores = reranker.predict([(query, c) for c in candidates]) ranked = sorted(zip(scores, candidates), key=lambda x: x[0], reverse=True) return [doc for _, doc in ranked[:top_n]]# 完整流程:粗检索 → 精排raw_candidates = retriever.retrieve(query, top_k=10) # 粗检索 10 个final_context = rerank(query, raw_candidates, top_n=3) # 精排保留 3 个

这是性价比最高的单点优化,接入成本低,效果立竿见影。粗检索 8~12、精排保留 3~5,这个区间大多数场景下都稳。


六、坑五:用户提问太模糊,检索完全跑偏

发生了什么

测试阶段大家都在问很完整的问题,上线之后真实用户是这样问的:

  • 「上次说的那个报销流程怎么弄?」
  • 「之前那个问题解决了吗」
  • 「还有呢」

这种问题里根本没有检索锚点,向量数据库不知道「上次」「之前」指的是什么,只能返回莫名其妙的结果。

为什么会这样

RAG 的检索是无状态的,每次检索只看当前这条 Query,完全不知道之前聊了什么。

怎么解决

Query 改写:检索之前,先用 LLM 把用户的模糊问题结合对话历史改写成一个完整、独立的检索 Query:

from openai import OpenAIclient = OpenAI()def rewrite_query(history: list[dict], user_query: str) -> str: # 只取最近 4 轮,太长引入干扰 recent = history[-4:] history_text = "\n".join(f"{m['role']}: {m['content']}"for m in recent) prompt = f"""根据以下对话历史,将用户的最新问题改写为一个完整、独立的搜索查询。只输出改写后的查询,不超过 30 字,不要任何解释。对话历史:{history_text}用户最新问题:{user_query}改写后的查询:""" resp = client.chat.completions.create( model="gpt-4o-mini", # 改写任务用 mini 就够,省钱 messages=[{"role": "user", "content": prompt}], temperature=0 # 改写不需要创造力,temperature=0 保证稳定输出 ) return resp.choices[0].message.content.strip()# 实际效果示例:# 用户说:"上次说的那个报销流程怎么弄?"# 改写后:"差旅费报销流程及所需提交材料" ← 这才能检索到东西

这步成本极低,每次改写消耗 Token 不超过 200,换来多轮对话场景下检索质量的大幅提升,强烈建议无脑加上。


七、坑六:文档更新了,知识库还活在过去

发生了什么

某个功能的操作流程改了,新文档上传了,但向量数据库里还是旧版本。

用户问新功能怎么用,AI 给出旧答案,还给得理直气壮。

怎么解决

给每个 Chunk 打上元数据,用内容哈希做变更检测,设计增量更新机制:

import hashlibdef upsert_document(doc_id: str, text: str, meta dict, vector_store): # 计算内容哈希,内容没变就跳过,节省 Embedding 费用 content_hash = hashlib.md5(text.encode()).hexdigest() existing = vector_store.get_metadata(doc_id) if existing and existing.get("content_hash") == content_hash: print(f"[跳过] {doc_id} 内容未变化") return # 删除该文档的所有旧 Chunk vector_store.delete(filter={"doc_id": doc_id}) # 重新切分、向量化、插入 chunks = splitter.split_text(text) vectors = embed(chunks) records = [ { "id": f"{doc_id}_chunk_{i}", "vector": vectors[i], "text": chunk, "metadata": { **metadata, "doc_id": doc_id, "chunk_index": i, "content_hash": content_hash } } for i, chunk in enumerate(chunks) ] vector_store.upsert(records) print(f"[完成] {doc_id} 更新,共 {len(chunks)} 个 Chunk")

建议在 CI/CD 流程里挂一个自动同步脚本,文档仓库有变更就触发更新,彻底解决知识库过期的问题。


八、把这些坑串起来,看完整的流程

经历了上面这些之后,整个 RAG 流程大概长这样:

【建库阶段】 文档 → 递归语义切分(chunk_size=500, overlap=80) → bge-m3 向量化 → 存入向量数据库(带 doc_id、内容哈希等元数据) → 文档变更时自动增量更新【查询阶段】 用户输入 → Query 改写(结合近 4 轮对话历史,gpt-4o-mini) → 混合检索(向量 + BM25,top_k=10) → Rerank 精排(bge-reranker-v2-m3,保留 top_3) → 拼入 Prompt → LLM 生成回答

不需要一次性全上,先从最痛的那个坑开始解决,每解决一个,效果就会有一次明显跃升。


九、最后说几句真心话

RAG 这个方向入门门槛很低。LangChain、LlamaIndex 封装得很完善,三十行代码就能跑起来一个 demo,然后你会觉得「这也太简单了」。

但这正是最大的陷阱所在。

**Demo 跑通不等于生产可用。**真正的难点有三个:

**一是评估体系。**你怎么知道 RAG 效果是在变好还是变坏?不能只靠「感觉」,得有指标(检索召回率、答案忠实度、RAGAS 评分),得有 benchmark 数据集,得能量化比较每次优化前后的差异。这件事很多人懒得做,然后调参全靠玄学。

**二是数据质量。**垃圾进垃圾出。文档里大量重复内容、排版混乱的 PDF、表格被提取成乱码——再好的检索策略也救不了一份烂文档,预处理阶段该花的时间一点都省不了。

**三是对业务的理解。**用户最常问什么?他们的问法有什么规律?哪类问题答错了代价最大?这些问题的答案,直接影响你 Chunk 策略、检索权重、兜底逻辑的设计。技术是工具,业务才是靶心。

我现在对 RAG 的判断是:**这是一个工程细节决定成败的方向,不是一个算法壁垒很高的方向。**把本文这几个坑都填完,再搭好评估体系,80 分以上的 RAG 系统大多数团队都能做到。

如果你也在做 RAG,欢迎评论区聊聊你踩过的坑——尤其是那种「代码没报错、结果就是不对」的情况,说不定比我的更离谱。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 18:45:17

AI编程革命:Codex一键生成高效脚本

告别重复造轮子:Codex写脚本的技术文章大纲引言重复造轮子的定义及其在编程中的负面影响自动化工具(如Codex)如何提升开发效率本文目标:展示如何利用Codex快速生成脚本,减少重复劳动Codex简介Codex的背景与核心能力&am…

作者头像 李华
网站建设 2026/4/18 20:56:21

ESP-Drone实战指南:3步搭建百元级开源无人机方案

ESP-Drone实战指南:3步搭建百元级开源无人机方案 【免费下载链接】esp-drone Mini Drone/Quadcopter Firmware for ESP32 and ESP32-S Series SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-drone ESP-Drone是一个基于乐鑫ESP32系列芯片的完整…

作者头像 李华
网站建设 2026/4/18 14:28:50

Prodigy-PDF的PDF标注与OCR技术

最近推出了Prodigy插件,通过直接支持第三方集成来扩展Prodigy的功能。其中一款插件是Prodigy-PDF,它提供了PDF标注的功能。 [00:00] 介绍Prodigy-PDF [00:24] 标注PDF分段 [02:22] PDF分段中的OCR [03:55] 折叠启发式算法 本教程相关资源 ● Prodig-ANN:…

作者头像 李华
网站建设 2026/4/18 9:04:56

SITS2026模型压缩实战手册(FP16+知识蒸馏+动态token剪枝三阶加速)

第一章:SITS2026深度解析:图文理解模型优化 2026奇点智能技术大会(https://ml-summit.org) SITS2026(Semantic-Interleaved Text-Image System 2026)是面向多模态大模型推理效率与细粒度对齐能力双重瓶颈所提出的新一代图文理解架…

作者头像 李华
网站建设 2026/4/18 19:33:17

如何快速检测微信单向好友:WechatRealFriends完全操作指南

如何快速检测微信单向好友:WechatRealFriends完全操作指南 【免费下载链接】WechatRealFriends 微信好友关系一键检测,基于微信ipad协议,看看有没有朋友偷偷删掉或者拉黑你 项目地址: https://gitcode.com/gh_mirrors/we/WechatRealFriends…

作者头像 李华
网站建设 2026/4/18 18:18:33

3个关键步骤:ComfyUI-Impact-Pack图像增强插件完整使用指南

3个关键步骤:ComfyUI-Impact-Pack图像增强插件完整使用指南 【免费下载链接】ComfyUI-Impact-Pack Custom nodes pack for ComfyUI This custom node helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more. 项目地址: htt…

作者头像 李华