一、输入层集成(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的三种检索集成方式有什么区别?")