一、输入层集成(Input-layer Integration)
1. 核心定义
输入层集成是最经典、工业界最通用的 RAG 模式:在生成开始前,把检索到的所有参考文档和用户问题拼接在一起,整体作为输入传给大模型,模型基于参考资料一次性生成最终答案。
对应书中描述:「将检索内容与原始查询一同传递给生成器」,典型代表是 REALM 模型,也就是我们日常用的标准 RAG。
2. 通俗类比
相当于开卷考试:拿到题目后,先把所有相关参考书、笔记都摆在桌面,看完所有资料后,一次性写完答案。
3. 工作流程
用户输入问题
检索器从知识库召回所有相关文档
把「问题 + 全部参考文档」拼接成完整提示词
大模型根据提示词一次性生成最终答案
4. 优缺点
优点:实现最简单,逻辑直观,调试方便,是绝大多数 RAG 系统的默认方案
缺点:参考资料过多时容易超出模型上下文窗口;无关资料会形成噪声,干扰生成质量
二、输出层集成(Output-layer Integration)
1. 核心定义
输出层集成是事后修正的模式:大模型先只根据自身知识生成一份初稿答案,然后再把检索到的参考资料和初稿一起输入,让模型基于资料对初稿进行校对、纠错、补充,最终输出优化后的答案。
对应书中描述:「先生成输出,随后将输出与检索结果相结合,采用加权整合的方法来优化」,典型代表是 kNN-LM。
2. 通俗类比
相当于闭卷先写答案,交卷前再翻开参考书,把写错、写漏的地方修改补充一遍,形成最终答案。
3. 工作流程
用户输入问题
大模型不看参考资料,直接生成初稿答案
检索器召回相关文档
把「初稿答案 + 参考资料 + 原始问题」一起传给模型,让模型修正初稿
输出最终优化后的答案
4. 优缺点
优点:不干预模型原生生成逻辑,灵活度高;可以有效减少幻觉,修正初稿的事实错误
缺点:最终质量依赖初稿质量;当初稿和检索资料冲突时,模型可能出现取舍不当的问题
三、中间层集成(Middle-layer Integration)
1. 核心定义
中间层集成是生成过程中动态介入的模式:在生成的不同阶段,多次调用检索获取资料,让模型边生成、边查资料、边调整内容,实现知识的深度融合。
书中提到的原生模型级方案(如 RETRO)需要修改 Transformer 内部结构,工程落地难度极高;我们通常用分步迭代生成来模拟同等效果,不需要修改模型本身。
2. 通俗类比
相当于写学术论文:先写大纲,然后写每个章节的时候,都去查对应的参考文献,补充细节后再接着写下一部分,全程边查边写。
3. 工作流程(工程模拟版)
用户输入问题
第一步:先生成回答的大纲框架
针对大纲的每个要点,分别检索对应的参考资料
基于每个要点的专属资料,分步生成详细内容
把所有部分拼接整合,输出最终的完整答案
4. 优缺点
优点:生成过程中多次利用检索信息,信息利用率高,长文本、复杂问题的生成质量显著更好
缺点:实现逻辑复杂,需要多轮调用模型,生成速度慢;原生模型级的中间层集成需要改模型结构,成本极高
自己搞了个示例代码,便于理解。
1 # ===================== 1. 依赖导入 ===================== 2 from langchain_community.llms import Ollama 3 from langchain_community.embeddings import OllamaEmbeddings 4 from langchain_community.vectorstores import FAISS 5 from langchain_core.documents import Document 6 from langchain_core.prompts import PromptTemplate 7 from langchain_core.output_parsers import StrOutputParser 8 9 # ===================== 2. 公共基础配置(完全沿用你的环境) ===================== 10 OLLAMA_BASE_URL = "http://192.168.0.119:11434" 11 EMBED_MODEL = "nomic-embed-text" 12 LLM_MODEL = "llama3.2:3b" 13 14 # 初始化嵌入模型 15 embed_model = OllamaEmbeddings( 16 model=EMBED_MODEL, 17 base_url=OLLAMA_BASE_URL, 18 ) 19 20 # 初始化大模型 21 llm = Ollama( 22 model=LLM_MODEL, 23 base_url=OLLAMA_BASE_URL, 24 timeout=120.0, 25 temperature=0.3 26 ) 27 28 # ===================== 3. 构建 FAISS 向量检索库(真实语义检索) ===================== 29 # 3.1 准备知识库文本 30 knowledge_texts = [ 31 "RAPTOR是一种递归抽象处理的多层级索引结构,通过聚类+摘要自底向上构建树状索引。", 32 "输入层集成是将检索内容与原始查询一同传给生成器,是传统RAG的标准模式。", 33 "输出层集成是先生成答案初稿,再用检索结果加权修正,事后校准提升准确率。", 34 "中间层集成是在生成器内部中间层引入检索信息,代表模型为RETRO。", 35 "MultiVectorRetriever是多向量检索器,用摘要做检索,返回完整原始文档。", 36 "RecursiveRetriever可以实现分层递归检索,先粗筛再深入细节。", 37 "FAISS是Facebook开源的向量相似度搜索库,支持高效的最近邻检索。", 38 "RAG全称检索增强生成,通过外部知识库补充大模型知识,减少幻觉。" 39 ] 40 41 # 3.2 转成Document对象 42 knowledge_docs = [] 43 for text in knowledge_texts: 44 doc = Document(page_content=text) 45 knowledge_docs.append(doc) 46 47 # 3.3 构建FAISS向量索引(内存版,真实语义检索) 48 print("正在构建FAISS向量索引...") 49 faiss_db = FAISS.from_documents(knowledge_docs, embed_model) 50 # 创建检索器,每次返回Top3最相关的文档 51 retriever = faiss_db.as_retriever(search_kwargs={"k": 3}) 52 print("FAISS索引构建完成\n") 53 54 # ===================== 4. 输入层集成 ===================== 55 def input_layer_rag(query: str) -> str: 56 """ 57 输入层集成:检索到的所有资料一次性放进提示词,模型一次性生成答案 58 对应传统标准RAG模式 59 """ 60 # 第一步:用FAISS做真实语义检索,获取相关文档 61 retrieved_docs = retriever.get_relevant_documents(query) 62 63 # 把检索到的文档拼接成参考资料文本 64 context_str = "" 65 for i, doc in enumerate(retrieved_docs, 1): 66 context_str += f"{i}. {doc.page_content}\n" 67 68 # 第二步:构建提示词,把资料和问题一起传给模型 69 prompt = PromptTemplate.from_template(""" 70 请根据以下参考资料,回答用户的问题。 71 要求:答案必须基于参考资料,不要编造信息。 72 73 参考资料: 74 {context} 75 76 用户问题:{question} 77 78 回答: 79 """) 80 81 # 构建链路并生成答案 82 chain = prompt | llm | StrOutputParser() 83 answer = chain.invoke({ 84 "context": context_str, 85 "question": query 86 }) 87 88 print("="*50) 89 print("【输入层集成 结果】") 90 print(f"检索到相关文档:{len(retrieved_docs)}条") 91 print(f"最终答案:{answer.strip()}") 92 print("="*50 + "\n") 93 return answer.strip() 94 95 # ===================== 5. 输出层集成 ===================== 96 def output_layer_rag(query: str) -> str: 97 """ 98 输出层集成:先生成初稿,再用检索资料修正优化 99 对应事后校准的模式 100 """ 101 # 第一步:闭卷生成初稿(不使用任何检索资料) 102 first_draft_prompt = PromptTemplate.from_template(""" 103 请直接回答用户的问题,凭借你的已有知识作答。 104 用户问题:{question} 105 初稿答案: 106 """) 107 draft_chain = first_draft_prompt | llm | StrOutputParser() 108 first_draft = draft_chain.invoke({"question": query}).strip() 109 110 # 第二步:用FAISS检索相关参考资料 111 retrieved_docs = retriever.get_relevant_documents(query) 112 context_str = "" 113 for i, doc in enumerate(retrieved_docs, 1): 114 context_str += f"{i}. {doc.page_content}\n" 115 116 # 第三步:用资料修正初稿,生成最终答案 117 revise_prompt = PromptTemplate.from_template(""" 118 请根据下方的参考资料,对初稿答案进行修正和补充。 119 要求: 120 1. 修正初稿中与参考资料不符的内容 121 2. 补充初稿中缺失的关键信息 122 3. 保持语句通顺,输出最终的完整答案 123 124 参考资料: 125 {context} 126 127 初稿答案: 128 {draft} 129 130 最终修正后的答案: 131 """) 132 133 revise_chain = revise_prompt | llm | StrOutputParser() 134 final_answer = revise_chain.invoke({ 135 "context": context_str, 136 "draft": first_draft 137 }).strip() 138 139 print("="*50) 140 print("【输出层集成 结果】") 141 print(f"生成的初稿:{first_draft}") 142 print(f"检索到相关文档:{len(retrieved_docs)}条") 143 print(f"修正后最终答案:{final_answer}") 144 print("="*50 + "\n") 145 return final_answer 146 147 # ===================== 6. 中间层集成(分步迭代模拟版) ===================== 148 def middle_layer_rag(query: str) -> str: 149 """ 150 中间层集成模拟:分步生成,每个步骤都检索对应资料再续写 151 模拟生成过程中动态引入检索信息的效果 152 """ 153 # 第一步:先生成回答的大纲框架 154 outline_prompt = PromptTemplate.from_template(""" 155 请针对用户的问题,生成3个核心要点的回答大纲,只输出要点,不要展开解释。 156 用户问题:{question} 157 回答大纲: 158 1. 159 """) 160 outline_chain = outline_prompt | llm | StrOutputParser() 161 outline_raw = outline_chain.invoke({"question": query}).strip() 162 163 # 拆分每个大纲点 164 outline_points = [] 165 for line in outline_raw.split("\n"): 166 line = line.strip() 167 if line: 168 outline_points.append(line) 169 170 # 第二步:针对每个大纲点,分别检索资料,生成详细内容 171 detail_sections = [] 172 for idx, point in enumerate(outline_points, 1): 173 # 针对当前要点做精准语义检索 174 point_docs = retriever.get_relevant_documents(point) 175 point_context = "" 176 for i, doc in enumerate(point_docs, 1): 177 point_context += f"{i}. {doc.page_content}\n" 178 179 # 基于该部分的专属资料,生成详细内容 180 detail_prompt = PromptTemplate.from_template(""" 181 请根据参考资料,详细阐述下面这个要点,写成一段通顺的文字。 182 要点:{point} 183 参考资料:{context} 184 详细内容: 185 """) 186 detail_chain = detail_prompt | llm | StrOutputParser() 187 detail = detail_chain.invoke({ 188 "point": point, 189 "context": point_context 190 }).strip() 191 detail_sections.append(detail) 192 193 # 第三步:整合所有部分,形成最终完整答案 194 final_content = "\n\n".join(detail_sections) 195 196 print("="*50) 197 print("【中间层集成 结果】") 198 print("生成的大纲:") 199 for p in outline_points: 200 print(f" - {p}") 201 print(f"\n分步生成后的最终答案:\n{final_content}") 202 print("="*50 + "\n") 203 return final_content 204 205 # ===================== 运行测试 ===================== 206 if __name__ == "__main__": 207 # 测试输入层集成 208 input_layer_rag("什么是输入层集成和输出层集成?") 209 210 # 测试输出层集成 211 output_layer_rag("什么是中间层集成?") 212 213 # 测试中间层集成 214 middle_layer_rag("RAG的三种检索集成方式有什么区别?")