聊起大模型应用,RAG(检索增强生成)绝对是这两年最火的技术之一。它就像一个给大模型配的“智能助理”,专门解决模型“一本正经胡说八道”的幻觉问题,还能让知识库随时更新,成本还低。今天,咱们就深入拆解一下RAG的完整技术栈,并手把手带你构建一个结合了向量检索和图谱推理的GraphRAG系统。
我们为什么要使用RAG
简单来说,RAG给大模型装上了“外设大脑”。它的核心价值体现在几个方面:
- 消除幻觉,提供事实依据: RAG强制大模型在回答问题前,先去一个可靠的知识库里“查阅资料”。毕竟,大模型的工作原理是“概率预测”而非“数据库检索”,一些幻觉是不可避免的。有了RAG,回答就有了准绳。
- 知识实时更新,成本极低: 当世界发生变化或公司政策更新时,你不需要耗费几百万美元和数月时间重新训练大模型。只需要把新的文档扔进RAG的向量数据库里,系统立刻就能用上新知识。
- 数据隐私与安全: 很多企业不敢把核心数据喂给公共大模型训练。通过RAG,私有数据只存在于你本地的数据库中,大模型只是在运行时作为“阅读理解工具”来处理临时喂给它的文本片段,数据不出域。
- 可溯源性: RAG系统可以清晰地告诉你,它的回答是基于知识库里的哪一篇文章、哪一页生成的。这对于需要核实信息来源的技术分析和决策场景至关重要。
RAG的结构
一个标准的RAG系统,其工作流程清晰地分为两条链路:离线的数据处理与入库,以及在线的检索与生成。
链路一:数据处理与入库(离线准备阶段)
这是给系统“喂食”的阶段,目标是把原始资料变成机器能高效查找和理解的格式。
- 文档解析: 将各种格式的数据(PDF、Word、HTML、内部Wiki)提取为纯文本。这一步的关键是保留完整的逻辑结构,比如标题、段落。
- 文本分块: 大模型的上下文窗口有限,不能把整本书塞进去。我们需要把长文本切分成几百个字的小段落,同时要防止把完整语义从中间截断,因此相邻块之间通常会有少量重叠。
- 向量化: 使用Embedding模型,将这些文字块转化成计算机能理解的“多维浮点数向量”。语义越相近的段落,在向量空间里的距离就越近。
- 向量数据库: 将转化好的向量以及对应的原始文本存储到专门的数据库(如Pinecone, Milvus, Chroma)中,为后续的快速相似度搜索做好准备。
链路二:检索与生成(在线问答阶段)
当用户提问时,系统开始“开卷考试”。
- 查询转换与重写: 用户提问往往很简短或指代不清。系统需要结合对话历史,把问题重写为一个清晰的独立问题,有时甚至会让大模型先“盲答”一篇假想文档,再用这篇文档的向量去搜索,以提升命中率。
- 检索: 将处理后的查询也转化为向量,然后在向量数据库中进行“相似度搜索”,找出最相关的若干个文档块。
- 重排序: 向量检索是“粗排”,可能召回表面相似但逻辑无关的内容。这时可以用一个专门的Reranker模型对结果进行二次打分和精确排序,剔除噪声。
- 提示词组装: 将用户的原始问题,加上检索出来的高质量文档块,组装成一个巨大的Prompt模板,例如:“请根据以下参考资料回答用户问题。参考资料:[段落1]、[段落2]... 用户问题:[问题]”。
- 大模型生成: LLM接收到这个“开卷考题”,阅读参考资料,总结并生成最终答案。好的实践会要求模型在回答中标注引用来源。
为了将理论付诸实践,我们可以通过一个具体的项目来学习整个流程:构建一个关于大模型技术及其发展趋势的GraphRAG(基于图谱的检索增强生成)系统。
这个项目的核心脉络可以概括为以下三个阶段:
- 高质量的“原材料”准备: 把海量、排版复杂的大模型前沿学术论文、技术博客等非结构化数据,通过工具清洗成干净的标准化文本,并进行合理的切块。
- 核心枢纽——知识抽取与图谱构建: 设计一套智能工作流,让大模型阅读文本块,精准抽取出核心“实体”(如具体的算法、架构)以及它们之间的“关系”(如改进、解决等),并将其存入图数据库(如Neo4j)中。
- 精准推理与问答: 当用户询问复杂的技术演进路径时,系统不仅能在向量数据库中进行语义搜索,更能借助图数据库进行多跳的逻辑推理,给出极具深度和脉络的回答。
链路一:技术栈详解与实现
整个离线流程可以概括为:原始文档 → 可读文本 → 小块知识 → 向量表示 → 入库保存。
1. 文档解析
这是将人类可读的复杂版面转化为机器可处理的纯文本和结构化数据的第一步,关键在于保留完整且正确的结构。
- 技术原理: 涉及格式提取(解析PDF、Word等文件的底层结构)、版面分析(识别标题、段落、表格边界)以及针对扫描件的OCR技术。
- 发展演进: 从早期基于规则的脆弱提取,演进到引入传统机器学习进行版面识别,再到如今多模态与视觉大模型的介入,已经能够“看懂”复杂表格甚至提取图片信息。
- 常用工具: Unstructured.io(著名开源解析库)、LlamaParse(针对复杂PDF优化)、PyMuPDF/PDFPlumber(处理纯文本PDF速度快)、Marker/Surya(高精度PDF转Markdown工具)。
2. 文本分块
由于大模型有上下文窗口限制,且长文本容易导致“注意力稀释”,因此需要对解析后的文本进行切片。
- 技术原理: 采用滑动窗口策略,在切分时保留一定的重叠部分,以防止割裂语义。核心在于平衡粒度:块太小会丢失上下文,块太大会引入噪声。
- 发展演进: 从1.0时代的固定长度切分,到2.0基于规则(按段落、标题)切分,再到3.0的语义切分(根据句子相似度变化切分),以及目前前沿的4.0袋里切分(由小模型判断切分点并生成摘要)。
- 常用工具: LangChain/LlamaIndex自带的切分器(如
RecursiveCharacterTextSplitter最常用),以及一些高级框架中内置的语义切分器。
3. 向量化
这是RAG系统的“翻译官”,将人类语言翻译成计算机能进行数学计算的高维坐标。
- 技术原理: 使用深度学习模型将文本映射为高维浮点数数组。其核心逻辑是“语义相近,空间相邻”,例如“苹果手机”和“iPhone”的向量距离会很近。
- 发展演进: 从第一代基于词频的稀疏表示,到第二代静态词向量,再到第三代能理解语境的动态上下文向量,如今已进入专为检索优化的对比学习模型时代,支持超长上下文和多模态。
- 常用模型: 闭源API如OpenAI的
text-embedding-3系列;开源模型如智源的bge-m3系列(中文效果佳)、阿里的GTE系列。
4. 向量数据库
当向量数量达到百万甚至亿级时,需要专门的数据库来进行高效检索。
- 技术原理: 核心是近似最近邻算法,通过构建特殊索引,在毫秒级时间内找到“足够近”的向量,实现精度与速度的权衡。现代向量数据库还支持标量混合检索,可先用元数据过滤再进行向量搜索。
- 发展演进: 从早期的算法库,到传统数据库插件,再到专门为海量向量设计的云原生架构。
- 常用工具: Milvus(成熟的企业级方案)、Pinecone(免运维的云服务)、Chroma/Qdrant(轻量易上手)、Wea viate(兼容性优秀)。
在我们的项目中,第一阶段采用的技术栈是:MinerU (解析) + BGE-M3 (向量化) + Neo4j (图存储) + Chroma (向量存储)。核心代码流程如下:
PDF 文件
│
▼ Step 1: parse_pdf_with_mineru()
Markdown 文本
│
▼ Step 2: chunk_markdown()
文本块列表 (Chunks)
│
▼ Step 3&4: ingest_to_databases()
├──► ChromaDB(向量检索库)
└──► Neo4j(图数据库)
以下是关键的流水线代码框架:
import os
import uuid
from typing import List, Dict
from dotenv import load_dotenv
load_dotenv()
# LangChain 切分工具
from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
# 向量化模型
from FlagEmbedding import BGEM3FlagModel
# 数据库
import chromadb
from neo4j import GraphDatabase
class OfflineDataPipeline:
def __init__(self, neo4j_uri, neo4j_user, neo4j_pwd, chroma_path=None, embedding_model=None):
# 初始化 BGE-M3 向量模型、ChromaDB 和 Neo4j 连接
...
def parse_pdf_with_mineru(self, pdf_path: str, output_dir: str) -> str:
"""步骤 1: 使用 pymupdf 将 PDF 解析为 Markdown 文本。"""
...
def chunk_markdown(self, markdown_text: str) -> List[Dict]:
"""步骤 2: 将 Markdown 文本按标题和长度进行切块"""
...
def ingest_to_databases(self, doc_name: str, chunks_data: List[Dict]):
"""步骤 3 & 4: 向量化并进行双库写入 (Chroma + Neo4j)"""
...
def run(self, pdf_path: str):
"""执行完整流水线"""
doc_name = os.path.basename(pdf_path)
output_dir = "./parsed_md"
# 1. 解析
md_text = self.parse_pdf_with_mineru(pdf_path, output_dir)
# 2. 切块
chunks = self.chunk_markdown(md_text)
# 3. 入库
self.ingest_to_databases(doc_name, chunks)
print("✅ 链路一处理完毕。")
# --- 运行示例 ---
if __name__ == "__main__":
pipeline = OfflineDataPipeline(
neo4j_uri=os.getenv("NEO4J_URI", "bolt://localhost:7687"),
neo4j_user=os.getenv("NEO4J_USER", "neo4j"),
neo4j_pwd=os.getenv("NEO4J_PWD", "password"),
)
# pipeline.run("./data/attention_is_all_you_need.pdf")
pipeline.close()
链路二:智能检索与生成工作流
在线阶段,我们使用LangGraph构建一个智能的、可路由的工作流。
1. 查询转换与重写
用户提问往往口语化且意图模糊,直接检索效果差。
- 技术原理: 利用LLM作为“翻译官”,对查询进行扩写、拆解或多义词补充。前沿方法如HyDE让模型先“盲答”再搜索,能极大提高语义命中率。
- 核心补充: 在GraphRAG系统中,还需要意图路由。系统需判断问题适合向量检索、图谱查询还是双路召回,这是一个典型的Agent决策节点。
2. 检索
将查询转化为机器语言去数据库“捞”数据。
- 技术原理: 将Query向量化,计算与库中向量的相似度,取Top-K结果。工业界标配是混合检索,结合BM25的字面命中保下限,向量检索的语义理解提上限。
- 核心补充: GraphRAG检索不仅要拿文本块,还要利用识别出的实体在图数据库中进行多跳推理,将关系网络转化为文本一并作为上下文。
3. 重排序
这是提升答案质量极其重要的进阶模块。
- 技术原理: 引入交叉编码器模型,把Query和每个召回文档放在一起深度阅读,输出精准的相关性得分并重新排序,剔除低分噪声。
- 常用模型: BGE-Reranker(与BGE-M3搭配效果佳)、Cohere Rerank API。
4. 提示词组装
把提纯后的“参考资料”和用户问题拼接成格式化模板。
- 核心补充: 必须加入防幻觉约束指令,例如:“请严格根据提供的参考资料回答。如果资料中没有相关信息,请直接回答‘知识库中暂无相关信息’,严禁编造。”并要求模型在回答时带上引用来源。
5. 大模型生成
LLM接收拼装好的开卷考题,输出最终结果。
- 核心补充: 为提升体验,生成应采用流式输出。同时,可以利用LangGraph构建包含“反思机制”的工作流:如果LLM生成的答案被自评节点判定为不准确,流程可以回退并调整查询进行二次检索。
以下是链路二基于LangGraph的工作流核心代码框架:
import os
import operator
from typing import TypedDict, Annotated, List
from dotenv import load_dotenv
load_dotenv()
from langgraph.graph import StateGraph, END
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from FlagEmbedding import BGEM3FlagModel, FlagReranker
import chromadb
from neo4j import GraphDatabase
# 1. 定义工作流状态
class GraphRAGState(TypedDict):
original_query: str
rewritten_query: str
route_type: str
retrieved_contexts: Annotated[List[str], operator.add]
retrieved_metadatas: Annotated[List[dict], operator.add]
final_answer: str
# 2. 初始化核心组件(大模型、数据库、Reranker)
llm = ChatOpenAI(temperature=0, model=os.getenv("MODEL_NAME"))
embedding_model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)
reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=True)
# 连接 ChromaDB 和 Neo4j(略)
# 3. 定义各个功能节点
def query_analysis_node(state: GraphRAGState) -> GraphRAGState:
"""节点1:查询分析与路由决策"""
# 让LLM分析问题类型,决定路由到向量库、图谱库还是混合查询
...
def vector_retrieval_node(state: GraphRAGState) -> GraphRAGState:
"""节点2A:向量检索"""
...
def graph_retrieval_node(state: GraphRAGState) -> GraphRAGState:
"""节点2B:图谱检索"""
...
def rerank_node(state: GraphRAGState) -> GraphRAGState:
"""节点3:重排序"""
...
def generation_node(state: GraphRAGState) -> GraphRAGState:
"""节点4:最终生成"""
# 组装带来源的Prompt,要求模型引用编号,并流式输出答案
...
# 4. 定义条件路由逻辑
def route_query(state: GraphRAGState) -> str:
route = state["route_type"]
if route == "graph_only":
return "graph_retrieval_node"
else:
return "vector_retrieval_node"
# 5. 构建与编译LangGraph工作流
workflow = StateGraph(GraphRAGState)
# 添加所有节点...
workflow.set_entry_point("query_analysis")
# 设置条件边和固定边...
app = workflow.compile()
# 运行示例
if __name__ == "__main__":
test_query = "目前主流的多模态对齐算法有哪些?它们分别优化了哪些缺陷?"
final_state = app.invoke({"original_query": test_query})
print(final_state["final_answer"])
通过这样一个从数据准备到智能问答的完整项目实践,你不仅能深入理解RAG每个环节的技术选型和原理,更能掌握如何将向量检索与知识图谱结合,构建出能够进行深度逻辑推理的新一代检索增强系统。这无疑是开发现实世界复杂知识问答应用的一条坚实路径。
