从零构建RAG系统:基于Embedding与向量数据库的大模型知识增强实践

📅 2026/8/23 4:12:40
从零构建RAG系统:基于Embedding与向量数据库的大模型知识增强实践
在实际的大模型应用开发中直接让模型回答私有或最新数据是一个常见痛点。RAG检索增强生成技术通过结合信息检索与生成模型为解决这一问题提供了高效、可靠的工程路径。它并非简单的“搜索回答”而是一套涉及文本处理、向量化、相似性检索和提示工程等多个环节的系统性架构。本文将围绕RAG的核心组件——Embedding、Chunking、向量数据库以及它们如何协同工作构建一个从零到一、可运行、可调试的完整RAG系统。无论你是希望将公司文档接入大模型还是构建一个智能问答助手理解并实践这套流程都是关键一步。我们将使用 Python 作为主要语言以 ChromaDB 作为向量数据库并结合一个开源的 Embedding 模型来构建一个本地可运行的 RAG 原型。文章会详细解释每个步骤的设计考量、常见陷阱以及生产环境下的优化方向确保你不仅能跑通Demo更能理解背后的“为什么”。1. 理解 RAG 的核心架构与工作流程在深入代码之前必须先厘清 RAG 解决什么问题和它是如何工作的。这有助于你在后续配置和排错时能清晰地定位问题发生在哪个环节。1.1 RAG 要解决的根本问题大模型的“知识”局限大型语言模型LLM在预训练阶段学习了海量公开数据但其知识存在“静态”和“通用”两个局限静态性模型训练完成后其知识便固化下来无法自动获取训练截止日期之后的新信息如最新新闻、公司内部动态。通用性模型缺乏对特定组织、个人或私有数据的了解如公司产品手册、个人笔记、私有代码库。直接向模型提问这类它“不知道”的信息它可能会基于通用知识“幻觉”出一个看似合理但错误的答案。RAG 的核心思想是在让模型生成答案前先从外部知识库中检索出与问题最相关的文档片段并将这些片段作为上下文提供给模型从而引导模型基于给定的事实生成答案。1.2 RAG 的标准工作流程检索与生成的协同一个典型的 RAG 系统包含两个主要阶段索引Indexing和查询Querying/Retrieval。索引阶段线下进行文档加载从各种来源PDF、Word、网页、数据库加载原始文档。文本分块将长文档切割成大小适中、语义相对完整的片段Chunks。这是平衡检索精度和上下文长度的关键。向量化使用 Embedding 模型将每个文本块转换为一个高维向量Vector。这个向量在数学上表征了文本的语义。存储将文本块及其对应的向量存储到向量数据库中。查询/检索阶段线上实时进行问题向量化用户提问时使用相同的 Embedding 模型将问题转换为向量。相似性检索在向量数据库中计算问题向量与所有存储向量之间的相似度如余弦相似度返回最相似的 K 个文本块。提示构建将检索到的文本块作为上下文与原始问题一起按照特定格式构造成一个提示Prompt提交给 LLM。答案生成LLM 基于提供的上下文和问题生成最终答案。这个流程确保了答案来源于你提供的知识库极大地减少了“幻觉”并实现了知识的动态更新只需更新向量数据库。1.3 关键组件选型考量在动手前需要为每个环节做出技术选型Embedding 模型决定语义理解的质量。选择时需权衡质量、速度、维度和是否支持中文。对于入门和中文场景BAAI/bge-small-zh-v1.5是一个不错的起点。向量数据库负责高效存储和检索向量。ChromaDB 以其轻量、易用和内存/持久化模式切换灵活而适合学习和原型开发。生产环境可能会考虑 Milvus、Qdrant、Weaviate 等。文本分块策略直接影响检索效果。简单的按固定字符数切割会破坏语义更优的策略是按段落、句子或使用语义分割模型。LLM生成答案的引擎。可以是 OpenAI GPT、Claude 等云端 API也可以是 Llama、Qwen 等本地部署模型。本文将采用BAAI/bge-small-zh-v1.5ChromaDBLangChain用于简化流程编排 OpenAI API模拟生成实际可用本地模型替代的组合进行演示。2. 环境准备与核心依赖配置我们将创建一个独立的 Python 项目环境并安装所有必要的库。确保你的 Python 版本在 3.8 及以上。2.1 创建项目目录与虚拟环境首先创建一个干净的项目目录并进入。mkdir rag-from-zero-to-hero cd rag-from-zero-to-hero python -m venv venv # 创建虚拟环境激活虚拟环境Windows:venv\Scripts\activatemacOS/Linux:source venv/bin/activate激活后命令行提示符前应显示(venv)。2.2 安装依赖包创建一个requirements.txt文件内容如下langchain0.1.0 langchain-community0.0.10 chromadb0.4.22 sentence-transformers2.2.2 unstructured0.10.30 # 用于文档加载 pypdf3.17.4 # 用于读取PDF openai1.12.0 # 用于调用GPT API如使用本地模型可替换 tiktoken0.5.1 # 用于Token计数然后使用 pip 安装pip install -r requirements.txt注意sentence-transformers库依赖于 PyTorch。如果安装缓慢或出错可以先根据 PyTorch 官网 的指引安装适合你系统的 PyTorch再安装其他依赖。2.3 准备示例知识库文档在项目根目录下创建一个knowledge_base文件夹并放入一些文本文件或 PDF 作为测试数据。例如创建一个demo.txt# 公司产品手册 - AI助手“小智” 小智是我们公司开发的智能对话助手最新版本为 v2.1。 其主要功能包括智能问答、日程管理、邮件草拟和代码片段生成。 小智支持通过 API 和 WebSocket 两种方式集成。 该产品于2023年第三季度正式发布目前服务于超过500家企业客户。 技术栈基于 Python 和 FastAPI使用 Transformer 模型进行核心对话推理。再创建一个faq.txtQ: 如何重置小智助手的对话历史 A: 用户可以通过在对话中输入“/reset”命令或调用 /v1/conversation/reset API 端点来清空当前会话的上下文。 Q: 小智是否支持私有化部署 A: 是的小智提供完整的 Docker 镜像和部署脚本支持在企业内网进行私有化部署。具体请联系我们的售前团队获取部署包。 Q: 小智的计费模式是怎样的 A: 我们提供按调用次数计费和包年订阅两种模式。详细价格表请参阅官网定价页面。这些文档将作为我们构建知识库的原材料。3. 构建 RAG 索引从文档到向量数据库索引阶段是 RAG 系统的基石。这一步做得好检索质量才有保障。3.1 文档加载与文本分块策略我们使用 LangChain 提供的文档加载器和文本分割器。首先创建一个build_index.py脚本。# build_index.py import os from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. 配置路径 PERSIST_DIRECTORY ./chroma_db # 向量数据库持久化目录 DOCUMENT_DIRECTORY ./knowledge_base # 原始文档目录 # 2. 加载文档 print(正在加载文档...) loader DirectoryLoader(DOCUMENT_DIRECTORY, glob**/*.txt, loader_clsTextLoader) documents loader.load() print(f已加载 {len(documents)} 个文档。) # 3. 文本分块 (Chunking) print(正在进行文本分块...) text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块的最大字符数 chunk_overlap50, # 块之间的重叠字符数避免语义断裂 length_functionlen, separators[\n\n, \n, 。, , , , , , ] # 分割符优先级 ) chunks text_splitter.split_documents(documents) print(f文档被分割成 {len(chunks)} 个文本块。)关键参数解释chunk_size500这是分块的核心参数。太小会导致上下文碎片化检索到的信息不完整太大会引入噪声降低检索精度同时增加后续提示的长度和成本。500-1000 字符是常见起点。chunk_overlap50重叠是为了防止一个完整的句子或概念被硬生生切在两块中间导致语义不完整。50-100 字符的重叠通常足够。separators定义了分割的优先级。这里优先按双换行段落、单换行、句号等分割尽可能保证块的语义完整性。3.2 嵌入模型选择与向量化接下来我们初始化 Embedding 模型并用它将文本块转换为向量。# build_index.py (续) # 4. 初始化 Embedding 模型 print(正在初始化 Embedding 模型...) model_name BAAI/bge-small-zh-v1.5 model_kwargs {device: cpu} # 如果 GPU 可用可改为 cuda encode_kwargs {normalize_embeddings: True} # 标准化向量便于余弦相似度计算 embeddings HuggingFaceEmbeddings( model_namemodel_name, model_kwargsmodel_kwargs, encode_kwargsencode_kwargs ) print(fEmbedding 模型 {model_name} 加载成功。)为什么选择 BGE 模型BAAI/bge-small-zh-v1.5是智源研究院开源的针对中文优化的轻量级 Embedding 模型在中文语义相似度任务上表现良好且模型尺寸小适合本地快速运行。normalize_embeddingsTrue意味着生成的向量会被归一化为单位长度此时点积dot product就等于余弦相似度这是向量检索中最常用的相似度度量方式。3.3 创建并持久化向量数据库最后将向量和文本块存入 ChromaDB。# build_index.py (续) # 5. 创建向量数据库并持久化 print(正在创建向量数据库...) vectordb Chroma.from_documents( documentschunks, embeddingembeddings, persist_directoryPERSIST_DIRECTORY ) vectordb.persist() # 确保数据写入磁盘 print(f向量数据库已创建并持久化到: {os.path.abspath(PERSIST_DIRECTORY)}) print(索引构建完成)运行这个脚本python build_index.py如果一切顺利你会看到类似以下的输出并且项目目录下会生成一个chroma_db文件夹里面存储了向量数据。正在加载文档... 已加载 2 个文档。 正在进行文本分块... 文档被分割成 5 个文本块。 正在初始化 Embedding 模型... Embedding 模型 BAAI/bge-small-zh-v1.5 加载成功。 正在创建向量数据库... 向量数据库已创建并持久化到: /path/to/your/project/chroma_db 索引构建完成4. 实现检索与生成完成 RAG 查询链路索引准备好后我们来实现查询阶段。创建一个query_rag.py脚本。4.1 加载向量数据库与检索器# query_rag.py from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma from langchain.chains import RetrievalQA from langchain_openai import ChatOpenAI # 使用OpenAI后续可替换 import os # 1. 加载相同的 Embedding 模型 PERSIST_DIRECTORY ./chroma_db model_name BAAI/bge-small-zh-v1.5 embeddings HuggingFaceEmbeddings(model_namemodel_name) # 2. 加载已持久化的向量数据库 print(正在加载向量数据库...) vectordb Chroma( persist_directoryPERSIST_DIRECTORY, embedding_functionembeddings ) print(向量数据库加载成功。) # 3. 创建检索器 # search_kwargs{k: 3} 表示每次检索返回最相似的3个文本块 retriever vectordb.as_retriever(search_kwargs{k: 3})4.2 配置大语言模型与提示模板这里我们使用 OpenAI GPT-3.5-turbo 作为生成模型。你需要准备一个有效的 OpenAI API Key。# query_rag.py (续) # 4. 配置 LLM (这里以OpenAI为例生产环境可替换为本地模型) import getpass import os # 安全地设置API Key if OPENAI_API_KEY not in os.environ: os.environ[OPENAI_API_KEY] getpass.getpass(请输入你的 OpenAI API Key: ) llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.1) # temperature 控制随机性0.1 使输出更确定、更基于事实关键构建 RAG 提示模板LangChain 的RetrievalQA链内置了一个默认的提示模板但了解其结构至关重要。其核心思想是将检索到的上下文和用户问题组合起来。一个典型的模板如下请根据以下上下文信息回答问题。如果你不知道答案就说不知道不要编造。 上下文 {context} 问题{question} 答案{context}会被替换为检索到的多个文本块拼接后的内容{question}被替换为用户问题。我们使用 LangChain 默认的即可。4.3 组装检索增强生成链并提问# query_rag.py (续) # 5. 创建 RetrievalQA 链 print(创建 RAG 问答链...) qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最简单的方式将所有上下文塞入提示 retrieverretriever, return_source_documentsTrue, # 返回检索到的源文档便于调试 verboseFalse # 设为True可看到详细的链执行过程 ) # 6. 开始问答循环 print(\nRAG 系统已就绪输入 quit 或 exit 退出。) while True: query input(\n请输入你的问题: ) if query.lower() in [quit, exit]: break print(思考中...) result qa_chain.invoke({query: query}) print(f\n答案: {result[result]}) # 显示检索到的源文档验证答案来源 print(\n--- 参考来源 ---) for i, doc in enumerate(result[source_documents]): print(f[片段 {i1}]: {doc.page_content[:200]}...) # 只打印前200字符运行脚本并提问python query_rag.py输入你的 OpenAI API Key 后尝试提问“小智助手的主要功能有哪些”你应该能得到一个基于我们知识库 (demo.txt) 生成的答案并且脚本会打印出它参考了哪些文本块。5. 核心组件深度解析与调优一个能跑的 Demo 只是开始。要让 RAG 系统真正可用必须理解每个环节的可调参数和优化方向。5.1 文本分块策略的陷阱与优化常见陷阱固定长度分块破坏语义一个完整的问答对或一个技术点被切成两半。块大小不匹配块太大检索精度下降块太小上下文不足LLM 无法理解。忽略文档结构对于 Markdown、HTML、PDF直接按字符切分会丢失标题、列表等结构信息。优化策略语义分割使用如semantic-text-splitter等库尝试在句子边界或语义边界进行分割。重叠分块如我们之前所做的设置chunk_overlap。分层索引创建不同粒度的块如小节、段落、句子检索时进行融合。基于文档类型的分割器LangChain 为Markdown、Python代码等提供了特定的分割器。# 示例使用 Markdown 分割器 from langchain.text_splitter import MarkdownTextSplitter markdown_splitter MarkdownTextSplitter(chunk_size1000, chunk_overlap100)5.2 Embedding 模型的选择与评估Embedding 模型的质量直接决定了“语义相似度”计算是否准确。模型名称特点适用场景备注BAAI/bge-*-zh中文优化开源有不同尺寸中文知识库bge-large-zh质量更高但更慢text-embedding-ada-002OpenAI 提供通用性强稳定多语言预算充足API 调用有成本multilingual-e5-large多语言效果均衡混合语言知识库开源需自行部署本地微调模型最贴合领域数据专业领域医疗、法律需要标注数据和训练成本评估方法 在投入生产前可以构建一个小型测试集Q-A对人工或通过其他模型评估不同 Embedding 模型检索到的 Top-K 文本块是否包含正确答案。5.3 检索过程的进阶技巧相似度算法Chroma 默认使用余弦相似度。对于归一化后的向量余弦相似度与点积等价。某些场景下欧氏距离也可能被使用。重排序初步检索出 K 个如10个相关文档后使用一个更精细但更慢的“重排序模型”对它们进行二次评分只取 Top-N如3个送入 LLM。这能显著提升精度。混合检索结合向量检索语义匹配和关键词检索如 BM25精确匹配。例如使用langchain.retrievers.ensemble中的EnsembleRetriever。元数据过滤在存储时为每个块添加元数据如来源文件、章节、日期。检索时可以过滤例如“只检索2024年以后的文档”。# 示例为文档添加元数据并在检索时过滤 from langchain.schema import Document doc_with_meta Document(page_contenttext, metadata{source: demo.txt, page: 1}) # 在创建检索器时 retriever vectordb.as_retriever( search_kwargs{k: 3, filter: {source: demo.txt}} )5.4 提示工程与链类型在RetrievalQA中我们使用了chain_typestuff。这是最简单的方式但也有局限性当检索到的上下文总长度超过 LLM 的上下文窗口时会报错。其他链类型map_reduce将每个文档块单独送给 LLM 生成摘要再汇总摘要生成最终答案。处理长文档能力强但调用 LLM 次数多成本高、速度慢。refine迭代式处理文档用上一个答案和下一个文档去“精炼”答案。质量可能更高但速度慢且顺序敏感。map_rerank为每个文档块生成答案并评分选择最高分的答案。对于大多数场景优先优化分块大小以适应stuff模式是性价比最高的选择。6. 生产环境考量与常见问题排查将 RAG 从 Demo 推向生产需要解决一系列工程问题。6.1 生产环境部署清单事项学习/开发环境生产环境建议向量数据库ChromaDB (本地文件)独立的向量数据库服务 (如 Milvus, Qdrant)支持分布式、高可用、持久化。Embedding 模型本地 CPU 推理GPU 推理服务或专用 Embedding API关注延迟和吞吐量。LLM单一 API 调用考虑负载均衡、Fallback 策略、限流、缓存。索引更新手动重建实现增量更新管道监听文档变更实时或定时更新向量库。监控与日志打印到控制台记录每次查询的检索结果、LLM输入/输出、耗时、Token 使用量便于追踪和优化。权限与安全无知识库访问权限控制用户查询审计防止提示注入攻击。6.2 常见问题与排查路径当你发现 RAG 系统回答不准确或出错时可以按照以下路径排查问题1答案与知识库内容不符幻觉依旧可能原因检索到的上下文不相关。排查步骤检查source_documents看检索到的文本块是否真的包含答案。如果不相关问题出在检索环节。检查Embedding 模型是否适合你的领域尝试更换模型。分块大小是否合适块太大可能包含无关信息。相似度阈值是否设置得太低可以尝试提高k值或调整score_threshold。解决方案优化分块策略评估或微调 Embedding 模型引入重排序或混合检索。问题2答案说“根据上下文无法回答”但知识库明明有可能原因检索到了相关文档但 LLM 未能理解或提取信息。提示模板不够清晰。排查步骤检查source_documents确认相关文档已被检索到。打印出最终发送给 LLM 的完整提示在 LangChain 中设置verboseTrue检查上下文是否清晰、问题是否明确。解决方案优化提示模板明确指令如“请严格根据上下文回答如果上下文没有请直接说‘不知道’”。也可以尝试让 LLM 先引用原文再总结。问题3系统响应速度慢可能原因Embedding 推理慢首次加载模型或 CPU 推理。向量数据库检索慢数据量大、未建索引。LLM 调用慢网络或模型本身。排查步骤分别对 Embedding 调用、数据库检索、LLM 调用进行计时。解决方案Embedding使用 GPU或预计算并缓存文档向量。数据库确保向量索引已创建Chroma 自动创建考虑分片。LLM使用更快的模型或对答案实现缓存相同或相似问题。问题4更新文档后答案未变可能原因向量数据库未更新仍然使用旧的索引。解决方案实现索引更新流程。对于 ChromaDB需要先删除旧文档的向量再添加新文档的向量。注意处理更新和删除。# 示例向已有集合添加新文档 new_chunks text_splitter.split_documents(new_docs) vectordb.add_documents(new_chunks) vectordb.persist()6.3 评估 RAG 系统如何知道你的 RAG 系统是好是坏需要建立评估体系。检索评估计算检索召回率RecallK——对于一组测试问题正确答案所在的文档出现在 Top-K 检索结果中的比例。生成评估忠实度答案是否严格来源于提供的上下文可人工评估或使用“答案-上下文”一致性模型。答案相关性答案是否直接回答了问题流畅度答案是否通顺自然 可以借助RAGAS、TruLens等框架进行自动化评估。7. 扩展方向与进阶架构掌握了基础 RAG 后你可以探索更复杂的架构以提升效果。Agentic RAG让 LLM 作为“代理”主动决定何时检索、检索什么、如何组合多次检索的结果。这适用于复杂、多步的问答。RAG 与知识图谱结合向量检索擅长语义模糊匹配知识图谱擅长精确的关系推理。两者结合可以先用向量检索找到相关实体再用图谱查询其关联关系。RAG 路由系统根据问题类型路由到不同的知识库或使用不同的检索策略。例如技术问题检索代码库产品问题检索手册。多模态 RAG不仅处理文本还能处理图片、表格中的信息。这需要多模态 Embedding 模型和能够理解多模态上下文的 LLM。Self-RAG让模型在生成过程中自我批判和反思判断是否需要检索、检索到的信息是否相关从而动态调整行为。构建 RAG 系统是一个迭代过程。从最简单的流水线开始通过评估发现瓶颈是检索不准还是生成不好然后有针对性地优化对应模块。始终记住核心目标让大模型基于你提供的事实生成准确、可靠的答案。本文提供的代码和思路是一个坚实的起点你可以在此基础上根据具体的数据、场景和需求不断调整和深化每一个组件。