资讯动态

RAG知识库问答实战:从原理到代码落地

发布时间:2026/10/21 2:49:03 来源:尧图企业网站定制
前一阵子在做知识库问答项目时发现网上 RAG 相关的教程虽然很多但大部分要么只讲原理、不给可运行代码要么代码能跑但不解释为什么这么设计。对刚接触大模型应用开发的同学来说很容易卡在“原理看懂了一动手就报错”的阶段。这篇文章打算把 RAG检索增强生成从原理到落地完整梳理一遍。内容包括RAG 要解决什么问题、整体流程如何拆解、向量库选型、召回质量优化以及一个可以直接复制的知识库问答实战项目。零基础可以按顺序跟着做有基础的可以直接跳到实战部分或常见问题排查。1. 什么是 RAG背景与核心原理1.1 大模型的两个天然短板先看一个基础问题为什么不能直接把文档丢给大模型让它回答因为大模型本身有两个明显局限知识有时间截断。模型训练完成后新产生的内容它不知道。比如你问它公司内部最新的值班安排它只能“编”。无法访问私有数据。企业内部文档、产品手册、个人笔记这些数据不可能进入大模型训练集模型自然无从回答。如果硬要让模型回答它可能会用自己的“语言惯性”补全一个看起来合理、但实际错误的答案。这就是所谓的“一本正经地胡说八道”。1.2 RAG 的核心思路RAG 的全称是Retrieval-Augmented Generation即检索增强生成。它的思路很简单不在生成阶段强迫模型记住所有知识而是在生成之前先从外部知识库中检索出与用户问题最相关的内容片段把这些片段作为参考上下文一起交给大模型让模型基于这些材料组织答案。对比来看方案做法问题直接提问问什么答什么无法回答私有知识和新知识微调Fine-tuning用额外数据训练模型参数成本高、周期长、知识更新慢RAG检索相关资料拼进 Prompt 再生成检索质量决定回答质量RAG 的重要优势是知识可随时增删新增文档只需要重新建立索引不需要重新训练模型。这也是为什么 RAG 成为企业构建知识库问答首选方案的原因。1.3 RAG 的典型应用场景企业内部知识库问答制度、流程、产品资料。电商客服自动问答商品信息、售后政策。教育辅导教材内容问答、题库检索。代码仓库问答根据项目文档回答开发问题。舆情分析、竞品分析等需要结合外部资料的场景。凡是“模型本身不知道但你有资料”的问题都适合用 RAG 解决。2. RAG 完整流程拆解把 RAG 拆开来看整个流程分成两个阶段。2.1 索引阶段离线处理这个阶段的任务是“把知识变成可检索的形式”。原始文档 ↓ 文档加载PDF / Word / Markdown / HTML ↓ 文本切分Chunking ↓ Embedding 向量化 ↓ 存入向量数据库2.2 查询阶段在线问答这个阶段的任务是“根据用户问题检索相关内容并生成回答”。用户提问 ↓ 问题 Embedding 向量化 ↓ 在向量库中做相似度检索 ↓ 获取 TopK 相关文档片段 ↓ 组装 Prompt问题 检索片段 ↓ 大模型生成最终回答整个链路中有一个关键点检索出来的内容质量直接决定最终回答质量。如果检索到的片段和问题无关大模型再聪明也答不对。3. 环境准备与依赖安装3.1 运行环境说明本文示例以 Python 3.10 为主操作系统不限Windows / macOS / Linux 均可。版本不需要完全一致但建议 Python 不低于 3.9。核心依赖库如下pip install langchain langchain-community langchain-huggingface pip install faiss-cpu pip install sentence-transformers pip install chromadb pip install pypdf说明几点langchain目前已经做了模块拆分旧教程里的from langchain.embeddings import ...写法在新版本中可能失效需要注意。向量数据库选择 FAISS 做示例因为它是本地库、无需启动服务、对入门友好。Embedding 模型使用国产开源的BAAI/bge-small-zh-v1.5中文效果比较稳定。3.2 LLM 调用方式本文示例默认使用 OpenAI 兼容接口。如果你使用的是国产大模型平台或本地部署模型只要接口风格兼容都可以调整base_url和api_key替换。from langchain_openai import ChatOpenAI llm ChatOpenAI( model你的模型名称, api_key你的API Key, base_url你的接口地址, temperature0.1 )这里只做演示说明具体配置以你实际使用的服务商为准。4. 核心知识点拆解从 Chunk 到 Token4.1 文本切分Chunking文本切分是 RAG 流程中最容易被忽视、但影响最大的一步。如果切得太粗一个 chunk 包含太多无关内容检索命中后噪声大如果切得太细语义被切断单个片段信息量不足。常用的切分策略固定长度切分按字符数或 token 数切割简单但容易切断语义。按结构切分Markdown 按标题、PDF 按段落、代码按函数模块切分。递归切分LangChain 的RecursiveCharacterTextSplitter优先按段落、再按句子、最后按字符逐级回退切分是当前推荐做法。from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , , ] )chunk_overlap的作用是让相邻片段保留一定重复内容避免某个语义点恰好落在切分边界上被“切断”。4.2 Embedding把文字变成向量Embedding 模型的作用是把文本变成一维浮点数数组。语义越相近的文本在向量空间中距离越近。以 bge-small-zh-v1.5 为例from langchain_huggingface import HuggingFaceEmbeddings embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} )注意normalize_embeddings参数。相似度计算方式通常是余弦相似度归一化之后使用点积即可等价于余弦相似度一些向量库的默认距离度量是 L2归一化有助于保证检索一致性。4.3 向量数据库的选型思路向量数据库用于存储文本对应的向量并提供近似最近邻检索。常见选型方案特点适合场景FAISS本地库轻量速度快入门、原型验证、中小规模Chroma本地库API 简洁快速开发、原型项目Milvus分布式适合大规模生产环境、千万级向量Weaviate自带推理能力全功能生产方案QdrantRust 实现性能好生产环境入门阶段建议先用 FAISS把流程跑通后再按需要切换。4.4 检索相似度计算与 TopK检索的本质是把用户问题向量化在向量库中查找最相似的 K 个片段。常见距离度量余弦相似度衡量方向是否一致文本检索最常用。欧式距离L2数值越小越相似。内积IP配合归一化向量时等价于余弦。FAISS 默认使用 L2 距离构建索引时也可以指定内积import faiss dimension 512 index faiss.IndexFlatIP(dimension) # 内积需要预先归一化向量4.5 重排序Rerank向量检索得到的是“语义相近”的 TopK但语义相近不等于答案相关。比如用户问“报销流程”向量检索可能召回“报销制度总则”这类宽泛内容真正包含流程步骤的段落反而排在第 6 位。重排模型Cross-Encoder可以把(问题, 文档片段)拼接起来精细打分比双塔向量模型更准确。from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-base) pairs [(query, doc) for doc in docs] scores reranker.predict(pairs)4.6 Prompt 组装策略Prompt 决定了模型如何利用检索到的内容。建议结构你是一个知识库问答助手请根据以下资料回答问题。 如果资料中没有相关内容请直接说明“知识库中未找到相关内容”不要编造。 【资料开始】 {检索到的内容} 【资料结束】 用户问题{用户问题}设置“找不到就说不知道”的指令是防止大模型幻觉的最有效手段之一。5. 完整实战构建一个本地知识库问答系统下面开始搭建一个最小可运行的 RAG 项目。5.1 项目结构rag-project/ ├── docs/ # 原始文档存放目录 │ └── 产品手册.md ├── ingest.py # 索引构建脚本 ├── query.py # 问答脚本 ├── requirements.txt # 依赖清单 └── vector_store/ # FAISS 索引保存目录5.2 准备测试文档在docs/目录创建一份简单的产品手册文档内容如下# 智能考勤机使用说明 ## 打卡方式 - 支持人脸识别打卡、指纹打卡、IC卡打卡三种方式。 - 人脸识别支持佩戴口罩识别。 ## 请假流程 1. 员工在OA系统提交请假申请。 2. 部门主管审批。 3. 人事备案。 ## 常见问题 - 打卡失败时请检查网络连接。 - 指纹磨损用户建议使用人脸识别。这是非常典型的说明类文档适合验证 RAG 效果。5.3 编写索引构建脚本文件路径rag-project/ingest.pyimport os from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS # 1. 加载文档 loader TextLoader(docs/产品手册.md, encodingutf-8) documents loader.load() # 2. 切分文本 text_splitter RecursiveCharacterTextSplitter( chunk_size200, chunk_overlap20, separators[\n\n, \n, 。, , , , ] ) chunks text_splitter.split_documents(documents) print(f切分完成共 {len(chunks)} 个片段) # 3. 向量化 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} ) # 4. 构建向量库并保存 vector_store FAISS.from_documents(chunks, embeddings) vector_store.save_local(vector_store/faiss_index) print(索引构建完成已保存至 vector_store/faiss_index)运行python ingest.py第一次运行会下载 BGE 模型需要保持网络畅通。下载完成后输出类似切分完成共 6 个片段 索引构建完成已保存至 vector_store/faiss_index5.4 编写问答脚本文件路径rag-project/query.pyfrom langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough # 1. 加载向量库 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} ) vector_store FAISS.load_local( vector_store/faiss_index, embeddings, allow_dangerous_deserializationTrue ) # 2. 初始化 LLM llm ChatOpenAI( model你的模型名称, api_key你的API Key, base_url你的接口地址, temperature0.1 ) # 3. 定义 Prompt prompt ChatPromptTemplate.from_template( 你是一个知识库问答助手请根据以下资料回答用户问题。 如果资料中没有相关内容请直接回答“知识库中未找到相关内容”不要编造。 【资料开始】 {context} 【资料结束】 用户问题{question} ) # 4. 定义检索器 retriever vector_store.as_retriever( search_typesimilarity, search_kwargs{k: 3} ) # 5. 构建 RAG 链路 def format_docs(docs): return \n\n.join([doc.page_content for doc in docs]) rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) # 6. 测试问答 while True: question input(请输入问题输入 exit 退出) if question.lower() exit: break response rag_chain.invoke(question) print(\n回答, response) print(- * 50)5.5 运行验证python query.py依次测试几个问题请输入问题请假流程是什么 回答根据资料请假流程为1. 员工在OA系统提交请假申请2. 部门主管审批3. 人事备案。 请输入问题打卡失败怎么办 回答打卡失败时请检查网络连接。指纹磨损用户建议使用人脸识别。 请输入问题公司年假政策是什么 回答知识库中未找到相关内容。最后一个回答很关键。如果知识库没有相关内容模型明确告诉你“没有找到”而不是强行编造一个答案。这说明 RAG 的基础链路已经跑通了。6. 召回效果优化从“能用”到“好用”基础流程跑通之后下一步考虑如何优化。6.1 问题在于检索质量当回答质量不好时先不要怀疑大模型绝大多数情况是检索出了问题。常见的检索问题召回内容不相关向量相似但语义关联弱。关键信息被切碎答案跨多个 chunk单个片段都不完整。TopK 太小正确内容排在第 5但只取了前 3。6.2 优化一结合元数据过滤向量检索之外可以结合文档的标题、来源、更新时间等元数据做前置过滤。retriever vector_store.as_retriever( search_kwargs{k: 5, filter: {source: docs/产品手册.md}} )比如企业知识库中包含多份文档优先在“产品手册”范围内检索而不是全库模糊匹配。也可以先做关键词硬过滤再向量检索。6.3 优化二引入重排序先向量检索召回 10 条再用 Rerank 模型重新打分取前 3 条作为最终上下文。这种方式在中文文档场景里提升非常明显。from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-base) def rerank_documents(query, documents, top_n3): pairs [(query, doc.page_content) for doc in documents] scores reranker.predict(pairs) ranked sorted(zip(documents, scores), keylambda x: x[1], reverseTrue) return [doc for doc, score in ranked[:top_n]]6.4 优化三查询改写用户问题往往是口语化的比如“刷脸打不了卡怎么办”。直接拿原问题去检索效果未必理想。可以先用 LLM 把问题改写成更适合检索的形式。请根据对话语义改写成适合检索的查询语句。 原始问题刷脸打不了卡怎么办 改写结果改写后的查询可能是“人脸识别打卡失败 解决办法”。这种手段在真实场景中经常能明显提升检索命中率。6.5 优化四Hybrid 搜索关键词 向量有时用户问题中包含精确的专有名词比如型号“WF-200”向量检索可能把它和“考勤机”混在一起。此时结合 BM25 关键词检索再融合两路结果效果更稳定。LangChain 中的EnsembleRetriever可以实现from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever from langchain_community.vectorstores import FAISS bm25_retriever BM25Retriever.from_documents(chunks) bm25_retriever.k 3 vector_retriever vector_store.as_retriever(search_kwargs{k: 3}) ensemble_retriever EnsembleRetriever( retrievers[bm25_retriever, vector_retriever], weights[0.5, 0.5] )两路召回结果按权重融合排序取最终 TopK。6.6 优化五调整 Chunk 策略如果发现单个片段信息量不足可以适当调大chunk_size如果噪声大就调小。也可以按文档结构智能切分保证每个 chunk 是语义完整的段落。一个可供参考的方向是让 chunk 保留文档标题作为上下文# 智能考勤机使用说明 ## 请假流程 1. 员工在OA系统提交请假申请。 2. 部门主管审批。 3. 人事备案。这样可以增强检索时的语义定位能力。7. 常见问题与排查思路问题现象常见原因解决思路检索结果明显不相关Embedding 模型不匹配语言场景中文场景换 BGE 等中文向量模型回答内容编造Prompt 缺少“不知道就说不知道”约束在 Prompt 中加入防幻觉指令回答内容不全Chunk 切分切断了关键语义增加 overlap或按结构切分检索命中但答案不对TopK 太小正确内容在排名后面调大召回数量增加 Rerank启动报bge-small-zh下载失败网络无法访问 HuggingFace配置镜像源或手动下载模型到本地目录FAISS 加载报错langchain 版本变化检查allow_dangerous_deserialization参数回答速度太慢检索 生成链路过于冗余精简单个环节减少 context 长度再补充一个重点向量库加载时的安全参数。FAISS.load_local( vector_store/faiss_index, embeddings, allow_dangerous_deserializationTrue )allow_dangerous_deserialization是为了兼容新版 LangChain 的安全策略。这个参数只应在你信任自己的索引文件时开启。如果索引来自外部来源存在反序列化攻击风险不要随意加载。8. 工程化落地与最佳实践从“Demo 能跑”到“生产可用”中间还有不少工作。8.1 数据入库前的清洗文档加载前建议先做一次内容清洗去除页眉页脚、水印、导航栏信息。统一编码为 UTF-8。删除重复内容。表格结构尽量转成 Markdown 或 JSON 格式化文本。生产环境中数据决定效果上限模型只是一种解码器。脏数据入库再强的 Rerank 也救不回来。8.2 面向生产的向量库选型FAISS 适合单机原型验证。生产环境需要考虑数据规模百万级以上选择 Milvus、Qdrant 等分布式方案。权限隔离不同部门的数据需要独立隔离。高可用向量库需要主从部署、备份恢复方案。增量更新新增文档需要支撑准实时索引更新。8.3 评估体系搭建不要只凭“感觉回答变好了”来判断优化效果。建议建立简单评估集{ queries: [ {question: 请假流程是什么, expected: OA提交、主管审批、人事备案}, {question: 三种打卡方式是什么, expected: 人脸识别、指纹、IC卡} ] }对每个问题评估两个维度检索命中率期望答案是否出现在召回的 TopK 片段中。生成准确率模型回答是否包含期望要点。每次调整 chunk 大小、检索器、Prompt 之后用同一组测试集对比分数保证优化方向可量化。8.4 权限与安全企业知识库问答涉及内部数据有几点必须注意在检索层做数据权限隔离不能让普通员工检索到高管文档。对模型输出做敏感信息脱敏。涉及删除或重建索引时先备份向量库文件。API Key 不要写进代码仓库使用环境变量或密钥管理服务。8.5 成本控制思路RAG 的成本主要来自 Embedding 和 LLM 两部分文档 Embedding 是一次性成本可在离线任务中执行。在线问答的 LLM 调用与上下文长度成正比。上下文越长成本越高、延迟越大。控制 TopK 数量、精简 Prompt 模板、过滤重复信息都可以减少 token 消耗。一个实用的做法是系统给用户返回答案时同时引用来源文档段落。既方便用户核对也帮助定位检索质量问题。9. 总结与下一步学习路线到这里你已经完成了一个最小可运行的 RAG 知识库问答系统理解了 RAG 的原理掌握了文本切分、向量化、向量检索、Prompt 组装整条链路也知道了召回优化和工程落地的关键方向。下一步可以继续深入的方向深入研究你的场景数据不同类型的文档和数据适合不同的切分和检索策略没有万能配置。尝试不同的 Embedding 模型BGE、text-embedding-3-small、M3E 各有特点在自己的验证集上横向对比。引入 Agent 能力让 RAG 链路具备多步推理和工具调用能力处理复杂问题。学习 LangChain/Llamaindex 的进阶组件比如文档问答、多轮对话中的上下文管理。如果文章对你有帮助收藏备用也欢迎在评论区交流在实际落地中遇到的问题。动手跑一遍上面的代码比阅读十篇文章收获都大。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑