RAG(检索增强生成)
→ 返回工程实践
Retrieval-Augmented Generation:在 LLM 生成前先从外部知识库检索相关内容作为上下文,解决模型知识截止、幻觉和私域数据问题。
基础 RAG 流程
文档 → 分块(Chunk) → Embedding → 向量数据库
↓
用户提问 → Embedding → 向量检索 → Top-K 文档
↓
LLM(问题 + 上下文) → 回答
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Milvus
from langchain.chains import RetrievalQA
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = Milvus.from_documents(docs, embeddings, collection_name="kb")
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o"),
retriever=vectorstore.as_retriever(search_kwargs={"k": 5})
)
answer = qa.invoke("Redis 和 MongoDB 有什么区别?")Vector RAG vs Graph RAG
| 维度 | Vector RAG | Graph RAG |
|---|---|---|
| 知识表示 | 文本块向量 | 实体 + 关系图 |
| 检索方式 | 近似最近邻(ANN) | 图遍历 + 向量混合 |
| 多跳推理 | 弱(单次检索) | 强(沿关系边推理) |
| 全局理解 | 局部文本片段 | 跨文档关系网络 |
| 构建成本 | 低(分块+向量化) | 高(实体抽取+图构建) |
| 适用问题 | ”X 是什么" | "X 和 Y 的关系”、“找出满足条件的链路” |
| 典型工具 | LangChain、LlamaIndex | Microsoft GraphRAG、Neo4j + LangChain |
Vector RAG 的局限
问题:A 公司的供应商中,有哪些同时也是 B 公司的竞争对手?
Vector RAG:检索到 A 的供应商文档、B 的竞争对手文档,
但无法自动关联两个集合 → 依赖模型推理,容易出错
Graph RAG:图中直接查询两跳路径 → 精确、可解释
Graph RAG 核心思路(Microsoft GraphRAG)
1. 实体抽取 LLM 从文档中提取实体和关系 → 构建知识图谱
2. 社区检测 对图做 Leiden 算法聚类,生成社区摘要
3. 检索
├── Local Search:实体级检索,适合具体问题
└── Global Search:社区摘要聚合,适合全局问题
4. 生成 将检索结果作为上下文输入 LLM
Hybrid RAG
结合稠密检索(向量语义)和稀疏检索(关键词匹配)双路召回,互补各自的盲区。
为什么需要混合
| 检索类型 | 优势 | 盲区 |
|---|---|---|
| Dense(向量) | 语义相似,理解同义词 | 精确术语(型号、代码、缩写)容易丢失 |
| Sparse(BM25) | 精确关键词匹配 | 无法理解语义,同义词漏召 |
| Hybrid | 两者兼顾 | 需要融合排序,实现略复杂 |
问题:"GPT-4o 的 context window 是多少?"
Dense 检索:理解"上下文长度"语义,但"128K"这种数字可能漏
BM25 检索:精确匹配"GPT-4o"和"context window"关键词
Hybrid:两路都召,RRF 融合后覆盖更全面
RRF 融合排序
Reciprocal Rank Fusion:把多路检索结果按排名倒数加权合并,无需调权重:
def rrf(results_list: list[list], k: int = 60) -> list:
scores = {}
for results in results_list:
for rank, doc in enumerate(results):
doc_id = doc.metadata["id"]
scores[doc_id] = scores.get(doc_id, 0) + 1 / (rank + k)
return sorted(scores, key=scores.get, reverse=True)LangChain EnsembleRetriever
from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain_community.vectorstores import Milvus
# 稀疏检索(BM25)
bm25 = BM25Retriever.from_documents(docs, k=5)
# 稠密检索(向量)
vectorstore = Milvus.from_documents(docs, embeddings)
dense = vectorstore.as_retriever(search_kwargs={"k": 5})
# 混合:各占 50% 权重,内部使用 RRF 融合
hybrid = EnsembleRetriever(
retrievers=[bm25, dense],
weights=[0.5, 0.5]
)
results = hybrid.invoke("Redis 持久化方案有哪些?")权重调整建议
| 场景 | BM25 权重 | Dense 权重 |
|---|---|---|
| 技术文档、代码检索 | 0.6 | 0.4 |
| 通用问答、语义查询 | 0.3 | 0.7 |
| 混合型知识库 | 0.5 | 0.5 |
三种 RAG 横向对比
全维度对比表
| 维度 | Vector RAG | Hybrid RAG | Graph RAG |
|---|---|---|---|
| 知识表示 | 文本块向量 | 文本块向量 | 实体 + 关系图 |
| 稀疏检索 | ✗ | ✓ BM25 | ✗ |
| 稠密检索 | ✓ | ✓ | 可选 |
| 关系推理 | ✗ | ✗ | ✓ |
| 多跳查询 | ✗ | ✗ | ✓ |
| 全局摘要 | ✗ | ✗ | ✓(社区摘要) |
| 精确术语 | 弱 | 强 | 中 |
| 语义理解 | ✓ | ✓ | 中 |
| 构建成本 | 低 | 低 | 高(实体抽取) |
| 查询延迟 | 低 | 低~中 | 高 |
| 可解释性 | 弱 | 弱 | 强(路径可视) |
| 适合问题 | ”X 是什么" | "X 的型号/代号" | "X 和 Y 的关系” |
| 典型工具 | LangChain、LlamaIndex | EnsembleRetriever | MS GraphRAG、Neo4j |
检索能力雷达
精确术语
▲
│
Graph ───────┤──────── Hybrid
RAG ╲ │ ╱
╲ │ ╱
关系推理 ────╲───┼───╱──── 语义理解
╱ │ ╲
╱ │ ╲
╱ │ ╲
Vector RAG
│
构建简单
| 能力 | Vector RAG | Hybrid RAG | Graph RAG |
|---|---|---|---|
| 语义理解 | ★★★★★ | ★★★★★ | ★★★ |
| 精确术语 | ★★ | ★★★★★ | ★★★ |
| 关系推理 | ★ | ★ | ★★★★★ |
| 全局理解 | ★★ | ★★ | ★★★★★ |
| 构建简单 | ★★★★★ | ★★★★ | ★★ |
| 查询速度 | ★★★★★ | ★★★★ | ★★ |
典型问题对照
问题类型 推荐方案
─────────────────────────────────────────
"Redis 是什么?" Vector RAG
"GPT-4o 的上下文窗口多大?" Hybrid RAG ← 含精确数字/型号
"Redis 和 Kafka 哪些场景重叠?" Graph RAG ← 关系对比
"A 公司供应商中谁是 B 的竞争对手?" Graph RAG ← 多跳推理
"整个知识库的核心主题是什么?" Graph RAG ← 全局摘要
组合使用
三种方式并非互斥,生产中常组合:
┌─────────────────────────────────────────────────┐
│ 问题路由层 │
└──────┬──────────────────┬───────────────┬────────┘
↓ ↓ ↓
Vector RAG Hybrid RAG Graph RAG
(语义问题) (精确检索) (关系推理)
└──────────────────┴───────────────┘
↓
RRF 融合排序
↓
Re-ranking
↓
LLM
# 简单路由示例:根据问题类型选择检索器
def route_retriever(question: str):
# 含专有名词/数字/代号 → Hybrid
if re.search(r'[A-Z0-9]{3,}|版本|型号|\d+[KM]', question):
return hybrid_retriever
# 含关系词 → Graph
if any(w in question for w in ["关系", "区别", "对比", "影响", "依赖"]):
return graph_retriever
# 默认 → Vector
return vector_retrieverGraph-enhanced Embedding
结合图结构改进向量表示,介于 Vector RAG 和 Graph RAG 之间的方案。
方案一:图上下文增强文本
# 为每个文档节点附加其图邻居信息,再做 Embedding
def enrich_with_graph(doc_text, entity, graph):
neighbors = graph.neighbors(entity)
context = ", ".join([f"{rel}: {node}" for rel, node in neighbors])
return f"{doc_text}\n相关实体:{context}"
enriched = enrich_with_graph(doc.text, "Redis", knowledge_graph)
vec = embed(enriched) # 携带了图结构信息的向量方案二:Neo4j 原生向量 + 图混合检索
# Neo4j 5.x 向量索引 + 图关系联合查询
query = """
CALL db.index.vector.queryNodes('doc_embeddings', 5, $queryVec)
YIELD node, score
MATCH (node)-[:RELATED_TO]->(related)
RETURN node.text, related.name, score
ORDER BY score DESC
"""
results = session.run(query, queryVec=query_vector)方案三:多路召回融合
稠密检索(向量相似)
+
稀疏检索(BM25 关键词) → RRF 融合排序 → 重排序 → LLM
+
图检索(实体关系路径)
高级 RAG 模式
HyDE(假设文档扩展)
# 先让 LLM 生成假设答案,用假设答案去检索,效果优于原始问题
hypothesis = llm.invoke(f"请简要回答:{question}")
results = vectorstore.similarity_search(hypothesis, k=5)Re-ranking(重排序)
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
pairs = [(question, doc.page_content) for doc in candidates]
scores = reranker.predict(pairs)
reranked = sorted(zip(scores, candidates), reverse=True)
top_docs = [doc for _, doc in reranked[:3]]选型建议
| 场景 | 推荐方案 |
|---|---|
| 快速落地、私域文档问答 | Vector RAG + Re-ranking |
| 精确术语 + 语义并重 | Hybrid RAG(BM25 + Dense + RRF) |
| 需要关系推理、多跳问题 | Graph RAG 或混合检索 |
| 全局摘要、宏观分析 | Graph RAG(社区摘要) |
| Agent 长期记忆 | RAG + Memory 层(mem0 / MemGPT) |