本文通过一个真实案例,深入剖析了RAG知识库常见错误及其根源,指出检索环节是影响答案质量的关键。作者总结了chunk切分、向量召回精度、缺乏评测等三个断点,并提出了升级方案:选择合适的text splitter、使用reranker精排召回结果、建立评测闭环。文章详细介绍了如何利用LangChain实现这些优化,强调检索链路的重要性,并指出其与搜索架构的同构性,为读者提供了打造高效知识库的实用指导。
一、一个上线即翻车的知识库
上周我把已发的 30 篇文章喂进了一个 RAG 知识库,想让它回答关于我自己和产品的问题。
我问它:作者做的第一款小程序是干什么的?
它回答得流畅又自信:这是一款帮助用户分享美食菜谱、建立美食社区的微信小程序。
我回去翻了原文。第一款小程序叫「配料说」,拍一张食品配料表,把成分翻译成人话,告诉你这款食品值不值得买。它答错了,而且错得理直气壮。
检索日志更让我意外。召回的 top1 片段和问题相似度 0.87,向量库认为它是全库最相关的片段。相似度这么高,答案却和原文完全对不上。
RAG 的错误,往往藏在你看不见的检索环节里。
二、跑通了,答案却不可信
先把 RAG 全链路画出来。文档从加载到生成,一共 7 个环节:
橙色标注的 Retrieve 和 Generate,是答案质量的两个闸门。
做了 11 年搜索,我对这套链路有个本能判断。生成环节做的是翻译,把检索到的片段改写成通顺的人话。回答的上限由检索质量决定,生成模型只是执行者。
top1 相似度 0.87 只说明向量空间里它离问题最近,说明不了它在语义上正确。
更要命的是,生成模型不会质疑检索结果。喂给它一个错误片段,它会把这个错误包装成一段逻辑自洽、语气笃定的回答。RAG 翻车的剧本都长这样:用户看到的是自信的错答案,问题出在没人检查的检索环节。
三、藏在管线里的三个断点
复盘那次翻车,问题落在三个断点上。
断点 1:chunk 切分破坏了语义
答案本来在一个完整句子里,切分器把句子从中间腰斩。主语留在上一个 chunk,谓语留在下一个,检索时哪个 chunk 都和问题对不上。
另一种错配更隐蔽。库里有大量讲"怎么做"的段落,问题问的是"是什么",粒度不匹配,召回的片段自然文不对题。
断点 2:单向量召回的精度上限
向量相似度衡量的是"长得像",语义正确是另一回事。top-k 里经常混进形状相似但内容无关的片段,这是向量召回的固有噪声。
召回只负责回答"有没有可能相关",精度要靠下一级环节保证。这个道理在搜索系统里是常识,到了 RAG 教程里却没人讲。
断点 3:没有评测闭环
改了 chunk 大小,换了 embedding 模型,效果好坏全凭感觉。没有一组固定问题做回归,你永远不知道改动是变好还是变坏,上线翻车只是时间问题。
市面上的 RAG 教程普遍停在这个水平:跑通 demo 就收工,检索质量三件事(切分策略、精排、评测)一字不提。
| 数据形态 | 常见坑 | 典型表现 |
|---|---|---|
| 长文档(几万字) | chunk 切太小 | 一个主题被切散,回答缺上下文 |
| FAQ / 一问一答 | 一条 chunk 跨多条问答 | chunk 里混进无关问题 |
| 代码 / 配置 | 按字符硬切 | 语法被切断,语义全丢 |
| 中英混排 | 只用空格类分隔符 | 中文句子整段黏连切不动 |
四、三步把 RAG 从能跑升到能用
下面是我翻车后的完整升级路径。代码全部是 LangChain 1.x 新 API,旧 API 一条不出现。
Step 0:5 分钟跑通最小 RAG
先让链路转起来,再谈质量。环境准备两条:Docker 起 Milvus,Embedding 用 bge-m3。
docker run -d --name milvus -p 19530:19530 -p 9091:9091 milvusdb/milvus:latestEmbedding 用 bge-m3,模型名Pro/BAAI/bge-m3,输出 1024 维。
本地没有 Docker 的话,可以用 Chroma 平替跑通同一条链路:
| 维度 | Milvus(本篇主线) | Chroma(轻量平替) |
|---|---|---|
| 部署 | Docker 一键 | pip install 即用,进程内运行 |
| 定位 | 产线级向量库,支持分布式 | 单机原型、学习验证 |
| 迁移成本 | 接口换成langchain_chroma的Chroma即可 | 代码其余部分不变 |
建库写数据:
import os from dotenv import load_dotenv from langchain.embeddings import init_embeddings from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from pymilvus import MilvusClient load_dotenv() COLLECTION = "jianghu_articles" MILVUS_URI = "http://localhost:19530" # 1. 加载文章的 Markdown 源稿并切分 loader = TextLoader("articles.txt", encoding="utf-8") docs = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=300, chunk_overlap=50, separators=["/n/n", "/n", "。", ";", ",", " ", ""], # 中文优先按句号切 ) chunks = splitter.split_documents(docs) # 2. 向量化:bge-m3 统一入口 embed_model = init_embeddings( model="openai:Pro/BAAI/bge-m3", api_key=os.getenv("SILICONFLOW_API_KEY"), base_url=os.getenv("SILICONFLOW_BASE_URL"), ) vectors = embed_model.embed_documents([c.page_content for c in chunks]) # 3. 写库:主键 id,向量字段 vector,正文存 text 字段 client = MilvusClient(MILVUS_URI) if client.has_collection(COLLECTION): client.drop_collection(COLLECTION) client.create_collection( collection_name=COLLECTION, dimension=1024, metric_type="COSINE", ) data = [ { "id": i, "vector": vectors[i], "text": chunks[i].page_content, "source": chunks[i].metadata.get("source", "articles.txt"), } for i in range(len(chunks)) ] client.upsert(collection_name=COLLECTION, data=data) client.flush(collection_name=COLLECTION)检索加生成:
from langchain.agents import create_agent from langchain.chat_models import init_chat_model model = init_chat_model( model="qwen3-max", model_provider="openai", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url=os.getenv("DASHSCOPE_BASE_URL"), ) agent = create_agent( model=model, tools=[], system_prompt=( "你是问答助手。只根据检索到的上下文回答," "上下文不足以回答时直接说不知道," "回答末尾标注引用来源。" ), ) defrag_answer(question: str, k: int = 5): query_vector = embed_model.embed_query(question) hits = client.search( collection_name=COLLECTION, data=[query_vector], limit=k, output_fields=["text", "source"], )[0] context = "/n---/n".join(h["entity"]["text"] for h in hits) return agent.invoke({"messages": [ ("user", f"上下文:/n{context}/n/n问题:{question}"), ]})这套代码已经能回答问题了,答案质量停留在上一节说的水平。接下来三步才是分水岭。
Step 1:选对 splitter,再谈参数
聊 chunk 的文章,90% 只讲 RecursiveCharacterTextSplitter 的参数怎么调。切分器家族远不止这一种,选错策略的损失比调错参数更大。
LangChain 1.x 把切分器拆成独立包 langchain_text_splitters,按切分依据可以分成 5 类:
| 策略 | 切分依据 | 代表实现 | 适合场景 |
|---|---|---|---|
| 固定长度 | 字符数 / token 数 | CharacterTextSplitter | 快速兜底、英文文本 |
| 递归切分 | 分隔符按优先级逐级试 | RecursiveCharacterTextSplitter | 通用默认,90% 场景够用 |
| 结构切分 | Markdown / HTML 标题层级 | MarkdownHeaderTextSplitter | 文档自带清晰标题结构 |
| 代码切分 | 函数 / 类语法边界 | RecursiveCharacterTextSplitter.from_language | 代码库检索 |
| 语义切分 | Embedding 相似度断点 | SemanticChunker(实验性) | 话题跳跃大的高价值长文 |
递归切分是默认选项,它沿分隔符优先级逐级下探,先段落、再句子、再标点,像顺着纹理切肉。多数文档到这一层就够,代码库换 from_language,高价值长文再考虑语义切分,后者每次切分都要调 Embedding,慢且贵,量级不同。
中文场景有一条实测结论(text2vec 社区用 1150 份真实文档验证过):ChineseRecursiveTextSplitter 内置中文分隔符,切分质量普遍好于默认的 RecursiveCharacterTextSplitter。CharacterTextSplitter 按字符硬切,经常把转折词腰斩成两段,遇到没有空行的中文文档甚至整篇都不切:
from langchain_text_splitters import ChineseRecursiveTextSplitter splitter = ChineseRecursiveTextSplitter( chunk_size=300, chunk_overlap=50, ) chunks = splitter.split_documents(docs)chunk_size 的上界也有依据。embedding 模型对输入长度有上限,bge-m3 是 8192 token,早期的 BCE、bge-large-zh 只有 512。超过上限的 chunk 会被截断,丢的是尾部信息,检索时这段内容就缺了一半。
实测数据:chunk_size 用错能掉 10% 的指标,精细调参只换来 2%。先选对量级,再谈微调。
| 参数 | 起点值 | 调大 | 调小 |
|---|---|---|---|
| chunk_size | 300 | 答不全、缺上下文 | 答得泛、混入无关内容 |
| chunk_overlap | 50 | 跨 chunk 的语义衔接不上 | 库里有大量重复片段 |
| 中文 separators | 加 。;, | 中文句子黏连成块 | 单句被拦腰截断 |
公众号文章是 Markdown,自带标题层级,用结构切分更合适。先用 MarkdownHeaderTextSplitter 按标题分章,超长章节再递归精切,两刀组合:
from langchain_text_splitters import MarkdownHeaderTextSplitter from langchain_text_splitters import RecursiveCharacterTextSplitter # 第一刀:按标题分章,章节名自动带进每个 chunk md_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=[("#", "H1"), ("##", "H2"), ("###", "H3")] ) sections = md_splitter.split_text(md_text) # md_text 是 Markdown 原文 # 第二刀:超长章节再递归精切 fine_splitter = RecursiveCharacterTextSplitter( chunk_size=300, chunk_overlap=50, separators=["/n/n", "/n", "。", ";", ",", " ", ""], ) chunks = fine_splitter.split_documents(sections)结构切分的隐藏收益在 metadata。每个 chunk 都知道自己属于哪一章,检索命中后把章节名拼进上下文,回答天然带出处。
metadata 同样重要。每条 chunk 记录文章来源,回答强制带引用。检索质量最终要让用户可验证:答案后面跟着来源,读者点开原文能对得上,信任就建立起来了。
我在 system prompt 里写了"回答末尾标注引用来源",配合每篇一个文件加载,source 字段天然就是引用,零额外成本。
Step 2:reranker 精排,本篇灵魂
向量召回负责广撒网,精排负责一锤定音。用交叉编码器给召回结果重新打分,10 行代码:
from sentence_transformers import CrossEncoder reranker = CrossEncoder("BAAI/bge-reranker-v2-m3") def rerank(question: str, hits, top_n: int = 5): pairs = [(question, h["entity"]["text"]) for h in hits] scores = reranker.predict(pairs) ranked = sorted(zip(hits, scores), key=lambda x: x[1], reverse=True) return [h for h, _ in ranked[:top_n]]检索流程变成两段式:向量召回 top 20,reranker 精排取 top 5,再喂给模型。
同一个翻车问题,升级前后的检索结果对比:
| 位置 | 纯向量召回 top3 | reranker 精排后 top3 |
|---|---|---|
| 1 | 相似度 0.87,讲菜谱社区的无关段落 | 正确片段进 top1(配料说定义) |
| 2 | 相似度 0.79,另一篇文章的简介 | 相关补充说明 |
| 3 | 相似度 0.71,公众号菜单介绍 | 相关背景段落 |
看出差别了吗。纯向量把长得像的排前面,reranker 把语义对的排前面。精排后 top1 的原始相似度反而更低,答案却对了。
相似度数字会骗人,reranker 不会。
做过电商搜索的读者到这里应该会心一笑。向量召回对应多路召回,reranker 对应二次精排,评测集对应离线评测。RAG 的检索链和搜索系统是同一个架构,只是把 query 换成问题,把商品换成文档片段。
这套广撒网、精排定生死的打法,我在搜索产线用了 11 年,搬到 RAG 上是直接平移。市面教程写不出这一层,它需要搜索系统的实战积累。
Step 3:评测闭环
没有评测的优化都是玄学。我建了一组 20 条的 golden QA,覆盖文章里的关键事实:
| 问题 | 期望答案 |
|---|---|
| 作者做的第一款小程序是干什么的? | 拍配料表解读食品成分,判断值不值得买(配料说) |
| LangChain 1.0 的四大支柱是什么? | LangChain、LangGraph、Deep Agent、LangSmith |
| 作者在搜索领域做了多少年? | 11 年,五代架构 |
| 配料说用了什么 AI 模型? | Qwen + RAG |
每次改动跑一遍评测集,回答命中期望答案记 1 分,统计命中率。改动不再凭感觉:chunk 参数、embedding 模型、reranker 换不换,全部用数字说话。
我自己的结果:基线版本 20 条只对 11 条,那个翻车问题稳定答错。三步走完后 20 条对 18 条,翻车问题进了正确答案。剩下 2 条是知识库本身没覆盖的,属于边界问题,需要补文档而不是调参。
LangSmith 是这套评测的规模化版本,支持数据集、批量评测、回归对比,LC-09 展开讲。
五、RAG 与搜索架构是同构的
回头看那 30 篇文章的翻车,问题出在检索,解法也出在检索。召回、精排、评测这套老三样,从搜索系统平移到 RAG 几乎零成本,因为它们解决的是同一个问题:海量内容里怎么把对的片段捞出来。
跑通 RAG 是 1 小时的事,让检索质量可度量、可回归才是产线的事。
这套检索链还在往更远的地方走。我在产线里已经把它从"临时拼接上下文"升级成 Agent 的长期记忆:每次对话的结论、用户的偏好,向量化之后写回记忆库,跨会话可召回。这就是 LC-06 记忆模块要展开的内容,也是单 Agent 走向多 Agent 的核心资产。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】
为什么要学习大模型?
我国在A大模型领域面临人才短缺,数量与质量均落后于发达国家。2023年,人才缺口已超百万,凸显培养不足。随着AI技术飞速发展,预计到2025年,这一缺口将急剧扩大至400万,严重制约我国AI产业的创新步伐。加强人才培养,优化教育体系,国际合作并进是破解困局、推动AI发展的关键。
大模型入门到实战全套学习大礼包
1、大模型系统化学习路线
作为学习AI大模型技术的新手,方向至关重要。 正确的学习路线可以为你节省时间,少走弯路;方向不对,努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划,带你从零基础入门到精通!
2、大模型学习书籍&文档
学习AI大模型离不开书籍文档,我精选了一系列大模型技术的书籍和学习文档(电子版),它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。
3、AI大模型最新行业报告
2025最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。
4、大模型项目实战&配套源码
学以致用,在项目实战中检验和巩固你所学到的知识,同时为你找工作就业和职业发展打下坚实的基础。
5、大模型大厂面试真题
面试不仅是技术的较量,更需要充分的准备。在你已经掌握了大模型技术之后,就需要开始准备面试,我精心整理了一份大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。
适用人群
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。