RAG From Scratch 系列教程-2:查询转换(Query Translation) - 教程

在检索增强生成(RAG)系统中,查询转换(Query Translation)是提升检索效果的核心技术。本文将详细解析五种前沿的查询优化方法,包含原理说明、工作流程和可运行的代码示例。

一、多查询生成(Multi-query)

原理

通过语言模型生成原始问题的多个变体,扩大检索覆盖范围。研究表明,这种方式可以将检索召回率提升15-30%。

工作流程

  1. 接收原始用户问题

  2. 生成3-5个语义相同但表述不同的查询

  3. 并行执行所有查询的检索

  4. 合并并去重检索结果

代码实现

python

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
multi_query_prompt = ChatPromptTemplate.from_template(
"""请为以下问题生成3个不同表述的搜索查询,用换行符分隔:
问题:{question}
查询变体:"""
)
multi_query_chain = (
{"question": itemgetter("question")}
| multi_query_prompt
| ChatOpenAI(temperature=0.7)
| StrOutputParser()
| (lambda x: x.split("\n"))
)
# 使用示例
queries = multi_query_chain.invoke({"question": "如何预防感冒?"})
print(queries)
# 输出示例:
# ["感冒的预防措施有哪些?", "怎样避免得感冒?", "预防感冒的有效方法"]

二、RAG-Fusion

原理

结合关键词检索(如BM25)和向量检索的优势,通过加权分数融合两种检索结果。

工作流程

  1. 分别执行关键词检索和向量检索

  2. 对两组结果进行归一化评分

  3. 按权重合并排序(通常权重为0.3:0.7)

  4. 去除重复文档

代码实现

python

from rank_bm25 import BM25Okapi
from langchain_community.vectorstores import FAISS
def hybrid_retrieval(query, text_list, vector_store, k=5):
# 关键词检索
tokenized_corpus = [doc.split() for doc in text_list]
bm25 = BM25Okapi(tokenized_corpus)
bm25_scores = bm25.get_scores(query.split())
# 向量检索
vector_results = vector_store.similarity_search_with_score(query, k=k)
vector_scores = [res[1] for res in vector_results]
# 分数归一化(0-1范围)
norm_bm25 = (bm25_scores - min(bm25_scores)) / (max(bm25_scores) - min(bm25_scores))
norm_vector = 1 - (vector_scores / max(vector_scores))
# 加权融合(BM25权重0.3)
combined_scores = 0.3*norm_bm25 + 0.7*norm_vector
return sorted(zip(text_list, combined_scores), key=lambda x: x[1], reverse=True)

三、问题分解(Decomposition)

原理

将复杂问题拆解为多个子问题,分别检索后综合答案。特别适合多要素比较类和分步解决类问题。

工作流程

  1. 识别问题中的子任务

  2. 为每个子任务生成独立查询

  3. 并行检索所有子问题

  4. 综合子答案生成最终回复

代码实现

python

decompose_prompt = ChatPromptTemplate.from_template(
"""将以下复杂问题分解为3-5个子问题,用数字编号列出:
问题:{question}
子问题:"""
)
def retrieve_sub_answers(sub_questions, retriever):
return [retriever.invoke(q) for q in sub_questions]
decomposition_chain = (
{"question": itemgetter("question")}
| decompose_prompt
| ChatOpenAI()
| StrOutputParser()
| (lambda x: [q.strip() for q in x.split("\n") if q.strip()])
| retrieve_sub_answers
)
# 使用示例
sub_answers = decomposition_chain.invoke({
"question": "比较PyTorch和TensorFlow在图像分类和序列建模任务上的优缺点"
})

四、Step-back Prompting

原理

通过先回答抽象的上层问题建立理论框架,再解决具体问题。相当于让AI先"思考原理"再"应用知识"。

工作流程

  1. 生成step-back抽象问题

  2. 检索抽象问题的相关原理

  3. 检索原始问题的具体信息

  4. 结合两部分信息生成最终答案

代码实现

python

step_back_prompt = """请根据具体问题生成一个更抽象的原理性问题:
具体问题:{question}
抽象问题:"""
def step_back_qa(question, retriever, llm):
# 生成抽象问题
abstract_q = llm.invoke(step_back_prompt.format(question=question))
# 两阶段检索
abstract_docs = retriever.invoke(abstract_q)
concrete_docs = retriever.invoke(question)
# 综合回答
response = llm.invoke(
f"基于以下理论原理:{abstract_docs}\n"
f"和具体信息:{concrete_docs}\n"
f"请回答:{question}"
)
return response

五、假设文档嵌入(HyDE)

原理

通过假设答案构建与目标文档相似的向量表示,突破查询表述的字面限制。

工作流程

  1. 生成假设性答案(无需准确)

  2. 计算假设答案的嵌入向量

  3. 用该向量检索真实文档

  4. 基于真实文档生成最终答案

代码实现

python

hyde_prompt = """请为以下问题生成一个假设性答案(不需要准确):
问题:{question}
假设答案:"""
def hyde_retrieval(query, embeddings, vector_db, llm):
# 生成假设答案
hypothetical_answer = llm.invoke(
hyde_prompt.format(question=query)
)
# 向量化检索
hypo_vector = embeddings.embed_query(hypothetical_answer)
results = vector_db.similarity_search_by_vector(hypo_vector)
# 生成最终答案
return llm.invoke(
f"根据以下文档回答{query}:\n{results}"
)

技术对比与选型建议

技术适用场景计算开销实现难度效果提升
Multi-query开放域简单问题15-20%
RAG-Fusion专业术语密集型问题25-35%
Decomposition复杂多要素问题30-40%
Step-back需要理论支撑的问题20-30%
HyDE零样本/低资源场景15-25%

组合使用建议

  1. 对专业领域问题:RAG-Fusion + Decomposition

  2. 对复杂推理问题:Step-back + Multi-query

  3. 快速实现方案:HyDE + Multi-query

posted @ 2025-08-11 21:55  wzzkaifa  阅读(60)  评论(0)    收藏  举报