高级 RAG 技术
学习如何在本综合教程中使用 LangChain 实现高级 RAG 技术。逐步掌握多查询搜索、父子分块、重排序策略以及融合技术,以达到生产级别的准确度。
🎯 本高级 RAG 教程你将学到
- 如何在 LangChain 中实现多查询检索以获得更好的搜索结果。
- RAG 中父子分块实现的逐步指南。
- 如何在 LangChain 中使用元数据过滤进行精确的文档检索。
- 逐步实现重排序和融合技术以提升 RAG 性能。
🔍 如何在 LangChain 中实现多查询检索
🔍 为什么要使用多查询?
由于词汇不匹配或歧义,单一查询往往会遗漏相关信息。多查询检索会针对同一个问题生成多个视角,以提高召回率,并找到单一查询可能遗漏的文档。
多查询实现逐步教程
🔍 理解组件:
查询生成:大语言模型创建用户问题的多个版本。
并行检索:每个查询变体都在向量存储中进行搜索。
结果聚合:合并并去除所有查询结果的重复项。
分数融合:对匹配多个查询的文档进行智能排序。
📦 所需依赖:
在运行此多查询实现之前,请安装所需的包:
python
pip install langchain langchain-community langchain-google-genai python-dotenv
代码实现
python
# 如何在 LangChain 中实现多查询检索
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.prompts import PromptTemplate
from typing import List
import asyncio
import os
from dotenv import load_dotenv
load_dotenv()
class AdvancedMultiQueryRetriever:
"""逐步学习如何使用 LangChain 构建多查询检索"""
def __init__(self, vector_store, llm=None):
self.vector_store = vector_store
# [LangChain 1.x 更新] 初始化 LLM,确保使用最新的参数结构
self.llm = llm or ChatGoogleGenerativeAI(
model="gemini-2.0-flash",
temperature=0.5,
google_api_key=os.getenv("GOOGLE_API_KEY")
)
# 定义查询生成提示词
self.query_generation_prompt = PromptTemplate(
input_variables=["question"],
template="""你是一个帮助改进搜索结果的 AI 助手。
生成给定问题的 5 个不同版本,以检索相关文档。
包含以下变体:
1. 使用不同的关键词和同义词
2. 更具体或更笼统
3. 关注问题的不同方面
4. 使用技术性语言与通俗语言
5. 将问题改写为陈述句而不是疑问句
原始问题:{question}
将变体输出为编号列表:
1.
2.
3.
4.
5."""
)
def generate_queries(self, original_query: str) -> List[str]:
"""生成多个查询变体"""
# [LangChain 1.x 更新] 使用 invoke 方法调用 LLM
response = self.llm.invoke(
self.query_generation_prompt.format(question=original_query)
)
# 从响应中解析变体
variations = []
# 兼容字符串内容访问
content = response.content if hasattr(response, 'content') else str(response)
lines = content.strip().split('\n')
for line in lines:
if line.strip() and line[0].isdigit():
# 移除数字和句点
query = line.split('.', 1)[1].strip()
if query:
variations.append(query)
# 始终包含原始查询
variations.insert(0, original_query)
return variations[:6] # 限制总共 6 个查询
async def retrieve_with_multi_query(self, query: str, k: int = 5):
"""使用多个查询变体检索文档"""
# 生成查询变体
queries = self.generate_queries(query)
print(f"生成了 {len(queries)} 个查询变体:")
for i, q in enumerate(queries):
print(f" {i+1}. {q}")
# 检索每个查询的文档
all_docs = []
unique_docs = {}
for q in queries:
# [LangChain 1.x 更新] 确保向量存储调用方式正确
docs = self.vector_store.similarity_search_with_score(q, k=k)
for doc, score in docs:
# 使用内容哈希作为唯一标识符
doc_id = hash(doc.page_content)
if doc_id not in unique_docs:
unique_docs[doc_id] = {
"document": doc,
"scores": [score],
"matched_queries": [q]
}
else:
unique_docs[doc_id]["scores"].append(score)
unique_docs[doc_id]["matched_queries"].append(q)
# 聚合分数(使用每个文档的最大分数)
results = []
for doc_data in unique_docs.values():
max_score = max(doc_data["scores"])
avg_score = sum(doc_data["scores"]) / len(doc_data["scores"])
results.append({
"document": doc_data["document"],
"max_score": max_score,
"avg_score": avg_score,
"match_count": len(doc_data["matched_queries"]),
"matched_queries": doc_data["matched_queries"]
})
# 根据匹配数量和最大分数的组合进行排序
results.sort(
key=lambda x: (x["match_count"], x["max_score"]),
reverse=True
)
return results[:k]
# 使用示例 - 多查询检索演示
print("=== 多查询检索演示 ===")
# 创建模拟向量存储用于演示
class MockVectorStore:
def similarity_search_with_score(self, query, k=5):
# 基于查询关键词模拟搜索结果
mock_docs = [
{"content": f"关于 {query[:20]}... 的带有缓存策略的文档", "score": 0.9},
{"content": f"关于 {query[:15]}... 性能优化的高级指南", "score": 0.8},
{"content": f"涵盖 {query[:25]}... 实现细节的教程", "score": 0.7},
{"content": f"生产系统中 {query[:20]}... 的最佳实践", "score": 0.6},
{"content": f"当 {query[:30]}... 时的常见模式故障排除指南", "score": 0.5},
]
# 创建模拟文档对象
class MockDoc:
def __init__(self, content):
self.page_content = content
self.metadata = {"source": "mock_doc.txt"}
return [(MockDoc(doc["content"]), doc["score"]) for doc in mock_docs[:k]]
# 创建演示用的多检索器
mock_vector_store = MockVectorStore()
# 检查 API 密钥是否可用
if os.getenv("GOOGLE_API_KEY"):
# 如果 API 密钥可用,使用真实 LLM
multi_retriever = AdvancedMultiQueryRetriever(mock_vector_store)
print("正在使用 Google Gemini LLM 进行查询生成")
else:
# 为了演示目的,回退到模拟 LLM
class MockLLM:
def invoke(self, prompt):
# 查询生成的模拟响应
class MockResponse:
def __init__(self):
self.content = """1. Web 应用程序的缓存策略是什么?
2. 如何实现 Web 应用程序缓存机制?
3. Web 开发中缓存的最佳实践?
4. Web 应用缓存技术和实现?
5. Web 系统的应用程序级缓存?"""
return MockResponse()
mock_llm = MockLLM()
multi_retriever = AdvancedMultiQueryRetriever(mock_vector_store, llm=mock_llm)
print("正在使用模拟 LLM 进行演示(设置 GOOGLE_API_KEY 以使用真实 LLM)")
# 运行异步代码的演示函数
async def run_multi_query_demo():
print("\n正在测试多查询检索...")
query = "如何在 Web 应用程序中实现缓存?"
print(f"原始查询:{query}")
# 生成查询变体
variations = multi_retriever.generate_queries(query)
print(f"\n生成了 {len(variations)} 个查询变体:")
for i, var in enumerate(variations, 1):
print(f" {i}. {var}")
# 使用多查询进行检索
results = await multi_retriever.retrieve_with_multi_query(query, k=3)
print(f"\n前 {len(results)} 个结果:")
for i, result in enumerate(results):
print(f"\n结果 {i+1}:")
print(f" 分数:{result['max_score']:.3f}")
print(f" 被 {result['match_count']} 个查询匹配")
print(f" 内容:{result['document'].page_content[:80]}...")
return results
# 运行演示
try:
import asyncio
results = asyncio.run(run_multi_query_demo())
print(f"\n✓ 成功使用多查询方法检索到 {len(results)} 个文档")
except Exception as e:
print(f"演示模式 - 多查询检索概念已演示")
print("注意:这将在真实的向量存储和 LLM 下工作")
💡 查询生成流程
temperature=0.5:设置适中的随机性(温度值),在保证逻辑性的同时,让生成的查询变体具有足够的多样性。
提示词工程:通过精心设计的提示词,明确指导大语言模型生成 5 种特定类型的变体。
解析变体:从大语言模型的响应中提取出带编号的查询列表。
始终包含原始查询:确保用户的原始问题也会被纳入搜索范围,防止语义漂移。
🎯 检索与聚合策略
去重处理:利用文档内容的哈希值来识别唯一的文档,避免重复结果。
分数聚合:追踪每个文档在所有查询中的最高分和平均分。
匹配计数:如果一个文档被多个不同的查询命中,它的排名会更高(权重增加)。
双重排序:排序逻辑优先依据"匹配数量",其次依据"最高分数"。
⚠️ 性能考量
延迟问题:由于需要执行多次搜索,整体搜索时间会增加 2-3 倍。
优化建议:在可能的情况下,务必使用异步或并行搜索来减少等待时间。
成本问题:如果使用付费的向量数据库或 Embedding 服务,多次查询会导致 API 调用成本上升。
查询缓存:对于常见的问题,建议缓存生成的查询变体,避免重复生成。
💡 预期输出示例
以下是代码运行后控制台可能打印的输出结果:
生成了 6 个查询变体:
1. 如何在 Web 应用程序中实现缓存?
2. Web 应用程序缓存策略的最佳实践是什么?
3. 为网站性能优化实现缓存机制
4. 如何添加缓存以提高 Web 应用的速度和性能?
5. Web 应用程序缓存实现的技术和方法
6. 在现代 Web 应用程序中设置缓存系统
结果 1:
分数:0.923
被 4 个查询匹配
内容:缓存对于 Web 应用程序的性能至关重要。你可以在几个不同的层面实现它...
结果 2:
分数:0.887
被 3 个查询匹配
内容:Redis 和 Memcached 是 Web 应用程序流行的缓存解决方案。Redis 提供了持久化...
🔍 如何在 LangChain RAG 中实现父子分块
🔍 层级分块策略
父子分块在保留文档结构的同时,实现了精确检索。它使用小子块(Children)进行检索,但将更大的上下文(Parents)返回给大语言模型,从而同时提供精确度和全面的上下文信息。
逐步教程:父子分块
🔍 理解层级分块:
子块(约 400 字符):短小、精确的块,用于提高检索的准确性。
父块(约 2000 字符):更大的上下文块,最终返回给大语言模型。
InMemoryStore:用于存储父文档,以便通过 ID 快速检索。
UUID 映射:将每个子块链接回其所属的父文档。
代码实现
python
# 如何在 LangChain 中实现父子分块
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain.retrievers import ParentDocumentRetriever
from langchain_chroma import Chroma
from langchain_google_genai import GoogleGenerativeAIEmbeddings
import uuid
import os
from dotenv import load_dotenv
load_dotenv()
class HierarchicalChunker:
"""逐步学习如何在 LangChain 中实现父子分块"""
def __init__(self, vector_store, docstore=None):
self.vector_store = vector_store
# [LangChain 1.x] 使用 InMemoryStore 存储父文档
self.docstore = docstore or InMemoryStore()
self.child_chunks = []
self.parent_chunks = []
# 子分块器 - 用于检索的小块
self.child_splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", ", ", " ", ""]
)
# 父分块器 - 用于上下文的大块
self.parent_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=200,
separators=["\n\n\n", "\n\n", "\n", ". "]
)
def create_hierarchical_chunks(self, documents):
"""创建具有关联关系的父块和子块"""
all_parents = []
all_children = []
for doc in documents:
# 1. 创建父块
parent_chunks = self.parent_splitter.split_documents([doc])
for parent in parent_chunks:
# 生成唯一的父 ID
parent_id = str(uuid.uuid4())
parent.metadata["doc_id"] = parent_id
parent.metadata["type"] = "parent"
# 2. 从这个父块创建子块
children = self.child_splitter.split_documents([parent])
for child in children:
# 将子块链接到父块
child.metadata["parent_id"] = parent_id
child.metadata["type"] = "child"
all_children.append(child)
all_parents.append(parent)
# 将块存储为实例属性
self.parent_chunks = all_parents
self.child_chunks = all_children
return all_parents, all_children
def setup_parent_child_retriever(self, documents):
"""设置具有父子关系的检索器"""
# 创建层级块
parents, children = self.create_hierarchical_chunks(documents)
# 将父块存入文档存储
parent_docs = {}
for parent in parents:
parent_docs[parent.metadata["doc_id"]] = parent
# [LangChain 1.x] 使用 mset 批量存储
self.docstore.mset(list(parent_docs.items()))
# 将子块添加到向量存储(这是实际被搜索的部分)
self.vector_store.add_documents(children)
# 创建父文档检索器
retriever = ParentDocumentRetriever(
vectorstore=self.vector_store,
docstore=self.docstore,
child_splitter=self.child_splitter,
parent_splitter=self.parent_splitter
)
return retriever
def retrieve_with_context(self, query: str, k: int = 4):
"""检索子块,但返回父文档"""
# 搜索相关的子块
child_results = self.vector_store.similarity_search_with_score(
query,
k=k*2, # 获取更多子块以找到多样化的父块
filter={"type": "child"} # 仅搜索子块
)
# 获取唯一的父文档
parent_ids = set()
parent_docs = []
for child, score in child_results:
parent_id = child.metadata.get("parent_id")
if parent_id and parent_id not in parent_ids:
parent_ids.add(parent_id)
# 检索父文档
# [LangChain 1.x] mget 返回列表
parent = self.docstore.mget([parent_id])[0]
if parent:
parent_docs.append({
"document": parent,
"score": score,
"matched_child": child.page_content[:100] # 显示匹配的子块片段
})
# 返回前 k 个父文档
return parent_docs[:k]
# 创建 Embeddings 和向量存储
embeddings = GoogleGenerativeAIEmbeddings(
model="models/embedding-001",
google_api_key=os.getenv("GOOGLE_API_KEY")
)
# [LangChain 1.x] Chroma 初始化方式
vector_store = Chroma(
embedding_function=embeddings,
persist_directory="./hierarchical_db"
)
# 示例用法 - 父子分块演示
print("=== 父子分块演示 ===")
hierarchical_chunker = HierarchicalChunker(vector_store)
# 创建用于测试的示例文档
sample_document_content = """
# 机器学习基础
机器学习是人工智能的一个子集,它使计算机能够从数据中学习并做出决策,而无需为每个任务进行显式编程。
## 机器学习的类型
### 监督学习
监督学习使用带标签的训练数据来学习从输入变量到输出变量的映射函数。常见的算法包括线性回归、决策树和神经网络。
监督学习的例子:
- 图像分类:使用带标签的图像训练模型识别猫与狗
- 邮件垃圾检测:使用带标签的邮件识别垃圾邮件
- 股价预测:使用历史数据预测未来价格
### 无监督学习
无监督学习在没有带标签示例的情况下发现数据中的隐藏模式。它包括聚类、降维和关联规则学习。
无监督学习的例子:
- 客户细分:根据购买行为对客户进行分组
- 异常检测:识别网络流量中的异常模式
- 购物篮分析:发现经常一起购买的产品
### 强化学习
强化学习通过与环境交互来训练代理做出决策,从奖励和惩罚中学习。
强化学习的例子:
- 游戏对弈:AlphaGo 精通围棋
- 自动驾驶汽车:学习在交通中导航
- 推荐系统:优化用户参与度
## 关键概念
### 训练与测试
机器学习模型在训练集上进行训练,并在单独测试集上进行评估,以衡量泛化性能。
### 过拟合与欠拟合
过拟合发生在模型过度学习训练数据(包括噪声)时。欠拟合发生在模型过于简单无法捕捉底层模式时。
### 特征工程
选择、修改或创建输入特征以提高模型性能的过程。
"""
# 创建示例文档
class MockDocument:
def __init__(self, content, metadata=None):
self.page_content = content
self.metadata = metadata or {"source": "ml_tutorial.txt"}
sample_doc = MockDocument(sample_document_content)
documents = [sample_doc]
# 设置父子检索
print("正在设置父子检索系统...")
retriever = hierarchical_chunker.setup_parent_child_retriever(documents)
print(f"✓ 创建了 {len(hierarchical_chunker.child_chunks)} 个子块")
print(f"✓ 创建了 {len(hierarchical_chunker.parent_chunks)} 个父块")
# 测试查询
test_queries = [
"什么是监督学习?",
"给我一些强化学习的例子",
"过拟合和欠拟合的区别是什么?"
]
for query in test_queries:
print(f"\n--- 查询:'{query}' ---")
# 使用完整的父上下文进行检索
results = hierarchical_chunker.retrieve_with_context(query, k=2)
print(f"检索到 {len(results)} 个父文档:")
for i, result in enumerate(results):
print(f"\n父文档 {i+1} (分数:{result['score']:.3f}):")
print(f" 匹配的子块:{result['matched_child'][:100]}...")
print(f" 父上下文 ({len(result['document'].page_content)} 字符):")
print(f" {result['document'].page_content[:150]}...")
print("\n=== 分块策略比较 ===")
# 计算平均长度
avg_child_len = sum(len(c.page_content) for c in hierarchical_chunker.child_chunks) // len(hierarchical_chunker.child_chunks)
avg_parent_len = sum(len(p.page_content) for p in hierarchical_chunker.parent_chunks) // len(hierarchical_chunker.parent_chunks)
print(f"子块:{len(hierarchical_chunker.child_chunks)} 个 (平均 ~{avg_child_len} 字符)")
print(f"父块:{len(hierarchical_chunker.parent_chunks)} 个 (平均 ~{avg_parent_len} 字符)")
print("\n✓ 子块提供精确匹配")
print("✓ 父块为生成提供丰富的上下文")
💡 分块策略详解
双层层级结构:父块提供上下文,子块实现精确搜索。
子块大小(400 字符):足够小,以确保语义搜索的准确性。
父块大小(2000 字符):足够大,以提供全面的上下文信息。
重叠策略:子块重叠 50 字符,父块重叠 200 字符,以保持内容的连续性。
🎯 检索流程深入剖析
搜索阶段:
系统搜索小型的子块,以进行精确匹配。
父块查找:
利用每个匹配到的子块的 parent_id,获取完整的父文档。
去重处理:
多个子块可能属于同一个父块------我们只返回唯一的父块(避免重复)。
上下文交付:
大语言模型接收的是完整的父文档,而不是碎片化的子块。
⚠️ 重要注意事项
存储开销:需要同时存储父块和子块,占用更多空间。
文档存储选择:小数据集使用 InMemoryStore,生产环境建议使用 Redis 或 MongoDB。
父块大小调整:应根据你所用大语言模型的上下文窗口大小进行调整。
元数据保留:父块和子块都会继承原始文档的元数据。
📊 预期收益
之前(标准分块):
在分块边界处丢失上下文。
在精确度和上下文之间难以取舍(顾此失彼)。
之后(父子分块):
保留了完整的上下文。
实现了高精度检索 + 全面的上下文信息(兼得)。
🔍 如何在 LangChain RAG 中使用元数据过滤
逐步元数据过滤实现指南
🔍 理解元数据过滤:
元数据索引:预先构建所有元数据字段的索引,用于分析。
智能过滤器:根据查询和用户上下文动态生成过滤条件。
多级过滤:包括基于时间、部门、文档类型、访问级别的过滤。
分数加权:根据元数据匹配情况调整相关性得分。
代码实现
python
from datetime import datetime, timedelta
from typing import Dict, Any, List
import json
import os
from dotenv import load_dotenv
# LangChain 1.x 导入
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_chroma import Chroma
load_dotenv()
class MetadataFilteredRetriever:
"""逐步学习如何在 LangChain 中实现元数据过滤"""
def __init__(self, vector_store):
self.vector_store = vector_store
self.metadata_index = {}
def build_metadata_index(self, documents):
"""构建元数据索引以进行快速过滤"""
for doc in documents:
for key, value in doc.metadata.items():
if key not in self.metadata_index:
self.metadata_index[key] = set()
self.metadata_index[key].add(value)
print(f"构建了包含 {len(self.metadata_index)} 个字段的元数据索引")
for key, values in self.metadata_index.items():
print(f" {key}: {len(values)} 个唯一值")
def create_smart_filter(self,
query: str,
user_context: Dict[str, Any]) -> Dict:
"""根据查询和上下文创建智能过滤器"""
filters = {}
# 基于时间的过滤
if "recent" in query.lower() or "latest" in query.lower():
week_ago = (datetime.now() - timedelta(days=7)).isoformat()
filters["date"] = {"$gte": week_ago}
# 基于用户上下文的部门/团队过滤
if user_department := user_context.get("department"):
filters["department"] = {"$in": [user_department, "company-wide"]}
# 根据查询推断文档类型
query_lower = query.lower()
if any(word in query_lower for word in ["policy", "procedure", "guideline"]):
filters["doc_type"] = {"$in": ["policy", "procedure"]}
elif any(word in query_lower for word in ["report", "analysis", "study"]):
filters["doc_type"] = {"$in": ["report", "research"]}
# 访问级别过滤
user_clearance = user_context.get("clearance_level", 1)
filters["required_clearance"] = {"$lte": user_clearance}
return filters
def retrieve_with_metadata(self,
query: str,
filters: Dict = None,
k: int = 5,
score_threshold: float = 0.7):
"""使用复杂的元数据过滤进行检索"""
# 构建搜索参数
search_kwargs = {
"k": k * 2, # 获取额外结果用于后过滤
}
if filters:
# Chroma 期望多个条件的过滤器包装在 $and 中
if len(filters) > 1:
search_kwargs["filter"] = {"$and": [
{key: value} for key, value in filters.items()
]}
else:
search_kwargs["filter"] = filters
# 执行搜索
# [LangChain 1.x] 确保调用方式正确
results = self.vector_store.similarity_search_with_score(
query,
**search_kwargs
)
# 后处理结果
filtered_results = []
for doc, score in results:
if score >= score_threshold:
# 计算元数据相关性加权
metadata_boost = self._calculate_metadata_boost(
doc.metadata,
query
)
adjusted_score = score * (1 + metadata_boost)
filtered_results.append({
"document": doc,
"base_score": score,
"adjusted_score": adjusted_score,
"metadata": doc.metadata
})
# 按调整后的分数排序
filtered_results.sort(
key=lambda x: x["adjusted_score"],
reverse=True
)
return filtered_results[:k]
def _calculate_metadata_boost(self,
metadata: Dict,
query: str) -> float:
"""根据元数据计算相关性加权"""
boost = 0.0
query_lower = query.lower()
# 标题中包含查询词则加权
if title := metadata.get("title", "").lower():
query_words = query_lower.split()
matches = sum(1 for word in query_words if word in title)
boost += matches * 0.1
# 近期文档加权
if date_str := metadata.get("date"):
try:
doc_date = datetime.fromisoformat(date_str)
days_old = (datetime.now() - doc_date).days
if days_old < 30:
boost += 0.2
elif days_old < 90:
boost += 0.1
except:
pass
# 高优先级文档加权
if metadata.get("priority") == "high":
boost += 0.15
return min(boost, 0.5) # 加权上限为 50%
# 为演示创建 Embeddings 和向量存储
# 初始化 Embeddings
embeddings = GoogleGenerativeAIEmbeddings(
model="models/embedding-001",
google_api_key=os.getenv("GOOGLE_API_KEY")
)
# 创建向量存储
vector_store = Chroma(
embedding_function=embeddings,
persist_directory="./metadata_db"
)
# 示例用法 - 元数据过滤演示
print("=== 元数据过滤演示 ===")
metadata_retriever = MetadataFilteredRetriever(vector_store)
# 用户上下文
user_context = {
"department": "engineering",
"clearance_level": 3,
"role": "senior_developer"
}
# 创建智能过滤器
filters = metadata_retriever.create_smart_filter(
"recent security policies for API development",
user_context
)
print("生成的过滤器:", json.dumps(filters, indent=2))
# 为了演示目的,我们展示过滤器格式而不是实际检索
print("\n--- Chroma 过滤器格式 ---")
if len(filters) > 1:
chroma_filter = {"$and": [
{key: value} for key, value in filters.items()
]}
else:
chroma_filter = filters
print(f"兼容 Chroma 的过滤器: {json.dumps(chroma_filter, indent=2)}")
print(f"\n用户上下文: {user_context}")
# 创建智能过滤器
filters = metadata_retriever.create_smart_filter(
"recent security policies for API development",
user_context
)
print(f"\n生成的过滤器: {json.dumps(filters, indent=2)}")
# 演示基于元数据的相关性打分
print("\n--- 元数据加权计算演示 ---")
sample_docs = [
{"content": "2024 API 安全最佳实践", "metadata": {"date": "2024-01-15", "department": "engineering", "clearance": 3}},
{"content": "基础 API 指南", "metadata": {"date": "2023-06-01", "department": "general", "clearance": 1}},
{"content": "高级安全协议", "metadata": {"date": "2024-02-20", "department": "security", "clearance": 4}},
]
for doc in sample_docs:
# 为了演示,使用示例查询代替用户上下文
boost = metadata_retriever._calculate_metadata_boost(doc["metadata"], "API security policies")
# 手动计算基于用户上下文的加权
context_boost = 0.0
if doc["metadata"].get("department") == user_context.get("department"):
context_boost += 0.3
if doc["metadata"].get("clearance", 0) <= user_context.get("clearance_level", 0):
context_boost += 0.2
print(f"\n文档: {doc['content']}")
print(f" 元数据: {doc['metadata']}")
print(f" 基于查询的加权: {boost:.3f}")
print(f" 基于上下文的加权: {context_boost:.3f}")
print(f" 总加权: {(boost + context_boost):.3f}")
print("\n✓ 匹配用户部门和权限级别的文档获得更高分数")
print("✓ 近期文档获得额外加权")
print("✓ 这确保了为每个用户提供上下文相关的结果")
💡 智能过滤器创建详解
基于时间的过滤:
检测到 "recent"(最近)、"latest"(最新)等关键词 → 使用 ISO 格式日期过滤到过去 7 天内。
部门过滤:
使用用户的部门 + "company-wide"(全公司范围),以显示相关文档和通用文档。
文档类型推断:
分析查询词,自动按文档类型进行过滤。
访问控制:
确保用户只能看到其权限级别或低于该级别的文档。
🎯 元数据加权计算
标题匹配(每个词 0.1):如果标题中包含查询词,则增加文档权重。
近期加权(0.2/0.1):近期文档得分更高。
优先级加权(0.15):高优先级文档获得优先权。
最大上限(0.5):防止元数据权重过高而掩盖了内容本身的相关性。
⚠️ 过滤器语法参考
{
"date": {"$gte": "2024-01-01"}, // 日期大于或等于
"department": {"$in": ["eng", "all"]}, // 匹配列表中的任意项
"doc_type": {"$eq": "policy"}, // 精确匹配
"clearance": {"$lte": 3}, // 小于或等于
"tags": {"$contains": "security"} // 数组包含
}
📊 预期输出
生成的过滤器:
json
{
"date": {"$gte": "2024-12-20T..."},
"department": {"$in": ["engineering", "company-wide"]},
"doc_type": {"$in": ["policy", "procedure"]},
"required_clearance": {"$lte": 3}
}
结果 1:
调整后分数:0.965(基础分:0.82 + 元数据加权:0.145)
元数据:{"title": "API 安全策略", "date": "2024-12-25", "priority": "high"}
内容:本文档概述了所有 API 端点的安全要求...
🔍 如何在 LangChain RAG 中实现重排序与融合
🔍 为什么要重排序?
初始检索通常使用快速但不够精确的方法(如向量相似度)。重排序应用更复杂的评分模型来重新排列结果,而融合技术则结合多种检索方法的结果,以获得更好的覆盖率和准确性。
逐步教程:提升 RAG 效果的重排序
🔍 理解重排序组件:
Cross-Encoder(交叉编码器):比双塔模型(Bi-encoders)更精准,它成对评估查询和文档的相关性。
LLM 重排序:使用语言模型根据自定义标准对相关性进行评分。
倒数排名融合(RRF):结合多种排序方法的结果。
多级流水线:Cross-Encoder → LLM 重排序 → 融合,以获得最佳效果。
📦 所需依赖:
在运行此重排序实现之前,请安装所需的包:
python
pip install sentence-transformers
代码实现
python
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import (
LLMChainExtractor,
CrossEncoderReranker
)
from langchain.prompts import PromptTemplate
from sentence_transformers import CrossEncoder
import numpy as np
from typing import List, Dict, Tuple
import os
from dotenv import load_dotenv
from langchain_google_genai import ChatGoogleGenerativeAI
load_dotenv()
class AdvancedReranker:
"""具备融合能力的多级重排序器"""
def __init__(self, llm=None):
self.llm = llm or ChatGoogleGenerativeAI(
model="gemini-2.0-flash",
temperature=0
)
# 初始化用于重排序的 Cross-Encoder
# [LangChain 1.x] 使用 sentence-transformers 库加载模型
self.cross_encoder = CrossEncoder(
'cross-encoder/ms-marco-MiniLM-L-6-v2'
)
def llm_rerank(self, query: str, documents: List, top_k: int = 5):
"""使用 LLM 根据相关性重新对文档进行排序"""
rerank_prompt = PromptTemplate(
input_variables=["query", "document", "index"],
template="""给定查询和文档,请对相关性进行 0-10 分的评分。
查询:{query}
文档 {index}:{document}
考虑因素:
1. 直接回答查询
2. 相关的背景信息
3. 信息的质量和清晰度
相关性评分 (0-10):"""
)
scored_docs = []
for i, doc in enumerate(documents):
# 获取 LLM 相关性评分
# [LangChain 1.x] 使用 invoke 方法
response = self.llm.invoke(
rerank_prompt.format(
query=query,
document=doc.page_content[:500],
index=i+1
)
)
try:
# 解析分数
score = float(response.content.strip())
except:
score = 0.0
scored_docs.append({
"document": doc,
"llm_score": score / 10.0 # 归一化到 0-1
})
# 按 LLM 分数排序
scored_docs.sort(key=lambda x: x["llm_score"], reverse=True)
return scored_docs[:top_k]
def cross_encoder_rerank(self,
query: str,
documents: List,
top_k: int = 5):
"""使用 Cross-Encoder 进行精确重排序"""
# 准备 Cross-Encoder 的输入对
pairs = [[query, doc.page_content] for doc in documents]
# 获取 Cross-Encoder 评分
scores = self.cross_encoder.predict(pairs)
# 创建评分文档列表
scored_docs = []
for doc, score in zip(documents, scores):
scored_docs.append({
"document": doc,
"cross_encoder_score": score
})
# 按分数排序
scored_docs.sort(
key=lambda x: x["cross_encoder_score"],
reverse=True
)
return scored_docs[:top_k]
def reciprocal_rank_fusion(self,
result_sets: List[List[Dict]],
k: int = 60):
"""使用 RRF 融合多个结果集"""
# 追踪文档分数
doc_scores = {}
for results in result_sets:
for rank, result in enumerate(results):
# 获取文档标识符
doc_id = hash(result["document"].page_content)
# 计算 RRF 分数
rrf_score = 1.0 / (k + rank + 1)
if doc_id not in doc_scores:
doc_scores[doc_id] = {
"document": result["document"],
"scores": [],
"methods": []
}
doc_scores[doc_id]["scores"].append(rrf_score)
doc_scores[doc_id]["methods"].append(
result.get("method", "unknown")
)
# 计算最终分数
fused_results = []
for doc_data in doc_scores.values():
final_score = sum(doc_data["scores"])
fused_results.append({
"document": doc_data["document"],
"fusion_score": final_score,
"num_methods": len(doc_data["scores"]),
"methods": list(set(doc_data["methods"]))
})
# 按融合分数排序
fused_results.sort(
key=lambda x: x["fusion_score"],
reverse=True
)
return fused_results
def hybrid_rerank_and_fuse(self,
query: str,
initial_results: List,
top_k: int = 5):
"""完整的重排序和融合流水线"""
# 第一阶段:Cross-Encoder 重排序
cross_encoder_results = self.cross_encoder_rerank(
query,
[r["document"] for r in initial_results],
top_k=top_k*2
)
# 添加方法标签
for r in cross_encoder_results:
r["method"] = "cross_encoder"
# 第二阶段:LLM 重排序(仅针对头部结果)
top_docs = [r["document"] for r in cross_encoder_results[:10]]
llm_results = self.llm_rerank(query, top_docs, top_k=top_k)
# 添加方法标签
for r in llm_results:
r["method"] = "llm_rerank"
# 第三阶段:融合结果
fused_results = self.reciprocal_rank_fusion(
[cross_encoder_results, llm_results],
k=60
)
return fused_results[:top_k]
# 为演示创建 Embeddings 和向量存储
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_chroma import Chroma
embeddings = GoogleGenerativeAIEmbeddings(
model="models/embedding-001",
google_api_key=os.getenv("GOOGLE_API_KEY")
)
vector_store = Chroma(
embedding_function=embeddings,
persist_directory="./rerank_db"
)
# 示例用法 - 重排序演示
print("=== 高级重排序演示 ===")
reranker = AdvancedReranker()
# 为演示创建模拟初始结果
class MockDoc:
def __init__(self, content):
self.page_content = content
self.metadata = {"source": "database_guide.pdf"}
# 模拟具有不同相关性的初始检索结果
mock_documents = [
("数据库查询优化基础", 0.82),
("高级 SQL 性能调优", 0.78),
("NoSQL 数据库设计模式", 0.75),
("查询执行计划详解", 0.73),
("数据库索引策略", 0.71),
("数据库缓存策略", 0.69),
("数据库连接池", 0.67),
("PostgreSQL 中的查询优化", 0.85), # 高相关性但初始排名较低
("MongoDB 查询性能技巧", 0.65),
("数据库分片技术", 0.63),
]
initial_results = [(MockDoc(content), score) for content, score in mock_documents]
print(f"初始检索:{len(initial_results)} 个文档")
print("\n重排序前的 Top 5:")
for i, (doc, score) in enumerate(initial_results[:5]):
print(f"{i+1}. [{score:.3f}] {doc.page_content}")
# 转换为预期格式
formatted_results = [
{"document": doc, "initial_score": score}
for doc, score in initial_results
]
# 应用混合重排序和融合
final_results = reranker.hybrid_rerank_and_fuse(
query="如何优化数据库查询?",
initial_results=formatted_results,
top_k=5
)
print("\n\n重排序后的 Top 5:")
for i, result in enumerate(final_results):
print(f"\n结果 {i+1}:")
print(f" 融合分数:{result['fusion_score']:.3f}")
print(f" 方法:{', '.join(result['methods'])}")
print(f" 内容:{result['document'].page_content}")
print("\n✓ Cross-Encoder 基于语义相关性进行了重排序")
print("✓ LLM 基于特定查询标准进行了评分")
print("✓ RRF 结合了两种方法以实现最佳排序")
💡 LLM 重排序流程
提示词设计:要求 LLM 根据特定标准,按 0-10 的量表对相关性进行评分。
temperature=0:确保不同文档之间的评分标准一致(消除随机性)。
文档截断:仅取前 500 个字符,以控制 Token 使用量。
错误处理:如果无法解析 LLM 的响应,则默认为 0 分。
🎯 交叉编码器与双塔编码器
双塔编码器(初始检索):
分别对查询和文档进行编码。
速度快但准确性较低。
适用于初始过滤(海量筛选)。
交叉编码器(重排序):
将查询和文档放在一起处理(联合注意力机制)。
速度较慢但更准确。
非常适合对头部结果进行重排序(精细筛选)。
📊 倒数排名融合(RRF)解析
公式:
RRFScore=sumfrac1k+rankRRF Score = sum frac{1}{k + rank}RRFScore=sumfrac1k+rank
k=60:控制分数分布的常数。
rank:每个结果列表中的位置(从 0 开始)。
优势:无需对分数进行归一化即可结合排名(解决了不同模型分值不可比的问题)。
示例:
排名 0 的文档 → 1/(60+0)=0.01641 / (60 + 0) = 0.01641/(60+0)=0.0164
排名 1 的文档 → 1/(60+1)=0.01611 / (60 + 1) = 0.01611/(60+1)=0.0161
🚀 混合流水线优势
第一阶段(交叉编码器):对初始结果进行快速、准确的重排序。
第二阶段(LLM 重排序):增加语义理解和自定义标准(如时效性、权威性)。
第三阶段(融合):结合两种方法的优势。
结果:精确率指标通常可提升 20-40%。
💡 预期输出
最终重排序结果:
结果 1:
融合分数:0.048
方法:交叉编码器, LLM 重排序
内容:数据库查询优化涉及几个关键策略。首先,确保适当的索引...
结果 2:
融合分数:0.032
方法:交叉编码器, LLM 重排序
内容:SQL 性能调优需要了解执行计划。使用 EXPLAIN 来分析...
🔍 如何构建完整的 RAG 流水线 - 教程
结合所有 RAG 技术的逐步指南
🔍 完整流水线架构:
模块化设计:每个组件都可以独立启用或禁用。
顺序优化:每个阶段都对上一阶段的结果进行提炼。
元数据追踪:监控文档在流水线中的流转过程。
生产就绪:包含错误处理、异步支持和性能指标。
代码实现
python
from typing import Dict, List, Optional, Any
import asyncio
from datetime import datetime
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.prompts import PromptTemplate
import os
from dotenv import load_dotenv
load_dotenv()
# 注意:这个完整的流水线需要前面章节的以下类:
# - AdvancedMultiQueryRetriever (来自多查询章节)
# - HierarchicalChunker (来自父子分块章节)
# - MetadataFilteredRetriever (来自元数据过滤章节)
# - AdvancedReranker (来自重排序章节)
#
# 要运行此演示,请执行以下操作之一:
# 1. 将上面章节的所有类定义复制到您的脚本中
# 2. 或者将每个类保存在单独的文件中并导入它们
#
# 为了演示目的,我们在下面创建一个简化版本
class SimplifiedAdvancedRAG:
"""学习如何使用 LangChain 构建生产级 RAG - 完整实现"""
def __init__(self, vector_store, llm=None):
self.vector_store = vector_store
self.llm = llm or ChatGoogleGenerativeAI(
model="gemini-2.0-flash",
temperature=0.3,
google_api_key=os.getenv("GOOGLE_API_KEY")
)
# 流水线配置
self.pipeline_steps = []
async def query(self,
query: str,
user_context: Dict = None,
use_multi_query: bool = True,
use_reranking: bool = True,
k: int = 5):
"""执行高级 RAG 流水线"""
print(f"正在处理查询:{query}")
# 第一步:多查询扩展
if use_multi_query:
print("\n1. 生成查询变体...")
# [LangChain 1.x] 调用异步方法
initial_results = await self.multi_query.retrieve_with_multi_query(
query,
k=k*3
)
else:
initial_results = self.vector_store.similarity_search_with_score(
query,
k=k*3
)
# 第二步:元数据过滤
if user_context:
print("\n2. 应用元数据过滤器...")
filters = self.metadata_filter.create_smart_filter(
query,
user_context
)
# 使用过滤器重新搜索
filtered_results = self.metadata_filter.retrieve_with_metadata(
query,
filters=filters,
k=k*2
)
else:
# 如果没有上下文,直接转换结果格式
filtered_results = [
{"document": r["document"], "adjusted_score": r.get("max_score", 0)}
for r in initial_results
]
# 第三步:获取父文档以获得更好的上下文
print("\n3. 检索父上下文...")
parent_docs = []
for result in filtered_results[:k*2]:
# 检查文档是否有父级
if parent_id := result["document"].metadata.get("parent_id"):
# [LangChain 1.x] mget 返回列表
parent = self.hierarchical.docstore.mget([parent_id])[0]
if parent:
parent_docs.append({
"document": parent,
"child_score": result["adjusted_score"]
})
else:
parent_docs.append({
"document": result["document"],
"child_score": result["adjusted_score"]
})
# 第四步:重排序和融合
if use_reranking and len(parent_docs) > k:
print("\n4. 重排序结果...")
final_results = self.reranker.hybrid_rerank_and_fuse(
query,
parent_docs,
top_k=k
)
else:
final_results = parent_docs[:k]
# 第五步:生成响应
print("\n5. 生成响应...")
response = self._generate_response(query, final_results)
return {
"answer": response,
"sources": final_results,
"metadata": {
"total_retrieved": len(initial_results),
"after_filtering": len(filtered_results),
"final_sources": len(final_results)
}
}
def _generate_response(self, query: str, results: List[Dict]):
"""使用检索到的文档生成响应"""
# 准备上下文
context_parts = []
for i, result in enumerate(results):
doc = result["document"]
source = doc.metadata.get("source", "Unknown")
context_parts.append(
f"[来源 {i+1}: {source}]\n{doc.page_content}\n"
)
context = "\n---\n".join(context_parts)
# 生成响应
prompt = f"""根据提供的上下文回答问题。
如果无法在上下文中找到答案,请说明。
上下文:
{context}
问题:{query}
答案:"""
# [LangChain 1.x] 使用 invoke
response = self.llm.invoke(prompt)
return response.content
# 演示 - 完整的高级 RAG 流水线
print("\n=== 完整的高级 RAG 流水线演示 ===")
# 初始化组件(使用前面章节的或模拟实现)
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_chroma import Chroma
# 创建 Embeddings 和向量存储
embeddings = GoogleGenerativeAIEmbeddings(
model="models/embedding-001",
google_api_key=os.getenv("GOOGLE_API_KEY")
)
vector_store = Chroma(
embedding_function=embeddings,
persist_directory="./advanced_rag_db"
)
# 创建简化的流水线
simplified_rag = SimplifiedAdvancedRAG(vector_store)
# 演示函数
async def run_advanced_rag_demo():
# 添加示例文档以演示流水线
sample_docs = [
"API 安全最佳实践包括使用 OAuth 2.0 进行身份验证,实施速率限制,以及验证所有输入数据。",
"现代 API 开发需要仔细关注安全头、CORS 策略以及传输中数据的加密。",
"零信任架构正成为 API 安全的标准,需要在每个级别进行身份验证。"
]
# 如果向量存储为空,则添加文档
# [LangChain 1.x] 检查集合计数的方法
if hasattr(vector_store, '_collection') and vector_store._collection.count() == 0:
print("正在向向量存储添加示例文档...")
vector_store.add_texts(sample_docs)
# 运行查询
result = await simplified_rag.query(
query="API 安全的最佳实践是什么?",
k=3
)
print("\n🎯 查询结果:")
print(f"\n答案预览:{result['answer'][:200]}...")
print(f"\n流水线信息:")
for key, value in result['metadata'].items():
print(f" {key}: {value}")
return result
# 运行演示
print("\n正在运行完整的高级 RAG 流水线...")
try:
import asyncio
result = asyncio.run(run_advanced_rag_demo())
print("\n✅ 高级 RAG 流水线成功完成!")
print("\n👉 注意:要使用所有功能,请复制上面章节的类实现。")
except Exception as e:
print(f"\n⚠️ 演示遇到错误:{type(e).__name__}")
print("\n💡 要以完整功能运行此演示:")
print(" 1. 复制上面章节的所有类定义")
print(" 2. 确保您的 .env 文件中有有效的 GOOGLE_API_KEY")
print(" 3. 安装所有所需的依赖项")
print("\n📚 该演示展示了高级 RAG 流水线的概念流程。")
💡 流水线流程详解
第一步 - 多查询扩展:
生成查询变体 → 从多个角度检索 → 获得 k×3 个初始结果。
第二步 - 元数据过滤:
根据上下文应用智能过滤器 → 结合元数据加权重新评分 → 获得 k×2 个过滤后的结果。
第三步 - 父文档检索:
将子块映射回父块 → 检索完整上下文 → 对父文档进行去重。
第四步 - 重排序与融合:
交叉编码器 + LLM 重排序 → 融合结果 → 得到最终 k 个文档。
第五步 - 响应生成:
格式化上下文 + 查询 → 生成答案 → 连同元数据一起返回。
🎯 配置选项
- use_multi_query:启用/禁用查询扩展(会增加延迟)。
- use_reranking:启用/禁用重排序(能提高质量)。
- k 参数:最终文档的数量(在质量和上下文大小之间取得平衡)。
- user_context:传入用户元数据以进行个性化过滤。
⚠️ 性能调优
延迟优化:
对于简单查询,禁用多查询功能。
使用异步/并行处理。
缓存频繁查询。
减少重排序阶段。
质量优化:
启用所有流水线阶段。
增加 k 值以获取更多上下文。
微调重排序模型。
添加特定领域的过滤器。
💡 流水线优势总结
多查询:召回率提升 40-60%。
父子分块:在不损失精度的情况下提供更好的上下文。
元数据过滤:减少噪音,提高相关性。
重排序:精确率指标提升 20-30%。
融合:结合了多种方法的优势。
📊 预期输出
正在处理查询:API 安全的最新最佳实践是什么?
1. 正在生成查询变体...
2. 正在应用元数据过滤器...
3. 正在检索父上下文...
4. 正在重排序结果...
5. 正在生成响应...
答案:根据最新文档,以下是当前 API 安全的最佳实践:
1. **身份验证与授权**:使用 JWT 令牌实施 OAuth 2.0...
2. **速率限制**:使用滑动窗口速率限制,每分钟 100 个请求...
3. **输入验证**:使用 JSON Schema 验证所有输入...
元数据:{
"total_retrieved": 45,
"after_filtering": 18,
"final_sources": 5
}
📊 LangChain RAG 技术 - 性能对比指南
| 技术 | 召回率提升 | 精确率提升 | 延迟影响 | 最佳适用场景 |
|---|---|---|---|---|
| 多查询检索 | +40-60% | +10-15% | +2-3倍 | 模糊不清的查询 |
| 父子分块 | +5-10% | +20-30% | +1.2倍 | 长文档处理 |
| 元数据过滤 | -5-10%* | +30-50% | +1.1倍 | 结构化数据 |
| 重排序 | 0% | +20-40% | +1.5-2倍 | 对质量要求极高的场景 |
注:元数据过滤通常会减少检索到的文档总数(因为它在排除不相关内容),所以召回率看似下降,但这是为了换取更高的精确率。
✨ 高级 RAG 最佳实践
查询处理
在选择技术之前,先分析查询意图。
对于宽泛或模糊的查询,使用多查询技术。
缓存常见模式的查询扩展结果。
监控查询性能指标。
文档结构
根据内容类型设计分块层级。
在分块中保留语义边界(不要把一段话切断)。
在数据摄入阶段丰富元数据。
测试不同的分块策略。
检索优化
根据具体用例平衡精确率和召回率。
尽可能使用元数据进行预过滤。
实施降级/回退策略(当高级检索失败时,能退回基础检索)。
监控检索质量指标。
生产环境考量
分析每个组件的延迟情况。
尽可能实施异步处理。
对昂贵的操作(如 Embedding、LLM 调用)使用缓存。
为不同技术设置 A/B 测试。
🎉 下一步
在掌握高级 RAG 技术方面,你的工作非常出色!你已经学会了如何通过多查询搜索、层级分块、智能过滤和重排序来显著提高检索质量。
接下来,你将学习如何评估和优化 RAG 系统,以便进行生产部署。