RAG 整体架构——两条管道的每个环节

📅 2026/7/15 3:55:34
RAG 整体架构——两条管道的每个环节
RAG检索增强生成已成为构建企业级 AI 应用的核心范式。但很多人只停留在向量检索 丢给 LLM的粗浅理解上。本文带你深入 RAG 的两大核心管道——索引管道和查询管道逐环节拆解每个步骤的技术细节与最佳实践。一、RAG 的本质为什么需要两条管道RAG 的核心思想是让 LLM 在生成答案之前先去外部知识库里查资料。这样做解决了大模型的三个根本痛点知识滞后模型训练数据有截止日期RAG 可以接入最新文档幻觉问题要求模型仅凭检索结果回答大幅降低编造概率私有数据企业内部的文档、代码、规范不需要微调就能用但要把这件事做好需要两条相互配合的管道索引管道离线定期运行 文档 → 切分 → 向量化 → 存入向量数据库 ↓查询管道在线用户每次提问触发 用户问题 → 查询优化 → 检索 → 重排序 → LLM 生成答案索引管道决定能查到什么查询管道决定查得有多准。两条管道缺一不可。二、索引管道Ingestion Pipeline—— 让知识可被检索索引管道是离线预处理阶段目标是从原始文档中构建高质量的检索索引。环节 1文档加载Document Loading做什么把各种格式的文档统一加载为文本。支持的数据源文件PDF、Word、Markdown、HTML、TXT代码Java/Python/JS 等源码文件网页通过爬虫或 API 获取数据库MySQL、MongoDB 的查询结果企业系统Confluence、Notion、GitLab最佳实践# LangChain 文档加载示例from langchain.document_loaders import ( PyPDFLoader, # PDF UnstructuredHTMLLoader, # HTML NotionDirectoryLoader, # Notion)loaders [PyPDFLoader(手册.pdf), UnstructuredHTMLLoader(页面.html)]documents []for loader in loaders: documents.extend(loader.load())⚠️坑点提醒PDF 加载常有乱码、表格丢失、多栏排版错乱等问题生产环境建议使用专门的 PDF 解析库如pdfplumber或unstructured。环节 2文本分块Chunking—— 最容易被忽视的关键步骤为什么必须分块即使 Gemini 1.5 Pro 支持百万级 Token 上下文把整本书塞进去也不现实成本爆炸每次查询都要消耗大量 Token中间遗忘LLM 对超长上下文的中间部分记忆能力显著下降检索精度用整本书去做向量相似度匹配结果会非常模糊分块策略全景对比策略原理优点缺点适用场景固定大小切分按固定 Token/字符数切割简单快速块大小均匀可能切断句子或语义单元格式不规范的文档递归切分按分隔符优先级递归切割尽量保持语义完整实现稍复杂LangChain 默认最常用语义切分根据句子嵌入差异识别语义边界块内语义高度凝聚计算成本高对精度要求极高的场景结构感知切分按 Markdown 标题/HTML 标签切割完全保留文档结构块大小不均匀结构化文档技术文档、API 文档滑动窗口切分相邻块设置重叠Overlap避免边界信息丢失存储冗余配合其他策略使用关键参数设置# 递归切分示例LangChainfrom langchain.text_splitter import RecursiveCharacterTextSplittertext_splitter RecursiveCharacterTextSplitter( separators[\n\n, \n, 。, ., , ], # 分隔符优先级 chunk_size800, # 每块约 800 tokens chunk_overlap100, # 相邻块重叠 100 tokens length_functionlen,)chunks text_splitter.split_documents(documents)参数建议chunk_size5001000 tokens根据嵌入模型的最佳输入长度调整chunk_overlap50150 tokens太小会丢失跨块信息太大浪费存储高级技巧Parent Document Retriever小块到大块# 索引时用小块提高检索精度# 但返回给 LLM 时用大块保证上下文完整from langchain.retrievers import ParentDocumentRetrieverfrom langchain.storage import InMemoryStoreretriever ParentDocumentRetriever( vectorstorevectorstore, # 存小块的向量库 docstoreInMemoryStore(), # 存大块的文档库 child_chunk_size400, # 小块大小 parent_chunk_size2000, # 大块大小)核心思想检索用小块的精确向量生成用大块的完整上下文一箭双雕。环节 3文本嵌入Embedding做什么把每个文本块转换为固定维度的向量使其能被相似度搜索。核心公式v _ c E ( c ) , v _ q E ( q ) v\_c E(c), \quad v\_q E(q)v_cE(c),v_qE(q)其中E EE是嵌入模型c cc是文本块q qq是查询。主流嵌入模型对比2025 推荐模型维度语言特点推荐场景text-embedding-3-small1536多语言OpenAI 官方性价比高通用场景text-embedding-3-large3072多语言精度更高成本更高对精度要求极高的场景bge-m31024中英双语多功能、多粒度中文应用首选gte-large-zh1024中文中文优化效果好纯中文场景voyage-21024多语言Voyage AI 出品质量高替代 OpenAI Embedding⚠️ 关键原则查询和文档必须使用同一个嵌入模型不同模型生成的向量在不同向量空间无法直接比较相似度代码示例from langchain.embeddings import OpenAIEmbeddingsembeddings OpenAIEmbeddings( modeltext-embedding-3-small, dimensions1536 # 可以指定降维节省存储)# 对文本块进行向量化vectors embeddings.embed_documents([chunk.page_content for chunk in chunks])环节 4向量存储与索引Vector Storage Indexing做什么把向量和对应的文本块存入向量数据库并建立高效索引。主流向量数据库对比数据库开源部署方式特点推荐场景ChromaDB✅本地/内存轻量级开箱即用原型开发、本地测试Qdrant✅本地/云服务滤波能力强Rust 实现高性能生产环境Milvus✅本地/云服务分布式支持十亿级向量大规模企业应用Pinecone❌纯云服务全托管无需运维快速上线、无运维团队Weaviate✅本地/云服务模块化支持多模态需要混合检索的场景向量索引算法对比算法原理召回率查询速度说明Flat暴力搜索遍历所有向量计算距离100%慢小规模数据10万可用IVF倒排文件聚类后只在相关簇内搜索中高快适合中等规模HNSW多层图结构近似最近邻高快工业界首选LSH哈希函数映射相似向量到同一桶中最快精度换速度HNSW 关键参数# Qdrant 创建索引示例client.create_collection( collection_namedocs, vectors_configmodels.VectorParams( size1536, distancemodels.Distance.COSINE, ), hnsw_configmodels.HnswConfigDiff( m16, # 每个节点最大出度越大召回率越高内存消耗越大 ef_construct100, # 构建索引时的候选列表大小越大质量越高构建越慢 ))# 搜索时results client.search( collection_namedocs, query_vectorquery_vector, limit5, search_paramsmodels.SearchParams( hnsw_ef50, # 搜索时的候选列表大小越大召回率越高搜索越慢 ))参数调优经验m16, ef_construct100是通用起点追求更高召回率可设m32, ef_construct200资源受限时可设m8。存储时要保留的元数据# 每个向量条目应包含的元数据{ vector: [0.123, 0.456, ...], text: 原始文本块内容, metadata: { source: 用户手册.pdf, # 来源文档 page: 42, # 页码 chunk_id: chunk_123, # 块 ID last_updated: 2025-01-15, # 最后更新时间 category: 安装指南, # 自定义分类 }}元数据可以用于检索时的过滤如只搜索 2025 年之后的文档和加权如优先展示官方文档而非论坛回复。环节 5可选元数据增强与预处理Contextual RetrievalAnthropic 提出在嵌入之前用 LLM 为每个文本块生成一段上下文说明50100 tokens拼接到原文本后再做嵌入# 为每个块生成上下文说明def add_contextual_explanation(chunk, full_doc): prompt f 以下是文档中的一个片段以及完整文档的内容。 请为这个片段写一段简短说明描述它在完整文档中的上下文背景。 完整文档节选{full_doc[:2000]} 文档片段{chunk} 上下文说明 return client.chat.completions.create( modelgpt-4o, messages[{role: user, content: prompt}] ).choices[0].message.content# 存储时上下文说明 原文本enhanced_text f{contextual_explanation}\n\n{original_chunk}Anthropic 的实验表明这种方法能将检索失败率降低 67%。三、查询管道Query Pipeline—— 让检索更精准查询管道是在线服务阶段每次用户提问都会完整执行一遍。环节 1查询处理与优化Query Processing很多用户的问题并不直接适合检索口语化、指代模糊、过于简略这一步的目标是把用户的人话转成更适合检索的检索语言。1.1 查询重写Query Rewriting# 将口语化查询改写为更规范的形式# 原查询那个啥来着就是那个跑得快的框架# 重写后高性能 Web 框架 对比rewrite_prompt f请将以下用户查询改写为更适合向量检索的规范化查询。保留所有关键信息去除口语化表达补充可能的同义词。原始查询{user_query}规范化查询1.2 查询扩展Query Expansion让 LLM 生成多个查询变体从不同角度去检索# 生成 3 个不同角度的查询变体expansion_prompt f用户原始查询{user_query}请从以下 3 个不同角度生成查询变体1. 使用更专业的术语2. 使用更通俗的表达3. 分解为多个子问题输出格式每个变体一行# 用每个变体分别检索合并结果去重1.3 HyDE假设性文档嵌入这是目前效果最显著的查询优化技术之一# 步骤 1让 LLM 根据查询生成一个假设性答案hypothetical_doc llm.invoke(f请回答以下问题{user_query})# 步骤 2用这个假设性答案的向量去检索而不是用原查询的向量# 原理假设性答案的语义比原查询更接近真实相关文档hypothetical_vector embeddings.embed_query(hypothetical_doc)results vectorstore.similarity_search_by_vector(hypothetical_vector, k5)为什么 HyDE 有效原查询“RAG 怎么优化”很短语义稀疏假设性答案包含检索、分块、重排序、混合搜索等关键词语义丰富向量空间里假设性答案的向量更接近真实相关文档1.4 查询路由Query Routing根据查询类型路由到不同的数据源或检索策略def route_query(query: str) - str: 简单的关键词路由示例 if any(kw in query for kw in [比较, 对比, vs, 区别]): return vector_search # 需要广泛背景用向量搜索 elif any(kw in query for kw in [最新, 2025, 近期]): return keyword_search # 有时间敏感性用关键词精确匹配 elif any(kw in query for kw in [代码, 实现, 示例]): return code_search # 代码相关用专用代码检索器 else: return hybrid_search # 默认混合搜索环节 2检索Retrieval这是查询管道的核心环节。2.1 查询向量化# 使用与索引阶段完全相同的嵌入模型query_vector embeddings.embed_query(user_query)2.2 向量相似度计算相似度度量公式特点余弦相似度最常用$\cos(\theta) \frac{A \cdot B}{A点积A ⋅ B ∑ A _ i × B _ i A \cdot B \sum A\_i \times B\_iA⋅B∑A_i×B_i向量归一化后等价于余弦相似度计算更快欧氏距离$A - B# 向量数据库中的 Top-K 检索results vectorstore.similarity_search_by_vector( embeddingquery_vector, k10, # 先取 10 个候选后续重排序会进一步筛选)2.3 全文检索BM25—— 向量检索的重要补充向量检索擅长语义理解同义词、近义词但在精确关键词匹配上不如传统全文检索。BM25 算法score ( Q , D ) ∑ _ q _ i ∈ Q IDF ( q _ i ) × f ( q _ i , D ) × ( k _ 1 1 ) f ( q _ i , D ) k _ 1 × ( 1 − b b × ∣ D ∣ avgdl ) \text{score}(Q, D) \sum\_{q\_i \in Q} \text{IDF}(q\_i) \times \frac{f(q\_i, D) \times (k\_1 1)}{f(q\_i, D) k\_1 \times (1 - b b \times \frac{|D|}{\text{avgdl}})}score(Q,D)∑_q_i∈QIDF(q_i)×f(q_i,D)k_1×(1−bb×avgdl∣D∣​)f(q_i,D)×(k_11)​from rank_bm25 import BM25Okapi# 对文本块建立 BM25 索引tokenized_corpus [list(jieba.cut(doc.page_content)) for doc in all_chunks]bm25 BM25Okapi(tokenized_corpus)# 查询tokenized_query list(jieba.cut(user_query))scores bm25.get_scores(tokenized_query)top_k_docs sorted(zip(scores, all_chunks), reverseTrue)[:10]2.4 混合检索Hybrid Search—— 最佳实践是两者结合向量检索 → 候选集 A语义相似 ∪取并集或 RRF 融合BM25检索 → 候选集 B关键词匹配 ↓ 最终候选集RRF倒数排序融合RRFScore ( D ) ∑ _ r ∈ systems 1 k _ r r f rank _ r ( D ) \text{RRFScore}(D) \sum\_{r \in \text{systems}} \frac{1}{k\_{rrf} \text{rank}\_r(D)}RRFScore(D)∑_r∈systemsk_rrfrank_r(D)1​k _ r r f k\_{rrf}k_rrf通常取60不依赖各系统的原始得分得分范围可能差异很大只依赖排名def rrf_fusion(vector_results, bm25_results, k60): RRF 融合排序 scores {} # 向量检索结果 for rank, doc in enumerate(vector_results, 1): doc_id doc.metadata[chunk_id] scores[doc_id] scores.get(doc_id, 0) 1 / (k rank) # BM25 检索结果 for rank, doc in enumerate(bm25_results, 1): doc_id doc.metadata[chunk_id] scores[doc_id] scores.get(doc_id, 0) 1 / (k rank) # 按融合得分排序 return sorted(scores.items(), keylambda x: x[1], reverseTrue)环节 3重排序Reranking—— 性价比最高的优化手段为什么需要重排序召回阶段向量检索/BM25为了速度使用了近似算法精度有限。重排序阶段对少量候选文档如 Top 20进行精细化排序显著提升最终质量。重排序方法对比方法原理精度速度推荐Bi-Encoder双编码器查询和文档分别编码用向量相似度排序中快召回阶段用Cross-Encoder交叉编码器查询文档拼接后共同编码直接输出相关度分数高慢重排序首选LLM 重排序让 LLM 直接对候选文档打分或排序最高最慢候选集很小时用实战使用 bge-reranker 进行重排序from sentence_transformers import CrossEncoder# 加载重排序模型中文场景推荐 bge-rerankerreranker CrossEncoder(BAAI/bge-reranker-large)# 对候选文档进行重排序query_doc_pairs [[user_query, doc.page_content] for doc in candidate_docs]scores reranker.predict(query_doc_pairs)# 按重排序分数重新排序reranked_docs [ doc for _, doc in sorted( zip(scores, candidate_docs), keylambda x: x[0], reverseTrue )][:5] # 只保留 Top 5经验值召回阶段取 Top 2050重排序后取 Top 510 给 LLM性价比最高。环节 4上下文压缩与过滤Context Compression检索到的文档可能包含冗余信息或无关内容直接全部塞给 LLM 会浪费 Token成本↑引入噪声质量↓超出上下文窗口报错4.1 上下文压缩Context CompressionLLMLingua使用小型 LLM 识别并移除提示中的冗余 Token# 使用 LLMLingua 压缩检索结果from llmlingua import PromptCompressorcompressor PromptCompressor( model_namemicrosoft/llmlingua-2-bert-large-chinese, use_llmlingua2True, # 使用 LLMLingua-2 算法)compressed_context compressor.compress_prompt( context, rate0.5, # 压缩率 50% force_tokens[重要, 关键, 核心], # 这些 Token 不被压缩)4.2 上下文过滤# 简单的相关性过滤去掉重排序分数低于阈值的文档FILTER_THRESHOLD 0.3filtered_docs [ doc for doc, score in zip(reranked_docs, rerank_scores) if score FILTER_THRESHOLD]# 如果过滤后没有文档了诚实告知用户if not filtered_docs: return 抱歉我在知识库中没有找到与您问题相关的信息。环节 5提示构建Prompt Engineering将用户查询和检索到的上下文组装成最终的提示送给 LLM 生成答案。标准 RAG 提示模板RAG_PROMPT_TEMPLATE 你是一个专业的技术助手。请**严格根据以下提供的参考文档**来回答用户的问题。规则1. 如果参考文档中没有相关信息请明确回答根据现有资料无法回答此问题2. 不要编造任何信息3. 回答时请引用信息来源的文档编号4. 使用清晰的结构化格式列表、表格等呈现信息———— 参考文档开始 ————{documents}———— 参考文档结束 ————用户问题{question}请给出详细、准确的回答动态组装提示def build_rag_prompt(query: str, docs: list, max_tokens: int 4000): 组装 RAG 提示自动管理上下文窗口 context_parts [] total_tokens 0 # 按相关性从高到低依次加入上下文 for i, doc in enumerate(docs): doc_tokens len(doc.page_content) // 2 # 粗略估算 Token 数 if total_tokens doc_tokens max_tokens: break context_parts.append(f[文档 {i1}]来源{doc.metadata[source]}\n{doc.page_content}) total_tokens doc_tokens documents_text \n\n.join(context_parts) return RAG_PROMPT_TEMPLATE.format( documentsdocuments_text, questionquery )环节 6LLM 推理与生成LLM Inference Generation最终环节将组装好的提示送给 LLM生成答案。def generate_answer(query: str, docs: list) - str: prompt build_rag_prompt(query, docs) response client.chat.completions.create( modelgpt-4o, # 或 DeepSeek-V2、Qwen 等 messages[ {role: system, content: 你是专业的技术助手严格根据参考文档回答问题。}, {role: user, content: prompt} ], temperature0.1, # RAG 场景建议低温减少随机性 max_tokens2000, ) return response.choices[0].message.content进阶自适应检索Self-RAG不是所有问题都需要检索——“你好”、谢谢这类问题直接回答即可不需要浪费 RAG 流程。Self-RAG的思路是让 LLM自己判断是否需要检索# Self-RAG 简化版实现def self_rag_answer(query: str) - str: # 第一步让 LLM 判断是否需要检索 judge_prompt f 用户问题{query} 请判断以下问题是否需要查阅外部资料才能准确回答。 只需要回答 YES 或 NO。 needs_retrieval llm.invoke(judge_prompt).strip().upper() YES # 第二步根据判断结果决定流程 if needs_retrieval: docs retrieve(query) return generate_answer(query, docs) else: return llm.invoke(query) # 直接回答跳过检索四、两条管道的完整数据流【索引管道 - 离线】原始文档 ↓ Document Loading加载原始文本 ↓ Chunking分块文本块5001000 tokens ↓ Embedding向量化向量 文本块 ↓ Vector Storage存储向量数据库可被高效检索【查询管道 - 在线】用户查询 ↓ Query Processing查询优化优化后的查询可能多个变体 ↓ Retrieval检索候选文档集Top 2050 ↓ Reranking重排序精排后的文档Top 510 ↓ Context Compression压缩过滤最终上下文fit 进 LLM 窗口 ↓ Prompt Engineering提示构建最终提示 ↓ LLM Generation生成答案最终答案 → 返回用户五、每个环节的常见坑与最佳实践环节常见坑最佳实践文档加载PDF 解析乱码、表格丢失使用unstructured或pdfplumber对扫描版 PDF 先做 OCR分块块太大导致检索模糊块太小导致语义断裂用递归切分 100 tokens 重叠代码文件按函数/类切分嵌入查询和文档用了不同模型封装一个统一的 Embedding 服务确保两端用同一个模型向量存储HNSW 参数设置不当导致召回率低从m16, ef_construct100起步用标注数据做参数搜索查询优化不做任何优化直接用原查询检索至少加入 HyDE有条件的话加入查询重写检索只用向量检索忽略关键词匹配一定要用混合检索向量 BM25 RRF重排序跳过重排序直接用召回结果重排序是性价比最高的优化强烈建议加入上下文压缩把 10 个文档块全塞给 LLM用重排序分数过滤 按 Token 预算动态截断提示构建没有指示 LLM “不知道就说不知道”在提示中明确加入禁止编造的指令LLM 生成temperature 设太高答案随机性强RAG 场景建议temperature0.10.3六、如何评估你的 RAG 系统RAG 评估框架Ragas核心指标指标说明理想值Faithfulness忠实度答案是否完全基于检索到的上下文越高越好接近 1.0Answer Relevance答案相关性答案是否真正回答了用户的问题越高越好Context Precision上下文精确率检索到的上下文中有多少是真正相关的越高越好Context Recall上下文召回率正确答案所需的信息有多少被检索到了越高越好from ragas import evaluatefrom ragas.metrics import ( faithfulness, answer_relevancy, context_precision, context_recall,)# 用标注好的测试集评估results evaluate( datasettest_dataset, metrics[faithfulness, answer_relevancy, context_precision, context_recall],)print(results)七、总结RAG 看似简单实则每个环节都有深度可挖索引管道决定知识库的质量上限 ↓查询管道决定每次查询的体验下限快速提升 RAG 效果的三板斧按投入产出比排序加入重排序Reranker—— 几乎零成本效果立竿见影混合检索向量 BM25 RRF—— 解决单一检索的盲区查询优化至少加入 HyDE—— 让用户的烂查询也能搜到好结果RAG 不是接入向量数据库就完事——它是一条有很多环节的流水线最弱的那个环节决定了整个系统的天花板。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】