公司动态

RAG技术解析:从向量检索到智能问答,构建企业级知识助手

📅 2026/8/15 6:17:39
RAG技术解析:从向量检索到智能问答,构建企业级知识助手
1. 从“幻觉”到“靠谱”为什么我们需要RAG如果你最近在折腾大语言模型不管是ChatGPT、Claude还是开源的Llama、Qwen大概率都遇到过同一个让人头疼的问题模型一本正经地胡说八道。你问它一个非常具体、需要精确数据或内部知识的问题它可能会给你编造一个看似合理但完全错误的答案。比如你问“我们公司2024年Q2的销售额是多少”它可能会根据训练数据里其他公司的模式给你杜撰一个数字。这种现象在AI圈里被称为“幻觉”。对于企业应用来说这种不确定性是致命的没人敢把关乎决策的关键信息交给一个会“编故事”的系统。那么怎么让大模型变得“靠谱”起来让它能基于我们指定的、准确的信息来回答问题呢这就是检索增强生成技术要解决的核心问题。简单来说RAG就是给大模型配了一个“外接硬盘”和一个“智能秘书”。这个“外接硬盘”里存储着你所有的专属知识库公司文档、产品手册、个人笔记等而“智能秘书”则在你提问时快速地从硬盘里找到最相关的几份资料递给大模型并告诉它“嘿回答这个问题时请主要参考这几份文件。” 这样一来大模型生成答案的“原料”就从它那庞大但可能过时、不精确的预训练数据变成了你提供的、实时且准确的上下文信息。它的回答自然就变得有据可依大大减少了“幻觉”。RAG不是要取代大模型的推理能力而是给它“赋能”。模型依然负责理解和组织语言进行流畅的生成但信息的来源被牢牢地锚定在了你提供的知识上。这就像一位学识渊博但记忆可能模糊的教授在演讲前拿到了最新、最准确的讲义其输出的质量和可信度将得到质的提升。无论是构建智能客服、企业内部知识助手还是个人学习研究工具RAG都是当前将大模型能力安全、可控地落地到具体业务场景中最主流、最实用的技术路径。接下来我们就拆开这个“黑盒”看看它到底是怎么工作的以及如何亲手搭建一个。2. RAG系统核心三要素检索器、向量库与生成器一个完整的RAG系统其工作流程可以清晰地划分为三个核心环节它们环环相扣共同确保了答案的准确性与相关性。理解这三部分是进行任何实战操作的基础。2.1 知识库的预处理与向量化把文档变成模型能“读懂”的数学在你把一堆PDF、Word或网页丢给系统之前需要先对它们进行一番“精加工”。这个过程的目标是将非结构化的文本数据转化为便于计算机检索和模型理解的格式。第一步是文本分割。你不能把一整本几百页的产品手册直接塞给系统。一方面大模型有上下文长度限制另一方面检索时需要精准定位。常见的做法是按语义进行“切片”。例如按段落、按章节分割或者使用更智能的滑动窗口法在保证片段完整性的同时允许少量重叠以避免在边界处丢失关键信息。比如一个关于“蓝牙配对”的段落如果恰好被从中间切断那么检索时可能就无法获取完整信息。分割后的文本片段就会进入核心环节——向量化。这是让计算机“理解”文本含义的关键。我们使用一个称为“嵌入模型”的神经网络将每一段文本转换成一个固定长度的数字列表也就是“向量”或“嵌入”。这个向量的神奇之处在于语义相似的文本其对应的向量在数学空间里的“距离”也会很近。例如“如何连接蓝牙设备”和“蓝牙配对步骤”这两个句子尽管字面不同但向量会很接近。目前常用的开源嵌入模型有text-embedding-ada-002的同类开源模型如BGE、M3E系列、Sentence Transformers等。选择时需要考虑对中文的兼容性、向量维度如384维、768维以及性能。这些文本向量连同它们对应的原始文本片段会被存储到专门的向量数据库中。你可以把它想象成一个存储了所有文档“数学指纹”的仓库。流行的选择包括Chroma轻量易用、Milvus功能强大、适合生产环境、Qdrant性能优异、PGVector基于PostgreSQL便于集成等。至此你的知识库就从一堆文件变成了一个结构化的、可查询的向量集合。2.2 检索器的工作逻辑如何找到最相关的信息当用户提出一个问题时系统并不会直接去翻原始文档。检索器的工作是将用户的查询问题用同样的嵌入模型转化为一个查询向量。然后它向向量数据库发起一次“最近邻搜索”。算法会计算查询向量与库中所有存储向量之间的“距离”常用余弦相似度或欧氏距离并返回距离最近的K个向量所对应的原始文本片段。这个K值通常是个超参数比如5或10意味着检索出最相关的5到10段文本。这里就涉及到一个关键技巧检索的质量直接决定了最终答案的上限。如果检索器找不到相关文档再强的大模型也巧妇难为无米之炊。因此优化检索是RAG实战中的重中之重。除了基础的语义搜索进阶策略还包括混合检索结合基于关键词的传统检索如BM25和向量检索取长补短。关键词检索能保证字面匹配向量检索保证语义匹配。重排序初步检索出较多结果如20个后用一个更精细但更耗资源的模型称为重排序器对它们进行二次打分和排序选出最精准的Top-K个。这能有效提升召回结果的质量。元数据过滤在存储向量时附带文档来源、章节、日期等元数据。检索时可以加入过滤条件如“仅搜索2024年的产品手册”使检索范围更精准。2.3 生成器的上下文构建与答案合成给模型“划重点”检索器返回的几段文本我们称之为“上下文”。但这几段文本不能直接堆砌给大模型。我们需要将它们连同用户的原始问题按照一定的格式组织成一个清晰的“提示”也就是Prompt。一个典型的Prompt模板如下请基于以下提供的上下文信息回答用户的问题。如果上下文中的信息不足以回答问题请直接说明“根据已知信息无法回答该问题”不要编造信息。 上下文 {context_1} {context_2} ... {context_k} 问题{question} 请给出回答这个Prompt设计有几个要点明确指令开头就告诉模型“基于以下上下文”这是约束其回答范围的核心指令。提供上下文将检索到的文本片段清晰罗列。强调诚实要求模型在信息不足时承认这是对抗“幻觉”的重要防线。清晰的问题分隔。这个大Prompt会被送入大语言模型。模型会基于其强大的语言理解和生成能力消化这些上下文然后组织语言生成一个连贯、准确且基于给定证据的答案。整个过程中模型自身的参数不会被修改它只是在“阅读”你给的材料后作答。这就是“增强生成”的含义——用外部检索的信息来增强生成过程的质量和可靠性。3. 从零搭建一个RAG问答系统以LangChain为例理论讲完了我们动手实现一个最简单的RAG系统。这里我们选择LangChain这个流行的框架它像“乐高积木”一样将RAG的各个组件模块化让我们能快速搭建原型。本例将使用Chroma作为向量库BGE嵌入模型和Qwen开源大模型。3.1 环境准备与依赖安装首先确保你的Python环境建议3.8以上并安装必要的库。我们将使用langchain的核心包、社区集成包以及chromadb。pip install langchain langchain-community langchain-chroma pip install sentence-transformers # 用于BGE嵌入模型 pip install chromadb pip install transformers # 用于运行Qwen模型如果使用API则不需要如果你打算使用本地部署的Qwen模型还需要安装相应的加速库如torch。为了简化我们也可以先使用Ollama本地运行模型或者使用通义千问、DeepSeek等提供的API。这里假设我们使用Ollama运行Qwen2.5:7b模型。# 安装Ollama (请参考Ollama官网) # 拉取Qwen模型 ollama pull qwen2.5:7b3.2 构建向量知识库加载、分割与存储假设我们有一个名为knowledge_base.pdf的文档。第一步是加载并处理它。from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_chroma import Chroma # 1. 加载文档 loader PyPDFLoader(./knowledge_base.pdf) documents loader.load() # 2. 分割文本 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个片段大约500字符 chunk_overlap50, # 片段间重叠50字符避免断句 separators[\n\n, \n, 。, , , , , , ] # 中文分割符 ) texts text_splitter.split_documents(documents) print(f原始文档被分割成了 {len(texts)} 个文本片段。) # 3. 初始化嵌入模型 # 使用BGE的中文模型模型会自动从Hugging Face下载 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, # 一个优秀的中文小模型 model_kwargs{device: cpu}, # 使用CPU有GPU可改为cuda encode_kwargs{normalize_embeddings: True} # 标准化向量便于余弦相似度计算 ) # 4. 创建并持久化向量数据库 vectorstore Chroma.from_documents( documentstexts, embeddingembeddings, persist_directory./chroma_db # 向量库保存到本地目录 ) vectorstore.persist() # 确保数据写入磁盘 print(向量知识库构建完成已保存至 ./chroma_db)注意chunk_size的选择需要权衡。太小会丢失上下文太大会包含无关信息且可能超出模型上下文窗口。通常需要根据你的文档类型和模型窗口进行调试。对于中文按字符数计算更合理。3.3 实现检索与生成链连接所有组件知识库建好后我们需要组装检索和生成的流水线。from langchain.chains import RetrievalQA from langchain_community.llms import Ollama # 使用Ollama本地模型 # 如果使用API例如DeepSeek可以这样 # from langchain_community.chat_models import ChatOpenAI # llm ChatOpenAI(modeldeepseek-chat, api_keyyour_key, base_urlhttps://api.deepseek.com) # 1. 加载已存在的向量数据库 embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-small-zh-v1.5) vectorstore Chroma( persist_directory./chroma_db, embedding_functionembeddings ) # 2. 将向量库转换为检索器这里设置返回最相关的4个片段 retriever vectorstore.as_retriever(search_kwargs{k: 4}) # 3. 初始化大语言模型 llm Ollama(modelqwen2.5:7b, temperature0.1) # temperature调低让输出更确定 # 4. 创建RAG链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最常用的类型将所有上下文“塞”进Prompt retrieverretriever, return_source_documentsTrue, # 返回源文档便于追溯 chain_type_kwargs{ prompt: PROMPT # 这里可以传入自定义的Prompt模板后续会讲 } ) # 5. 进行问答 query 我们公司的主打产品是什么有什么特点 result qa_chain.invoke({query: query}) print(问题, query) print(答案, result[result]) print(\n--- 参考来源 ---) for i, doc in enumerate(result[source_documents]): print(f[{i1}] {doc.page_content[:200]}...) # 打印前200字符这段代码的核心是RetrievalQA链。它内部自动完成了我们之前描述的过程用retriever检索上下文用llm生成答案。chain_typestuff是最简单直接的方式适合上下文总长度不超过模型限制的情况。如果文档很长可能需要考虑map_reduce、refine等其他更复杂但能处理长文本的链类型。3.4 设计有效的Prompt模板上面代码中我们提到了自定义PROMPT。一个精心设计的Prompt能显著提升效果。我们可以这样定义from langchain.prompts import PromptTemplate template 请严格根据以下提供的上下文信息来回答问题。如果上下文没有提供足够的信息来完整回答问题请明确指出哪些部分无法从给定信息中得出并仅根据已知信息回答。 上下文信息 {context} 问题{question} 请基于上下文给出准确、清晰的回答 PROMPT PromptTemplate( templatetemplate, input_variables[context, question] )这个模板比默认模板更强调了对信息不足情况的处理指令更清晰。在创建qa_chain时将其传入chain_type_kwargs即可。4. 超越基础RAG实战中的性能优化与高级技巧一个能跑通的Demo只是起点。要让RAG系统真正好用、可靠我们需要深入一系列优化策略。这些往往是区分玩具项目和生产系统的关键。4.1 检索质量优化解决“找不到”和“找不准”检索环节的瓶颈直接决定了系统天花板。以下是几个核心优化方向1. 文本分割策略的精细化语义分割使用基于嵌入的语义分割工具如langchain_experimental.text_splitter.SemanticChunkSplitter尝试将语义连贯的句子保持在同一片段内比简单的按字符或标点分割更合理。层次化分割对于结构清晰的文档如Markdown、HTML可以按标题层级进行分割并保留标题作为元数据。检索时可以优先检索相关章节下的内容。小片段父文档检索先按较小粒度分割存储。检索时不仅返回匹配的小片段还将其所在的更大父文档如整个章节的ID也一并返回在构建上下文时引入父文档的更多内容避免信息割裂。2. 嵌入模型的选择与微调领域适配通用嵌入模型在法律、医疗等专业领域可能表现不佳。如果条件允许可以在领域数据上对开源嵌入模型如BGE进行轻量级微调让它的向量空间更贴合你的专业术语。多向量检索除了文本向量还可以为同一段文本生成不同视角的向量如摘要向量、关键词向量检索时综合多种向量的结果。3. 重排序的引入初步的向量检索可能因为语义泛化而引入一些相关性稍差的文档。使用一个专门的、更强大的交叉编码器模型如bge-reranker对Top N例如20个的初筛结果进行重新打分和排序只保留Top K例如4个最相关的结果送入LLM。这能显著提升上下文质量。# 伪代码示例使用BGE重排序器 from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # 初始化基础检索器和重排序模型 base_retriever vectorstore.as_retriever(search_kwargs{k: 20}) # 初检索20个 model HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-base) compressor CrossEncoderReranker(modelmodel, top_n4) # 重排后取4个 compression_retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverbase_retriever ) # 然后将 compression_retriever 用于你的QA链4.2 生成效果优化让答案更精准、更可控即使检索到了完美上下文生成环节也可能出问题。1. Prompt工程的深化角色设定在Prompt中为模型设定一个角色如“你是一个严谨的技术支持专家”。格式要求明确要求答案的格式如“请先给出结论再分点列出特点”。引用溯源要求模型在答案中引用来源片段的编号例如“根据上下文[1]和[3]...”这极大增强了答案的可验证性。这需要在Prompt模板和后续解析上做更多工作。2. 上下文窗口的智能利用上下文压缩如果检索到的总文本太长超出了模型的上下文窗口可以使用MapReduce或Refine链。MapReduce先对每个片段单独生成答案Map再汇总这些答案得出最终结论Reduce。Refine则迭代式地完善答案每次带入新的上下文片段。选择性上下文不是把所有检索到的片段都堆进去而是用一个更小的模型或规则先对片段进行相关性排序或摘要只选择最关键的部分。3. 后处理与验证答案一致性检查对于事实性问题可以尝试从上下文中直接提取实体、日期、数字等与模型生成的答案进行交叉验证。拒绝回答机制当模型生成“根据已知信息无法回答”时可以触发一个备用流程比如将其转交给人工处理或者尝试用更宽泛的检索策略再试一次。4.3 系统层面的考量评估、迭代与部署1. 如何评估RAG系统的好坏不能只靠感觉。需要建立评估体系检索相关度人工或用小模型判断检索出的文档与问题的相关性。答案忠实度生成的答案是否严格基于提供的上下文有没有“夹带私货”幻觉。答案有用性答案是否真正回答了问题是否清晰、完整。人工评估定期抽样进行人工评分这是黄金标准。2. 知识库的更新与维护增量更新新文档进来如何增量地创建向量并插入数据库而不用全部重建大多数向量库支持add_documents操作。数据清洗定期检查知识库中过时、错误或低质量的文档片段。版本管理对于重要的知识库变更最好有版本记录以便在答案质量出现波动时进行回滚和排查。3. 生产环境部署API服务化使用FastAPI或Flask将你的RAG链包装成RESTful API。异步处理对于耗时的嵌入生成和模型推理使用异步框架如asyncio避免阻塞。缓存策略对常见问题的检索结果或最终答案进行缓存能极大降低响应延迟和计算成本。监控与日志记录每一次问答的查询、检索到的文档ID、生成的答案、耗时等用于后续分析和优化。5. 常见“坑点”与排查指南在实际开发和运维中你会遇到各种各样的问题。这里列举一些典型“坑”及其排查思路。5.1 答案出现明显“幻觉”与上下文不符这是最头疼的问题。排查步骤检查检索结果首先打印出每次查询时retriever实际返回的source_documents。看看模型看到的“上下文”到底是什么。很可能问题出在这里——检索到的文档根本不相关。调整检索参数如果检索结果不相关尝试增加k值返回更多文档也许相关信息在稍靠后的位置。调整文本分割的chunk_size。太大可能包含干扰信息太小可能丢失关键上下文。检查嵌入模型是否适合你的领域。尝试换一个嵌入模型如从text-embedding-ada-002切换到BGE-large。引入重排序或混合检索。检查Prompt指令如果检索结果是对的但模型还是瞎编。那么强化你的Prompt。用更严厉的语气例如“你必须且只能使用以下上下文中的信息。上下文未提及的内容绝对不允许出现在答案中。如果无法回答请说‘我不知道’。”降低模型“创造力”将LLM的temperature参数调至0或接近0让它的输出更确定、更可预测。5.2 检索速度慢响应延迟高向量数据库索引确保你的向量数据库如Milvus, Qdrant创建了合适的索引如HNSW, IVF。全量扫描Flat在小数据集上没问题数据量一大就慢。嵌入模型推理嵌入模型生成向量是CPU/GPU密集型操作。考虑使用更小的嵌入模型如BGE-small。对嵌入进行缓存。相同的问题或文档片段其嵌入向量是固定的可以缓存起来避免重复计算。使用GPU进行嵌入推理。LLM推理这是最大的瓶颈。考虑使用量化后的模型如GGUF格式用llama.cpp或Ollama运行在CPU上也能获得不错的速度。使用推理速度更快的模型如Qwen2.5-Coder在某些任务上比通用模型快。终极方案使用云API如DeepSeek, OpenAI它们有强大的后端优化。5.3 答案总是“根据已知信息无法回答”检查检索的k值k值是否太小尝试增大它。检查文本分割关键信息是否因为分割而破碎了尝试增大chunk_size或调整分割符。检查问题表述用户的问题是否太模糊或用了知识库里没有的同义词可以考虑对用户查询进行查询重写或查询扩展。例如用一个小模型将“咋装这个软件”重写为“如何安装此软件”或者根据问题生成几个相关的关键词一起用于检索。放宽检索相似度阈值有些向量检索器可以设置相似度分数阈值确保它没有过滤掉那些分数稍低但可能相关的文档。5.4 如何处理超长文档或复杂多跳问题当问题需要串联多个文档片段的信息才能回答时例如“张三负责的项目A的最终评审意见是什么”需要先找到“张三”再找到“项目A”最后找到“评审意见”简单的单次检索可能不够。多跳检索使用langchain的MultiQueryRetriever它能从原始问题生成多个相关但不同角度的问题分别检索后再合并结果。或者使用更复杂的Self-Query或Stepback检索策略。Agentic RAG这是更前沿的思路。将RAG系统包装成一个智能体让它具备“思考”和“执行工具”的能力。例如它可以先检索“张三”的信息发现他负责“项目A”然后自动发起第二次检索去查找“项目A的评审意见”。这通常需要借助LangGraph或AutoGen等多智能体框架来实现复杂度较高但能解决更复杂的问题。搭建一个可用的RAG系统可能一天就够了但打磨一个稳定、准确、高效的生产级系统需要在这些细节上反复迭代和调试。每一次优化都是你对数据、模型和业务需求理解加深的过程。