RAG与大模型集成原理
一、RAG原理深度解析
1.1 核心思想:为什么需要RAG?
大语言模型存在一个根本性问题:幻觉(Hallucination)。模型会生成流畅但事实错误的回答,这不是偶发bug,而是当前训练范式的"系统性弱点"。当模型被优化为"生成正确听起来的答案"而非"承认不确定性"时,它被隐性地奖励了"自信猜测"。
RAG(检索增强生成)直接解决了这个问题——将生成与可验证、最新的事实证据紧密耦合,而不是仅依赖模型的参数化记忆。简单来说,RAG是让模型"开卷考试":在回答问题前,先检索相关知识,然后基于这些材料生成答案。
1.2 RAG的四阶段理论框架
根据最新的系统性综述,现代RAG架构可以归纳为四阶段统一分类法:
| 阶段 | 英文 | 核心任务 | 关键考量 |
|---|---|---|---|
| 索引 | Indexing | 将文档切分、向量化并存储 | 分块策略、嵌入模型选择、索引算法 |
| 检索 | Retrieval | 根据查询召回最相关文档 | 相似度计算、召回数量k、检索算法 |
| 融合 | Fusion | 对召回的文档进行重排序/整合 | 多路召回融合、重排序模型、上下文压缩 |
| 生成 | Generation | 基于融合后的上下文生成答案 | 提示词构建、LLM调用、输出格式化 |
这个框架同时适用于向量RAG(基于向量数据库)和图RAG(基于知识图谱),是理解各类RAG变体的通用语言。
1.3 RAG解决了什么问题?
| 问题 | 传统LLM的表现 | RAG的解决方案 |
|---|---|---|
| 知识截止 | 只能回答训练数据截止前的信息 | 检索最新文档作为上下文 |
| 幻觉 | 虚构事实、编造引用 | 生成强制基于检索到的证据 |
| 领域适应性 | 通用领域知识,缺乏专业深度 | 注入领域文档(如CNC手册) |
| 可审计性 | 无法追溯信息来源 | 可输出引用文档,回答可验证 |
关键洞察:RAG不是让模型"更聪明",而是让模型"更诚实"——承认自己不知道,并基于给定的材料回答。
二、RAG相关论文脉络
2.1 奠基性工作(2022-2023)
-
Li et al. (2022):第一篇系统性地将RAG形式化为NLP范式的综述,奠定了术语和基本框架。
-
Gao et al. (2023):针对大语言模型的RAG专题综述,提出了从Naive RAG到Modular RAG的分类体系,深入分析了检索器/生成器的设计选择。
2.2 演进与变体(2024)
-
Zhao et al. (2024):将RAG扩展到AI生成内容领域,统一了跨模态RAG(文本+视觉),并开始讨论向量vs图的权衡。
-
Gupta (2024):首次明确对比向量RAG和图RAG,从内存占用、推理深度等维度进行系统比较。
2.3 最新综述(2025-2026)
-
Okonkwo et al. (2025):发表于arXiv的systematic review,追踪RAG从开放域问答到企业级部署的演进,重点分析了检索质量、隐私安全、混合检索等实际问题。
-
ScienceDirect综述 (2026):最新的四阶段分类法,涵盖向量RAG、图RAG、Agentic RAG、多模态RAG等所有现代变体,提供了超过300篇参考文献。
三、LangChain封装RAG的全流程实现
LangChain是目前最流行的RAG编排框架,它将上述四阶段抽象为一系列可组合的组件。
3.1 整体架构
典型的LangChain RAG流程包括5个步骤,下面用完整可运行的代码演示全过程:
python
# 1. 安装依赖
"""
pip install langchain langchain-community langchain-huggingface faiss-cpu sentence-transformers
"""
# 2. 导入所需模块
from langchain_community.document_loaders import TextLoader, PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.llms import HuggingFacePipeline
# 3. 准备文档加载器(支持多种格式)
def load_documents(file_path):
"""加载文档,支持txt和pdf"""
if file_path.endswith('.pdf'):
loader = PyPDFLoader(file_path)
else:
loader = TextLoader(file_path, encoding='utf-8')
return loader.load()
# ========== 第一阶段:索引构建 ==========
# 3.1 加载文档
documents = load_documents("cnc_manual.txt") # 你的知识文档
print(f"加载了 {len(documents)} 个文档")
# 3.2 文档分片(Chunking)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块字符数
chunk_overlap=50, # 块间重叠,保持语义连续
separators=["\n\n", "\n", "。", ",", " ", ""] # 按语义边界切分
)
chunks = text_splitter.split_documents(documents)
print(f"切分成 {len(chunks)} 个文本块")
# 3.3 创建嵌入模型并向量化
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2", # 轻量嵌入模型
model_kwargs={'device': 'cpu'}, # CPU运行
encode_kwargs={'normalize_embeddings': True}
)
# 3.4 存入FAISS向量库
vector_store = FAISS.from_documents(chunks, embeddings)
vector_store.save_local("faiss_cnc_index") # 持久化保存
print("向量库已保存")
# ========== 第二阶段:检索器构建 ==========
# 加载已保存的向量库
vector_store = FAISS.load_local(
"faiss_cnc_index",
embeddings,
allow_dangerous_deserialization=True
)
# 创建检索器
retriever = vector_store.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 3} # 返回最相关的3个块
)
# ========== 第三阶段:融合(可选重排序)==========
# 如果需要更精确的排序,可以添加重排序器
def rerank_documents(query, docs):
"""简单的重排序示例:基于BM25或交叉编码器"""
# 实际应用中可使用CrossEncoder或CohereRerank
return docs # 暂不重排,直接返回
# ========== 第四阶段:生成 ==========
# 4.1 构建提示模板
prompt_template = """
请基于以下参考资料回答问题。如果参考资料中没有相关信息,请明确说明"根据提供的资料无法回答"。
参考资料:
{context}
问题:{question}
回答:"""
prompt = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
# 4.2 初始化LLM(这里用HuggingFace Pipeline示例)
from transformers import pipeline
llm_pipeline = pipeline(
"text-generation",
model="gpt2", # 实际项目中替换为StarCoder-3B
max_new_tokens=200
)
llm = HuggingFacePipeline(pipeline=llm_pipeline)
# 4.3 构建检索增强的QA链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 将所有检索结果塞入上下文
retriever=retriever,
chain_type_kwargs={
"prompt": prompt,
"verbose": True
},
return_source_documents=True # 返回来源文档便于追溯
)
# ========== 第五阶段:执行查询 ==========
def ask_question(question):
"""执行RAG查询"""
result = qa_chain.invoke({"query": question})
print(f"问题:{question}")
print(f"回答:{result['result']}")
print("\n参考来源:")
for i, doc in enumerate(result['source_documents'], 1):
print(f"{i}. {doc.page_content[:100]}...")
return result
# 测试查询
ask_question("G00和G01指令有什么区别?")
3.2 LangChain各组件的作用
| 组件 | 类名 | 作用 | 对应RAG阶段 |
|---|---|---|---|
| 文档加载器 | DocumentLoader | 从PDF/TXT等源加载文档 | 索引 |
| 文本分割器 | TextSplitter | 将长文档切成语义完整的块 | 索引 |
| 嵌入模型 | Embeddings | 将文本转为向量 | 索引 |
| 向量存储 | VectorStore | 存储向量并支持相似性搜索 | 索引+检索 |
| 检索器 | Retriever | 封装检索逻辑,返回相关文档 | 检索 |
| 提示模板 | PromptTemplate | 构建结构化的LLM输入 | 生成 |
| QA链 | RetrievalQA | 编排检索+生成的完整流程 | 融合+生成 |
LangChain的核心理念:将RAG的各个环节抽象为可插拔的组件,开发者可以根据需求自由组合和替换。
四、RAG与大模型集成原理(以StarCoder-3B为例)
4.1 集成架构图
text
用户查询
↓
[查询向量化] ← 嵌入模型
↓
[向量检索] → FAISS向量库 → 召回相关文档块
↓
[上下文融合] ← 原始查询 + 检索到的文档块
↓
[提示构建] ← 结构化提示模板
↓
[StarCoder-3B推理] ← 生成G代码
↓
输出结果
4.2 为什么要单独集成?不是有LangChain吗?
LangChain提供了框架,但你需要决定如何将检索结果传递给模型。对于StarCoder-3B这样的代码生成模型,关键是要把检索到的知识正确地注入提示词。
4.3 从LangChain到StarCoder-3B的完整集成代码
下面是与StarCoder-3B集成的完整实现,包含本地CPU推理版本(基于你的无GPU需求):
python
"""
StarCoder-3B与RAG的完整集成
支持CPU推理(使用llama.cpp)
"""
import json
import requests
from typing import List, Dict, Any
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
class StarCoderRAG:
"""StarCoder-3B与RAG的集成类"""
def __init__(
self,
starcoder_endpoint: str = "http://localhost:8080/completion", # llama.cpp服务地址
embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2",
vector_store_path: str = "faiss_starcoder_index",
knowledge_files: List[str] = None,
k: int = 3
):
"""
初始化RAG系统
Args:
starcoder_endpoint: llama.cpp启动的StarCoder服务地址
embedding_model: 嵌入模型名称
vector_store_path: 向量库保存路径
knowledge_files: 知识文档文件列表
k: 检索文档数量
"""
self.endpoint = starcoder_endpoint
self.k = k
# 初始化嵌入模型(CPU运行)
self.embeddings = HuggingFaceEmbeddings(
model_name=embedding_model,
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)
# 加载或创建向量库
try:
self.vector_store = FAISS.load_local(
vector_store_path,
self.embeddings,
allow_dangerous_deserialization=True
)
print(f"已加载现有向量库: {vector_store_path}")
except:
print("未找到现有向量库,正在创建...")
self.vector_store = self._build_vector_store(knowledge_files)
self.vector_store.save_local(vector_store_path)
print(f"向量库已保存至: {vector_store_path}")
# 创建检索器
self.retriever = self.vector_store.as_retriever(
search_kwargs={"k": self.k}
)
def _build_vector_store(self, files: List[str]) -> FAISS:
"""从知识文档构建向量库"""
all_chunks = []
for file_path in files:
print(f"处理文件: {file_path}")
# 读取文件
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
# 文档分片
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300, # 代码知识块可以小一点
chunk_overlap=30,
separators=["\n\n", "\n", "G", "M", " ", ""] # 按G代码边界切分
)
# 创建文档对象
from langchain_core.documents import Document
doc = Document(page_content=text, metadata={"source": file_path})
chunks = text_splitter.split_documents([doc])
all_chunks.extend(chunks)
print(f" 生成 {len(chunks)} 个文本块")
print(f"总计 {len(all_chunks)} 个文本块,正在向量化...")
return FAISS.from_documents(all_chunks, self.embeddings)
def retrieve_context(self, query: str) -> str:
"""检索相关上下文"""
docs = self.retriever.invoke(query) # 注意:用invoke替代get_relevant_documents
context = "\n\n---\n\n".join([doc.page_content for doc in docs])
return context
def build_prompt(self, query: str, context: str) -> str:
"""
构建StarCoder-3B能理解的提示词
针对G代码生成任务的特定提示模板
"""
prompt = f"""### 任务描述
请根据以下参考信息和用户需求生成G代码。
### 参考信息(CNC加工知识):
{context}
### 用户需求:
{query}
### 生成G代码(只输出代码,不要额外解释):
```gcode
"""
return prompt
def generate(self, query: str, max_tokens: int = 512) -> Dict[str, Any]:
"""
执行完整的RAG生成流程
"""
# Step 1: 检索上下文
print("正在检索相关知识...")
context = self.retrieve_context(query)
# Step 2: 构建提示词
prompt = self.build_prompt(query, context)
# Step 3: 调用StarCoder-3B(通过llama.cpp)
print("正在调用StarCoder-3B生成代码...")
response = requests.post(
self.endpoint,
json={
"prompt": prompt,
"max_tokens": max_tokens,
"temperature": 0.2, # 代码生成用低温度
"top_p": 0.95,
"stop": ["```", "\n\n\n"] # 停止词,避免生成多余内容
}
)
if response.status_code == 200:
generated = response.json()["content"]
# 清理生成结果(提取纯G代码)
if "```gcode" in generated:
generated = generated.split("```gcode")[-1]
if "```" in generated:
generated = generated.split("```")[0]
return {
"success": True,
"gcode": generated.strip(),
"context": context,
"prompt": prompt
}
else:
return {
"success": False,
"error": f"API调用失败: {response.status_code}",
"context": context
}
def interactive(self):
"""交互式问答"""
print("\n" + "="*60)
print("StarCoder-3B RAG 交互系统(G代码生成)")
print("="*60)
print("输入 'quit' 退出")
print("输入 'context:on/off' 显示/隐藏检索到的上下文")
show_context = False
while True:
query = input("\n请输入加工任务描述 > ").strip()
if query.lower() == 'quit':
break
elif query.startswith('context:'):
show_context = query.endswith('on')
print(f"上下文显示: {'开启' if show_context else '关闭'}")
continue
elif not query:
continue
# 执行RAG生成
result = self.generate(query)
if result['success']:
if show_context:
print("\n" + "-"*40)
print("检索到的参考信息:")
print(result['context'])
print("-"*40)
print("\n生成的G代码:")
print(result['gcode'])
else:
print(f"生成失败: {result.get('error')}")
# ========== 使用示例 ==========
if __name__ == "__main__":
# 配置参数
KNOWLEDGE_FILES = [
"gcode_basics.txt", # G代码基础知识
"cnc_manual.txt", # CNC机床手册
"common_patterns.txt" # 常见加工模式
]
# 初始化RAG系统
rag = StarCoderRAG(
starcoder_endpoint="http://localhost:8080/completion", # 你的llama.cpp地址
knowledge_files=KNOWLEDGE_FILES,
k=3 # 检索3个相关文档块
)
# 启动交互式界面
rag.interactive()
# 单次生成示例
# result = rag.generate("铣削一个边长为50mm的正方形,深度2mm")
# print(result['gcode'])
4.4 启动StarCoder-3B服务(CPU推理)
由于你没有GPU,使用llama.cpp启动量化后的StarCoder-3B:
bash
# 1. 下载StarCoder-3B的GGUF量化版本
# 可以从Hugging Face下载,如 starcoder2-3b-Q4_K_M.gguf
# 2. 启动llama.cpp服务器
./llama-server -m ./models/starcoder2-3b-Q4_K_M.gguf \
--host 0.0.0.0 \
--port 8080 \
-c 2048 \
-t 8 # 使用8个CPU线程
# 服务器启动后,上面的Python代码就能通过http://localhost:8080调用模型
4.5 集成原理深度解析
| 集成环节 | 技术实现 | 关键考量 |
|---|---|---|
| 模型服务化 | llama.cpp启动HTTP服务 | 将模型包装为API,Python通过requests调用 |
| 提示词工程 | 结构化模板 + 上下文注入 | G代码生成需要严格格式,不能有额外解释 |
| 上下文截断 | 控制检索文档数量和长度 | StarCoder-3B的上下文窗口有限(约4K tokens) |
| 输出解析 | 提取```gcode块之间的内容 | 过滤模型的解释性文本,只保留纯G代码 | | |
| 迭代优化 | 可结合自纠错机制 | 验证生成的G代码,如失败则重新生成 |
4.6 针对你项目的特别优化建议
基于GLLM论文的发现,有几个关键点需要注意:
-
结构化提示至关重要:论文实验显示,结构化提示下所有模型在多数任务中可达100%成功率,而非结构化提示下部分任务成功率降至0%。因此上述代码中的
build_prompt方法必须保持清晰的结构。 -
RAG的集成位置:论文发现,在非结构化提示下使用RAG反而降低性能。原因是RAG引入的额外信息可能增加输入复杂性。解决方案是与结构化提示结合,或者只在特定情况下触发检索。
-
自纠错循环:GLLM的成功离不开生成→验证→反馈的迭代循环。可以在上述代码的基础上,添加语法验证和Hausdorff距离计算,形成完整的自纠错RAG。
五、总结与核心要点
5.1 你需要记住的关键概念
| 概念 | 一句话理解 |
|---|---|
| RAG本质 | 让模型"开卷考试",基于检索到的证据回答问题 |
| 四阶段框架 | 索引→检索→融合→生成,所有RAG系统的通用语言 |
| LangChain角色 | RAG的编排框架,将各环节抽象为可组合组件 |
| 集成关键 | 检索结果如何构造提示词,决定生成质量 |
| StarCoder-3B特点 | 代码生成能力强,3B参数适合CPU推理,需结构化提示 |
5.2 实践建议
-
从简单开始:先用上述完整代码跑通流程,再用自己的CNC文档替换知识库
-
提示词是核心:针对G代码任务,提示词必须结构化,明确区分"参考信息"和"用户需求"
-
先无RAG,再加RAG:对比有无RAG的效果,验证论文中的发现
-
迭代优化:逐步增加文档数量、优化分块策略、调整检索数量k
更多推荐
所有评论(0)