第8章:查询增强技术¶
用户的问题往往不够精确。查询增强技术可以"理解"用户意图,将复杂问题的检索质量提升15-20%。
📚 学习目标¶
学完本章后,你将能够:
- 理解查询增强的原理和价值
- 掌握HyDE技术的实现
- 实现查询重写
- 应用多查询策略
- 提升复杂问题的准确率
预计学习时间:3小时 难度等级:⭐⭐⭐☆☆
前置知识¶
- 完成模块1的基础RAG实现
- 理解向量检索原理
- 熟悉OpenAI API使用
环境要求: - OpenAI API(或类似) - 向量数据库
8.1 为什么需要查询增强?¶
问题:用户查询不精确¶
场景1:模糊查询
场景2:信息缺失
场景3:复杂多跳
解决方案:查询增强¶
8.2 HyDE(假设文档嵌入)¶
原理¶
HyDE (Hypothetical Document Embeddings):先让LLM生成假设答案,然后用假设答案进行检索。
用户问题:"Python的性能优势是什么?"
步骤1:生成假设答案
LLM生成:"Python具有简洁的语法、动态类型系统、丰富的库..."
步骤2:嵌入假设答案
假设向量 = embed("Python具有简洁的语法...")
步骤3:检索
用假设向量检索,找到类似的文档
步骤4:生成最终答案
基于检索到的文档生成准确答案
为什么HyDE有效?¶
核心思想:问题和答案在语义空间中更接近
实现代码¶
# 文件名:08_01_hyde_implementation.py
"""
HyDE实现
"""
from openai import OpenAI
import numpy as np
from typing import List
class HyDEQueryEngine:
"""HyDE查询引擎"""
def __init__(self, llm_model="gpt-3.5-turbo"):
self.client = OpenAI()
self.llm_model = llm_model
def generate_hypothetical_answer(self, query: str) -> str:
"""
生成假设答案
Args:
query: 原始查询
Returns:
假设答案
"""
prompt = f"""
请基于以下问题,提供一个详细的假设答案。
问题:{query}
要求:
1. 提供详细、具体的答案
2. 假设答案是事实性的
3. 包含相关技术细节
假设答案:
"""
response = self.client.chat.completions.create(
model=self.llm_model,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=200
)
hypothetical_doc = response.choices[0].message.content
return hypothetical_doc
def hyde_retrieve(self, query: str, vector_store, top_k: int = 3):
"""
使用HyDE检索
Args:
query: 用户查询
vector_store: 向量数据库
top_k: 返回文档数
Returns:
检索结果
"""
print("="*60)
print("HyDE检索演示")
print("="*60 + "\n")
# 步骤1:生成假设答案
print("步骤1: 生成假设答案")
hypothetical_answer = self.generate_hypothetical_answer(query)
print(f"原始问题: {query}")
print(f"假设答案: {hypothetical_answer[:150]}...\n")
# 步骤2:嵌入假设答案
print("步骤2: 嵌入假设答案")
hypothetical_embedding = vector_store.get_embedding(hypothetical_answer)
print(f"嵌入维度: {len(hypothetical_embedding)}\n")
# 步骤3:检索
print("步骤3: 基于假设答案检索")
results = vector_store.query(hypothetical_embedding, top_k=top_k)
print("检索结果:")
for i, (doc, score) in enumerate(zip(results['documents'], results['scores']), 1):
print(f" {i}. 相似度 {score:.3f}: {doc[:80]}...")
return results
# 使用示例
if __name__ == "__main__":
# 假设有一个简单的向量存储
class SimpleVectorStore:
def __init__(self):
self.docs = [
"Python是一种高级编程语言,具有简洁的语法和强大的功能。",
"Python的性能优势包括:简洁的语法减少开发时间,丰富的库支持快速开发。",
"JavaScript主要用于Web前端开发,可以创建动态的网页内容。"
]
def get_embedding(self, text):
# 简化:返回随机向量
return np.random.rand(768)
def query(self, embedding, top_k=3):
# 简化:返回所有文档
return {
'documents': self.docs,
'scores': [0.9, 0.7, 0.5]
}
store = SimpleVectorStore()
hyde = HyDEQueryEngine()
query = "Python有什么优势?"
results = hyde.hyde_retrieve(query, store)
HyDE vs 直接检索对比¶
# 文件名:08_02_hyde_comparison.py
"""
HyDE vs 直接检索对比
"""
def compare_retrieval_methods(query, vector_store):
"""
对比HyDE和直接检索
Args:
query: 查询
vector_store: 向量数据库
Returns:
对比结果
"""
print("="*70)
print("HyDE vs 直接检索对比")
print("="*70 + "\n")
# 方法1:直接检索
print("方法1: 直接检索")
query_emb = vector_store.get_embedding(query)
results_direct = vector_store.query(query_emb, top_k=3)
print("检索结果:")
for i, doc in enumerate(results_direct['documents'][:3], 1):
print(f" {i}. {doc[:80]}...")
print()
# 方法2:HyDE
print("方法2: HyDE检索")
hyde = HyDEQueryEngine()
results_hyde = hyde.hyde_retrieve(query, vector_store)
# 对比
print("\n" + "="*70)
print("对比总结")
print("="*70)
print("""
直接检索:
优点: 简单快速,只需一次嵌入和检索
缺点: 对模糊查询效果差
HyDE检索:
优点: 对模糊查询、复杂问题效果好
缺点: 需要额外LLM调用,增加延迟和成本
""")
return results_direct, results_hyde
8.3 查询重写¶
原理¶
将用户的模糊查询重写为更清晰、更具体的查询。
实现代码¶
# 文件名:08_03_query_rewriting.py
"""
查询重写实现
"""
class QueryRewriter:
"""查询重写器"""
def __init__(self, llm_model="gpt-3.5-turbo"):
self.client = OpenAI()
self.llm_model = llm_model
def rewrite_query(self, query: str, context: str = "") -> str:
"""
重写查询
Args:
query: 原始查询
context: 上下文信息
Returns:
重写后的查询
"""
prompt = f"""
你是一个查询优化专家。请将用户的模糊查询重写为更清晰、更具体的查询。
原始查询:{query}
{f"上下文:{context}" if context else ""}
要求:
1. 保持原意
2. 使其更具体
3. 添加相关细节
4. 保持简洁
重写后的查询:
"""
response = self.client.chat.completions.create(
model=self.llm_model,
messages=[{"role": "user", "content": prompt}],
temperature=0.3, # 降低随机性
max_tokens=100
)
rewritten_query = response.choices[0].message.content.strip()
return rewritten_query
# 使用示例
if __name__ == "__main__":
rewriter = QueryRewriter()
# 测试查询
test_queries = [
"怎么用?",
"性能好不好?",
"支持中文吗?"
]
print("="*60)
print("查询重写演示")
print("="*60 + "\n")
for query in test_queries:
print(f"原始查询: {query}")
rewritten = rewriter.rewrite_query(query)
print(f"重写查询: {rewritten}")
print("-" * 60)
多轮重写¶
# 文件名:08_04_multi_turn_rewriting.py
"""
多轮查询重写
"""
class MultiTurnQueryRewriter:
"""多轮查询重写器"""
def __init__(self):
self.client = OpenAI()
def expand_query(self, query: str, max_turns: int = 3) -> List[str]:
"""
扩展查询(多轮对话)
Args:
query: 原始查询
max_turns: 最大轮数
Returns:
查询变体列表
"""
print("="*60)
print("多轮查询扩展")
print("="*60 + "\n")
queries = [query]
for turn in range(max_turns):
print(f"第{turn+1}轮扩展")
# 确定是否需要继续扩展
prompt = f"""
原始查询:{query}
当前扩展:
{chr(10).join(f'{i+1}. {q}' for i, q in enumerate(queries))}
是否还需要扩展查询?
- 如果当前扩展已经足够清晰,回答"停止"
- 如果需要更多信息,提供一个新的查询变体
- 回答"停止"或提供新的变体
判断:
"""
response = self.client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
max_tokens=50
)
decision = response.choices[0].message.content.strip()
if decision.lower() in ["停止", "stop", "足够"]:
print(" → 停止扩展")
break
else:
queries.append(decision)
print(f" → 新增: {decision}")
print(f"\n最终生成 {len(queries)} 个查询变体")
return queries
8.4 多查询策略¶
原理¶
将一个查询扩展为多个相关查询,检索更多样化的结果。
原始查询:"Python的性能如何?"
扩展为多个查询:
1. "Python的执行速度如何?"
2. "Python有哪些性能优化技巧?"
3. "Python相比其他语言性能如何?"
分别检索 → 融合结果
实现代码¶
# 文件名:08_05_multi_query.py
"""
多查询策略实现
"""
from typing import List
import numpy as np
class MultiQueryEngine:
"""多查询引擎"""
def __init__(self, vector_store):
self.vector_store = vector_store
def expand_query(self, query: str) -> List[str]:
"""
扩展查询
Args:
query: 原始查询
Returns:
查询列表
"""
# 这里可以使用LLM生成
# 或使用规则扩展
# 简化版规则扩展
expansions = []
# 原始查询
expansions.append(query)
# 添加同义词
if "性能" in query:
expansions.append(query.replace("性能", "速度"))
expansions.append(query.replace("性能", "效率"))
if "如何" in query:
expansions.append(query.replace("如何", "怎么"))
expansions.append(query.replace("如何", "方法"))
# 去重
return list(set(expansions))
def multi_query_retrieve(self, query: str, top_k: int = 3):
"""
多查询检索
Args:
query: 原始查询
top_k: 每个查询返回的文档数
Returns:
融合后的检索结果
"""
print("="*60)
print("多查询检索演示")
print("="*60 + "\n")
# 步骤1:扩展查询
queries = self.expand_query(query)
print(f"扩展为 {len(queries)} 个查询:")
for i, q in enumerate(queries, 1):
print(f" {i}. {q}")
print()
# 步骤2:分别检索
all_results = {}
for i, q in enumerate(queries, 1):
print(f"检索查询 {i}: {q}")
emb = self.vector_store.get_embedding(q)
results = self.vector_store.query(emb, top_k=top_k)
all_results[q] = results
print(f" 找到 {len(results['documents'])} 个文档\n")
# 步骤3:融合结果
print("融合结果:")
fused_results = self.reciprocal_rank_fusion(all_results)
for i, (doc, score) in enumerate(zip(
fused_results['documents'][:5],
fused_results['scores'][:5]
), 1):
print(f" {i}. 相似度 {score:.3f}: {doc[:80]}...")
return fused_results
def reciprocal_rank_fusion(self, all_results: dict):
"""
RRF融合算法
Args:
all_results: 所有查询的检索结果
Returns:
融合后的结果
"""
# 收集所有文档
all_docs = set()
for results in all_results.values():
all_docs.update(results['documents'])
# 计算RRF分数
scores = {}
k = 60 # RRF常数
for doc in all_docs:
rrf_score = 0
for results in all_results.values():
# 找到该文档在这次检索中的排名
try:
rank = results['documents'].index(doc) + 1
except ValueError:
rank = len(results['documents']) + 1
rrf_score += 1 / (k + rank)
scores[doc] = rrf_score
# 排序
sorted_docs = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return {
'documents': [doc for doc, _ in sorted_docs],
'scores': [score for _, score in sorted_docs]
}
# 使用示例
if __name__ == "__main__":
class SimpleVectorStore:
def __init__(self):
self.docs = [
"Python性能优化包括使用内置函数、避免全局变量等技巧。",
"Python的执行速度相对较慢,但开发效率高。",
"JavaScript是Web开发的主要语言。"
]
def get_embedding(self, text):
return np.random.rand(768)
def query(self, emb, top_k):
return {
'documents': self.docs,
'scores': [0.9, 0.7, 0.5]
}
store = SimpleVectorStore()
engine = MultiQueryEngine(store)
query = "Python的性能如何?"
results = engine.multi_query_retrieve(query)
8.5 查询分解¶
原理¶
将复杂问题分解为多个子问题,分别检索后再综合。
复杂问题:"比较Python和JavaScript在Web开发中的差异"
分解:
子问题1:Python在Web开发中的应用
子问题2:JavaScript在Web开发中的应用
子问题3:两者的优缺点对比
分别检索 → 综合答案
实现代码¶
# 文件名:08_06_query_decomposition.py
"""
查询分解实现
"""
class QueryDecomposer:
"""查询分解器"""
def __init__(self, llm_model="gpt-3.5-turbo"):
self.client = OpenAI()
def decompose_query(self, query: str) -> List[str]:
"""
分解查询
Args:
query: 复杂查询
Returns:
子查询列表
"""
prompt = f"""
将以下复杂问题分解为2-4个子问题。
原始问题:{query}
要求:
1. 每个子问题应该独立可回答
2. 子问题合起来应该能完整回答原问题
3. 子问题之间应该有逻辑关系
子问题(用JSON数组格式):
"""
response = self.client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
response_format={"type": "json_object"}
)
import json
try:
result = json.loads(response.choices[0].message.content)
sub_queries = result.get("sub_queries", [])
except:
# 简化处理:按行分割
sub_queries = response.choices[0].message.content.split("\n")
return sub_queries
def multi_step_query(self, query: str):
"""
多步查询处理
Args:
query: 复杂查询
Returns:
综合答案
"""
print("="*60)
print("多步查询演示")
print("="*60 + "\n")
# 步骤1:分解
print("步骤1: 分解查询")
sub_queries = self.decompose_query(query)
print(f"生成了 {len(sub_queries)} 个子查询:")
for i, sub_q in enumerate(sub_queries, 1):
print(f" {i}. {sub_q}")
print()
# 步骤2:分别检索和回答
print("步骤2: 分别检索和回答")
answers = []
for i, sub_q in enumerate(sub_queries, 1):
print(f"\n子问题 {i}: {sub_q}")
# 检索
# (这里简化,实际应该调用检索器)
retrieved_docs = [f"与{sub_q}相关的文档"]
# 生成答案
answer = f"这是关于{sub_q}的答案"
answers.append(answer)
print(f" 答案: {answer[:100]}...")
# 步骤3:综合
print("\n步骤3: 综合答案")
final_answer = self.synthesize_answers(query, sub_queries, answers)
return final_answer
def synthesize_answers(self, original_query, sub_queries, answers):
"""
综合多个答案
Args:
original_query: 原始问题
sub_queries: 子问题列表
answers: 答案列表
Returns:
综合答案
"""
prompt = f"""
原始问题:{original_query}
子问题和答案:
{chr(10).join(f'Q{i+1}: {q}{chr(10)}A: {a}' for i, (q, a) in enumerate(zip(sub_queries, answers)))}
请基于以上信息,给出一个综合的、结构化的答案。
"""
response = self.client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=500
)
return response.choices[0].message.content
# 使用示例
if __name__ == "__main__":
decomposer = QueryDecomposer()
complex_query = "比较Python和JavaScript在Web开发中的差异,并说明各自的适用场景。"
final_answer = decomposer.multi_step_query(complex_query)
print(f"\n最终答案:\n{final_answer}")
总结¶
本章要点回顾¶
- 查询增强价值
- 处理模糊查询
- 补充缺失信息
-
分解复杂问题
-
核心技术
- HyDE:生成假设答案检索
- 查询重写:表达更清晰
- 多查询:融合多个检索
-
查询分解:分步处理
-
效果提升
- 简单问题:+5-10%
- 复杂问题:+15-20%
学习检查清单¶
- 理解查询增强的必要性
- 掌握HyDE实现
- 能够重写查询
- 实现多查询策略
- 掌握查询分解
下一步学习¶
- 下一章:第9章:混合检索与重排序
- 目标:结合多种检索策略
实践练习¶
- 基础练习
- 实现HyDE检索
- 测试查询重写效果
-
实现多查询融合
-
进阶练习
- 组合HyDE和多查询
- 优化查询分解策略
-
测量性能提升
-
挑战项目
- 实现自适应查询增强
- 处理多轮对话查询
- 达到复杂问题准确率>75%
返回目录 | 上一章:高级分块策略 | 下一章:混合检索与重排序
本章结束
查询增强是处理复杂和模糊查询的关键技术。通过HyDE等技术,即使是不完美的问题也能找到相关的文档,这是让RAG系统更"聪明"的重要一步!