RAG(Retrieval-Augmented Generation,检索增强生成)这几年几乎是企业大模型落地的“默认选项”。但现实是,很多团队搭建知识库时并不顺利:文档喂进去了,检索结果却五花八门;模型答非所问,甚至引用了完全不相关的片段。问题通常不在大模型本身,而是从文档解析、分块策略、向量化方案、召回逻辑到重排机制这一段完整链路没有真正打通。
这篇文章会把 RAG 知识库从零到工程化的完整路径拆开讲清楚。内容包括:RAG 的核心原理与适用边界、环境与组件选型、文档切片与索引构建、检索召回优化、重排模型接入、完整的查询代码示例,以及生产环境必须考虑的知识更新、评估指标和排查方法。文章会提供可直接复制的代码,也会点出那些“教程里很少写、实际项目中天天踩”的坑。
1. 为什么企业知识库需要 RAG:先解决一个真实问题
先看一个很常见的业务场景。某制造企业希望做一个售后问答系统,让客服人员输入设备故障代码后,系统能给出对应的维修步骤。企业内部有一份 3000 页的维修手册,存放在内部文件系统里,大模型完全没有见过这些内容。有人提议把手册直接拼接进 Prompt,但 3000 页明显超出上下文窗口;有人建议微调一个专用大模型,但数据标注成本高、训练周期长,而且每次手册更新都要重新训练,后续维护成本几乎不可接受。
RAG 的思路完全不同:它先把维修手册切分成小块、向量化并存入向量数据库;用户提问时,先从向量库里检索最相关的小块,再把检索结果作为上下文交给大模型,让大模型基于这些内容组织答案。这样,知识更新不需要重新训练模型,只需要更新索引即可。手册改了,重新跑一遍文档处理流程就行。
这正是 RAG 最核心的价值:把知识更新的成本从“重训练”降低到“重索引”。大模型的参数是相对固定的,企业知识则处于高频变化中,RAG 在企业内部私有知识问答、产品文档检索、客服助手、政策法规问答等场景具有天然优势。相比之下,微调更适合改变模型的语气、输出格式或特定领域能力,而不适合高频更新的知识注入。
这也是本文要反复强调的判断:如果需求是“让模型知道某些文档内容”,优先考虑 RAG;如果需求是“让模型学会某种能力或约束输出风格”,再考虑微调。两者不是互斥关系,但在实际项目里,RAG 往往是成本更低、迭代更快的第一步。
2. RAG 核心概念与完整链路
RAG 的完整链路包括七个关键环节:文档加载、分块、向量化、索引存储、检索召回、重排、Prompt 组装与生成。理解这条链路,是后续调优的基础。
文档加载负责从 PDF、Word、Markdown、HTML 等格式中提取纯文本。这个环节看起来简单,却往往是知识库质量的第一个瓶颈,例如 PDF 中的表格、扫描图片、复杂排版,都可能让文本提取结果变得混乱。
分块是把长文本切成合适大小的片段。切得太大会让检索结果包含大量无关信息,还会迅速消耗大模型上下文;切得太小则可能截断语义,造成上下文信息不完整。常见的做法是使用 300-800 字符左右的块大小,并设置少量重叠,让相邻块之间的语义保持连续。
向量化是把文本块转换为高维向量。这个步骤依赖 Embedding 模型,例如 BAAI/bge-large-zh-v1.5、text2vec 等。向量化的核心目标是把语义相近的文本映射到向量空间中的邻近位置,这样搜索向量就能找到语义相关的文档。
索引存储是把向量和原始文本写入向量数据库。常见选择包括 FAISS、Chroma、Milvus、Elasticsearch 等。向量数据库需要支持向量相似度检索,生产环境下还要考虑水平扩展、持久化和过滤能力。
检索召回是用户提问后的第一步处理:把问题也做向量化,然后在向量库中找出最相似的 Top-K 个文本块。更复杂的检索方案会结合关键词检索(如 BM25)构成混合检索,提高召回率。
重排是很多入门教程不讲的环节,但对回答质量影响巨大。向量检索召回的结果只是“初步候选”,排序未必精准。重排模型会逐条计算候选文本与问题的相关性,重新打分排序,把更靠前、更相关的文本块交给大模型。
Prompt 组装与生成是最后一步:把用户问题、检索到的文本块、必要的提示词模板拼装成完整 Prompt,交给大模型生成最终答案。这一步需要控制上下文长度,处理检索结果为空和检索结果互相矛盾的情况。
用一个类比可以帮助理解:RAG 像一场开卷考试。大模型是考生,知识库是教材,向量检索是在教材目录和索引中快速定位相关章节,重排是从若干候选页里筛出最对题的段落,最后考生根据这些段落组织答案。没有 RAG 的大模型相当于闭卷考试,只能依赖训练时记住的知识,遇到没见过的内容就很容易“编答案”。
在企业级项目里,单纯使用原生大模型往往有三个硬伤:知识过时、缺少私有领域知识、幻觉问题难以控制。RAG 针对这三个问题给出了工程化方案。这也是 RAG 近几年成为大模型应用落地主流的根本原因。
3. 环境准备与组件选型
在动手编码之前,需要先确认环境依赖和组件选型。这里的选型原则不仅影响开发效率,也影响后续生产部署时的稳定性和维护成本。
3.1 Python 环境与依赖
RAG 开发一般以 Python 为主。建议使用 Python 3.9 以上版本。以下依赖是常用的:
| 依赖名 | 用途 |
|---|---|
| langchain | 编排 RAG 流程 |
| langchain-community | 第三方集成组件 |
| langchain-openai | 调用 OpenAI 兼容接口 |
| langchain-text-splitters | 文本切分器 |
| sentence-transformers | 加载本地 Embedding 与重排模型 |
| chromadb | 轻量级向量数据库 |
| faiss-cpu | 本地向量索引 |
| pymilvus | Milvus 客户端 |
具体版本以实际安装为准,这里不写死某个版本号,因为 LangChain 生态迭代较快,使用最新稳定版即可。安装命令如下:
pip install langchain langchain-community langchain-openai langchain-text-splitters pip install sentence-transformers chromadb faiss-cpu pymilvus如果下载模型遇到网络问题,可以将 HuggingFace 模型库设置为国内镜像,再执行安装脚本,具体镜像配置方式可由开发者根据网络环境自行调整,这里不做展开。
3.2 Embedding 模型选型
中文场景下,BAAI/bge-large-zh-v1.5、BAAI/bge-small-zh-v1.5 和 text2vec-large-chinese 都是常见选择。选型时需要考虑检索效果和资源占用之间的平衡。
如果知识库文档量大、对实时性要求高,可以优先选择 bge-small 系列,向量维度更低,检索速度更快。如果对准确率要求更高,并且服务器有足够显存,可以考虑 bge-large。bge-v1.5 版本在使用时,通常建议在查询语句前加上“为这个句子生成表示以用于检索相关文章:”这类指令前缀,这会影响最终检索效果。
3.3 向量数据库选型
向量数据库的选型取决于项目阶段和数据规模。
本地学习或小规模 Demo 阶段,可以选用 Chroma 或 FAISS。它们部署简单,适合快速验证链路。数据量达到百万级、并发查询量较大时,更适合选用 Milvus 这类分布式向量数据库,它支持水平扩展、数据持久化和丰富的过滤查询语法。
如果企业已经存在 Elasticsearch 集群,也可以使用 Elasticsearch 的向量检索能力,通过 KNN 查询或 dense_vector 字段实现近似搜索,降低额外组件运维成本。
选型时最忌讳一开始就上重型组件。建议先用轻量级方案把业务链路跑通,再根据性能瓶颈做替换。RAG 工程的主体是流程编排和数据处理,向量数据库只是其中一个环节,替换成本通常低于预期。
3.4 大模型选型与调用方式
大模型可以使用 OpenAI 兼容接口,也可以使用本地部署的模型。生产环境中,企业私有化部署往往选择本地模型以规避数据安全风险。例如通过 Ollama 部署 Qwen 系列开源模型,再以http://localhost:11434/v1作为 base_url 接入 LangChain。
这里的关键是:RAG 对 LLM 的“指令遵循能力”有一定要求。模型需要能从 Prompt 中提取用户问题、阅读检索上下文并判断信息是否足够。如果最终回答经常东拉西扯,可以先用一个指令遵循能力更强的基础模型验证 RAG 链路本身是否正常,再返回排查各环节。
3.5 重排模型
重排模型强烈建议使用 bge-reranker 系列。这类模型基于 Cross-Encoder 结构,对“问题-文档片段”的语义相关性建模更加精准,但推理速度比向量检索使用的双塔模型慢。实际项目中,一般先用向量检索召回 20-50 条候选,再用重排模型筛出最相关的 3-5 条,兼顾效果与性能。
4. 核心流程拆解:从文档到可检索的知识库
搭建 RAG 知识库,最重要的不是写检索代码,而是把文档处理流程质量做得足够高。这一步决定了知识库的上限。
4.1 文档解析与清洗
首先要处理文档格式。以 PDF 为例,注意不要使用简单的二进制文本抽取。更稳妥的做法是使用专门的文档解析库,例如 PyMuPDF、pdfplumber 或企业内部的文档解析服务。解析后要清洗文本中的异常字符、多余空白和无效换行。
表格内容在解析后往往会丢失结构。如果维修手册中的故障代码表被解析成一段混乱的文本,后续检索效果必然受影响。实际项目中,可以采用“结构化优先”的策略:把表格、关键字段以 Markdown 表格或 JSON 行形式保存,作为单独的知识块。
4.2 分块策略
分块是最容易被低估的环节。常见策略包括固定长度分块、递归字符分块、按 Markdown 标题分块和语义分块。不要盲目套用某个固定chunk_size=512,应结合文档结构选择合适的切分方式。
一个比较稳妥的通用方案是使用RecursiveCharacterTextSplitter。它会优先尝试按段落、句子、换行符逐级切分,尽可能保持语义完整性。设置chunk_size=400、chunk_overlap=80是相对保险的起点,后续可以根据检索效果微调。
对于结构清晰的 Markdown 文档,也可以先按标题拆分成小节的正文,再对过长的小节做二次切分,这样可以避免检索结果跨越多个不相关的主题。
4.3 向量化与索引存储
分块完成后,使用 Embedding 模型对每个文本块编码,得到向量,并连同原始文本、元数据一起写入向量数据库。元数据通常包括文档来源、标题、页码、更新时间等,用于后续的过滤和溯源展示。
这里推荐用一个小脚本完成“文档到知识库”的索引构建。下面的示例使用本地 Markdown 文件作为输入:
# 文件路径:ingest.py from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS # 1. 加载文档 loader = TextLoader("kb/company_policy.md", encoding="utf-8") documents = loader.load() # 2. 递归字符切分 splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) chunks = splitter.split_documents(documents) # 3. 中文 Embedding 模型 embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh-v1.5", encode_kwargs={"normalize_embeddings": True} ) # 4. 构建向量索引并保存到本地 vectorstore = FAISS.from_documents(chunks, embeddings) vectorstore.save_local("faiss_index") print(f"共写入 {len(chunks)} 个文本块")运行这段脚本后,本地会生成faiss_index目录,后续查询时直接加载即可。这里使用 FAISS 是为了演示方便,生产环境替换为 Milvus 只需要改动向量库连接部分。
4.4 索引构建的注意点
从工程经验来看,索引构建阶段常见的几个问题值得关注。
第一,文档更新需要重建索引。如果只新增了少量文档,可以采用增量追加的方式;如果文档内容大范围修改,建议整体重建,避免索引中出现过期版本。第二,元数据一定要在入库时写清楚,缺少来源和更新时间,后续做权限过滤和答案溯源时会非常痛苦。第三,向量归一化会影响相似度计算方式,实际使用中保持编码和检索两侧的配置一致。
5. 检索召回优化:不止是向量相似度
很多团队搭建完 RAG 后第一个疑问是:为什么向量检索明明返回了相似结果,模型却答不对?原因往往在于召回环节。
5.1 向量检索的局限
向量检索的核心假设是:语义相近的文本在向量空间中也相近。但这个假设并不总是成立。企业文档里充满专有名词、缩写和产品编号,例如“PLC-3000”和“可编程逻辑控制器-3000”,在语义上指的是同一个设备,但向量表征未必能充分关联。此外,向量检索对短文本匹配不够敏感,关键词精确命中有时比语义相似更重要。
针对这些局限,需要引入混合检索和过滤条件。
5.2 混合检索(BM25 + 向量检索)
BM25 是经典的关键词检索算法,擅长处理精确匹配场景。混合检索的思路是:同时执行向量检索和 BM25 关键词检索,将两组结果合并,再交给重排模型统一排序。
在 Elasticsearch 中,可以通过布尔查询同时执行multi_match与knn查询,再使用 RRF(Reciprocal Rank Fusion)算法融合结果。在 LangChain 中,可以使用EnsembleRetriever组合两者:
# 文件路径:hybrid_retriever.py from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever from langchain_community.vectorstores import FAISS from langchain_community.embeddings import HuggingFaceEmbeddings # 模拟一批文档切片 texts = [ "PLC-3000 支持以太网通信接口", "可编程逻辑控制器需要定期检查后备电池", "维修手册第一章介绍安全注意事项" ] # BM25 检索器 bm25_retriever = BM25Retriever.from_texts(texts) bm25_retriever.k = 4 # 向量检索器 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5") vectorstore = FAISS.from_texts(texts, embeddings) vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # 混合检索,权重各 0.5 ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.5, 0.5] ) results = ensemble_retriever.invoke("PLC-3000 通信接口是什么?") for r in results: print(r.page_content)混合检索能较好地兼顾“语义相似”和“关键词精确匹配”两种场景。实际项目中,两个检索器的返回数量一般分别设置为 10-20,再通过重排筛选到 3-5,效果较为稳定。
5.3 元数据过滤与权限控制
在企业知识库中,元数据过滤几乎是必备能力。例如:只检索某部门文档、只检索最近一年更新的内容、只检索当前用户有权限查看的文档。这些需求不能靠“再检索后过滤”实现,而应该在向量检索阶段就利用元数据过滤下推。
在 Milvus 中,可以通过布尔表达式过滤标量字段;在 Elasticsearch 中,可以通过filter条件限制检索范围。这样既加快检索速度,又避免了权限信息泄露到大模型上下文。
5.4 检索参数调优
两个最基础的参数是 Top-K 和相似度阈值。Top-K 过小会漏掉关键文档,过大又会把噪声引入上下文。阈值设置需要在实际数据上验证,不要想当然地设成 0.7 或 0.8。一个可行的调试方法:人工准备 50 条问题,逐一查看召回的 Top-5 结果,统计准确率,再反向调整参数。
6. 重排:决定回答质量的关键一环
重排是 RAG 全链路中最容易被忽略、却对最终质量影响最大的模块。理解重排,能让 RAG 效果提升一个档次。
6.1 为什么需要重排
向量检索阶段使用的双塔模型(bi-encoder)会提前把文档编码成向量,查询时计算向量相似度。这种设计换来了高效率,却牺牲了查询和文档之间的深层交互。
双塔模型的问题在于:查询和文档在编码时是独立的,它们的“交互”只发生在最后的向量相似度计算中。对于同一句话,不同的表达方式可能被编码成差异较大的向量。重排阶段的 Cross-Encoder 模型则完全不同:它将查询和文档拼接成一个序列输入模型,在每一层 Transformer 中充分交互,因此能够更准确地判断“这段文档是否真的回答问题”。
6.2 双塔模型与 Cross-Encoder 对比
| 对比维度 | 双塔模型(向量检索) | Cross-Encoder(重排) |
|---|---|---|
| 编码方式 | 查询和文档分别编码 | 查询和文档拼接后编码 |
| 训练效率 | 可提前向量化,效率高 | 无法提前缓存,速度慢 |
| 交互深度 | 浅层交互(相似度计算) | 深层交互(多层注意力) |
| 适用阶段 | 召回大量候选 | 精排少量候选 |
| 典型模型 | bge-small、text2vec | bge-reranker |
所以,工程上通常采用两阶段级联:向量检索先召回 20-50 个候选,重排模型再把候选精排到 3-5 个。这样既控制了重排模型的调用开销,又显著提高了最终送入大模型的上下文质量。
6.3 常用重排模型
中文场景下,推荐 bge-reranker-base 或 bge-reranker-v2-m3。bge-reranker-v2-m3 对多语言支持更强,适合中英混合文档。在线重排服务也有更强大的商业模型,适合不介意数据出域的团队。本地模型更安全,成本也更可控。
6.4 重排代码实现
LangChain 提供了ContextualCompressionRetriever加CrossEncoderReranker的组合方式,代码非常简洁:
# 文件路径:rerank_retriever.py from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder from langchain_community.vectorstores import FAISS from langchain_community.embeddings import HuggingFaceEmbeddings # 加载已有的向量库 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5") vectorstore = FAISS.load_local( "faiss_index", embeddings, allow_dangerous_deserialization=True # 仅本地可信数据可开启 ) base_retriever = vectorstore.as_retriever(search_kwargs={"k": 10}) # 加载重排模型 reranker = CrossEncoderReranker( model=HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base"), top_n=3 ) compressed_retriever = ContextualCompressionRetriever( base_compressor=reranker, base_retriever=base_retriever ) docs = compressed_retriever.invoke("设备故障代码 E-302 怎么处理?") for doc in docs: print(doc.page_content[:100])这里先使用search_kwargs={"k": 10}召回 10 条候选,再由重排模型筛选出最有价值的 3 条。如果要更严格地控制质量,可以把召回数量提高到 20,甚至更大。
6.5 重排的实际效果
从项目实践观察,加入重排后,RAG 回答质量的提升往往非常明显。最直观的变化是:模型不再经常引用“看起来相关但实际不解决问题”的段落。尤其是维修手册、法律条文这类表达严谨、术语密集的文档,重排的增益尤其明显。
7. 完整 RAG 查询示例:检索、重排到生成
前面已经分别实现了索引构建、混合检索和重排,现在把它们串成一个完整的查询流程。下面这个脚本可以直接用于验证整体链路。
# 文件路径:rag_query.py from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI # 1. 加载向量库 embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh-v1.5", encode_kwargs={"normalize_embeddings": True} ) vectorstore = FAISS.load_local( "faiss_index", embeddings, allow_dangerous_deserialization=True ) # 2. 基础检索器:召回 10 条候选 base_retriever = vectorstore.as_retriever(search_kwargs={"k": 10}) # 3. 重排压缩器:筛选到 3 条 reranker = CrossEncoderReranker( model=HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base"), top_n=3 ) compressed_retriever = ContextualCompressionRetriever( base_compressor=reranker, base_retriever=base_retriever ) # 4. 构造提示词模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个企业内部知识助手。请只根据提供的上下文回答问题,不要编造不存在的知识。如果上下文不足以回答问题,请明确告诉用户:知识库中没有足够信息。"), ("human", "上下文:\n{context}\n\n问题:{question}") ]) # 5. 初始化大模型(示例使用 Ollama 本地部署的 OpenAI 兼容接口) llm = ChatOpenAI( base_url="http://localhost:11434/v1", api_key="ollama", model="qwen2.5:7b", temperature=0.2 ) # 6. 查询并生成 def ask(question: str): docs = compressed_retriever.invoke(question) context = "\n\n".join([d.page_content for d in docs]) chain = prompt | llm answer = chain.invoke({"context": context, "question": question}) print("问题:", question) print("检索到的片段数量:", len(docs)) print("回答:", answer.content) if __name__ == "__main__": ask("公司年假政策是什么?")运行验证的预期结果是:问题被正常输入,检索模块从知识库中定位到相关片段,大模型基于这些片段生成一个条理清晰的回答。如果检索到的片段数量为 0,说明文档索引或查询词不匹配,需要检查向量库内容。如果回答与巡检片段无关,需要检查重排模型和 Prompt 模板。
为了验证重排是否起作用,可以临时把compressed_retriever换成base_retriever,对比同一个问题的回答质量。这种 AB 对比方式非常直观。
8. 工程化落地:从 Demo 到生产环境
把 RAG 从本地脚本变成生产服务,还需要解决知识更新、权限安全、性能、可观测性和评估指标等问题。
8.1 知识更新机制
知识更新是生产系统最频繁的操作。建议采用以下方式:文档上传后触发解析与分块任务,计算文本块的哈希值,只对新变更的块执行向量化与写入。文档删除时,需要同步删除对应的向量和元数据,避免遗留过期内容。
对于线上系统,推荐采用双索引发布机制:新版本索引构建完成后,先验证抽样查询效果,再切换流量;如果效果异常,可以快速回滚到旧索引。这种思路与灰度发布一致,能显著降低知识更新带来的线上风险。
8.2 权限与安全
企业知识库往往包含敏感数据。权限控制必须前置到检索阶段,而不是在生成之后做过滤。推荐的方案是:在文档入库时为每个文本块打上权限标签,例如部门编码、密级、可见用户组。查询时,先根据当前用户的权限构造过滤表达式,再执行向量检索。
生产环境部署时,还要注意 API 访问鉴权、请求审计和数据加密。对涉及数据库写入、知识库批量更新的操作,应当在测试环境先行验证,并保留备份,以便在异常时快速恢复。
8.3 缓存与性能
RAG 查询链路中,Embedding 计算和重排是相对耗时的步骤。对于高频相似问题,可以引入查询结果缓存。例如对用户问题进行规范化处理后计算哈希,命中缓存则直接返回历史答案。
面对高并发场景,需要对向量检索和模型推理做异步化处理。向量数据库支持连接池和并发查询,LLM 推理则需要做好并发限流,避免模型服务被打崩。实际项目中,通常采用请求队列或令牌桶限制并发数。
8.4 可观测性与日志
生产环境必须记录每一个查询的完整链路信息:原始问题、检索候选数量、重排后的片段、最终回答和耗时。这些日志一方面用于问题排查,另一方面也是后续评估知识库质量的数据基础。
一旦用户反馈回答质量差,可以快速定位是哪一环出了问题:如果检索结果本身就不相关,问题大概率在分块策略和 Embedding 模型;如果检索结果相关但回答错误,问题可能在 Prompt 模板或 LLM 能力。
8.5 评估指标
RAG 评估不能只凭主观感觉。目前常用的框架是 RAGAS,核心指标包括:
| 指标 | 含义 | 关注点 |
|---|---|---|
| Faithfulness(忠实度) | 回答是否基于检索上下文,而非模型幻觉 | 防幻觉效果 |
| Answer Relevancy(答案相关性) | 回答是否切题 | 用户体验 |
| Context Relevancy(上下文相关性) | 检索到的上下文是否与问题相关 | 检索质量 |
建议在项目初始化阶段就建立一套包含几百条问题的评测集,覆盖常见问题、边界问题和无答案问题,每次调整 RAG 链路后都运行评测集,对比指标变化。这样能让优化工作有据可依,而不是反复靠人工抽检。
9. 常见问题与排查方法
RAG 系统的问题通常不是单一原因造成的,下面汇总了几类高频问题及其排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 检索结果总是无关 | 文档解析质量差或分块不合理 | 打印召回片段,人工检查相关性 | 优化解析规则,调整分块大小和重叠 |
| 关键词精确匹配不到 | 仅使用向量检索 | 检索“产品编号”类短词测试 | 引入 BM25 混合检索 |
| 回答正确但引用原文不准确 | 重排未生效或阈值过低 | 对比开启重排前后的 Top-N 片段 | 加入 Cross-Encoder 重排并调整 top_n |
| 模型回答编造答案 | Prompt 未约束或上下文不足 | 检查模型输出的引用来源 | 在 Prompt 中增加“仅凭上下文回答”约束 |
| 知识更新后检索到旧内容 | 索引没有及时同步 | 查看索引构建日志和更新时间 | 实现增量更新或双索引切换 |
| 响应速度慢 | 重排候选过多或 LLM 推理慢 | 观测各环节耗时 | 降低召回数、加入缓存、异步化处理 |
最有效的排查方法,是把整个 RAG 链路拆开,每个环节单独验证。先用一条测试问题走读“文档解析-分块-向量检索-重排-Prompt”每一步的输出,往往很快就能发现问题所在。
10. 工程建议与后续实践方向
如果已经跟着示例跑通了一套 RAG 流程,接下来可以从三个方向继续深入。
第一,把向量数据库从 FAISS 替换为生产级的 Milvus,并验证元数据过滤和增量更新。第二,建立标注评测集,引入 RAGAS 指标来量化每次改动的影响,避免“调参靠感觉”。第三,针对业务文档特点定制分块方案,例如把 Markdown 标题、表格结构融入切分逻辑,让知识块更贴近真实语义边界。
在实际项目中,更推荐的思路是把 RAG 当作一个持续迭代的数据系统来对待,而不是一次性的模型应用。知识库质量、检索质量、重排质量和生成质量这四个层面都需要有对应的数据反馈和评估机制。后续接触 Agentic RAG 或多跳检索时,也是基于这套基础能力演化而来。
建议先把文档处理流程和检索验证做扎实,再逐步引入更复杂的重排、混合检索和在线学习机制。这套链路沉淀下来的能力,在企业知识问答、智能客服、个人助理等场景中都能复用。