在检索增强生成(RAG)系统的实际应用中,用户原始查询往往存在表述模糊、信息不完整或过于复杂等问题,这直接影响检索质量和最终生成效果。查询优化技术作为RAG pipeline中的关键环节,旨在通过各种策略对用户查询进行预处理、改写和增强,从而提升检索的准确性和召回率。本章将系统介绍七种核心的查询优化技术,包括查询重写、查询分解、多查询生成、假设性文档嵌入、查询路由、纠错增强检索以及自反思检索增强生成。这些技术从不同的角度解决查询质量问题,在实际应用中常常组合使用,形成强大的查询优化体系。
查询优化的核心目标是弥合用户原始查询与文档集合中相关信息之间的语义鸿沟。研究表明,经过优化的查询在检索质量上可提升20%-50%,最终答案的准确性和相关性也有显著改善。随着大语言模型(LLM)能力的不断增强,查询优化技术也在持续演进,从简单的规则改写发展到基于LLM的智能改写、从静态查询发展到动态自适应检索。深入理解和掌握这些技术,对于构建企业级RAG系统至关重要。
8.1 查询重写:让问题更清晰
查询重写(Query Rewriting)是查询优化中最基础也是最广泛应用的技术。其核心思想是通过各种方式对用户原始查询进行改写,使其更适合检索系统理解和处理。根据改写策略的不同,查询重写可分为直接改写、抽象改写和伪文档扩展三大类。直接改写保持查询的核心语义不变,通过语法调整、同义词替换等方式优化查询表述;抽象改写则将具体查询提升为更通用的抽象形式;伪文档扩展通过生成假设性文档来丰富查询信息。
8.1.1 查询重写的动机与分类
用户原始查询往往存在多种问题:表述口语化、包含歧义词汇、缺少关键上下文信息、专业术语使用不当等。这些问题导致检索系统难以准确理解用户意图,从而返回大量无关或低相关性的文档。查询重写技术正是为了解决这些问题而诞生的。根据Ma等人在RRR框架中的分类,查询重写技术主要包含以下几种类型:
表8-1 查询改写方法对比
| 改写类型 | 核心思想 | 适用场景 | 代表方法 | 性能提升 |
|---|---|---|---|---|
| 直接改写 | 语法优化、同义词替换 | 口语化查询 | RRR框架 | 10-15% |
| 抽象改写 | 提取通用原理 | 具体问题求解 | Step-Back | 7-27% |
| 伪文档扩展 | 生成相关文档片段 | 信息稀疏查询 | Query2doc | 3-15% |
| 多查询生成 | 生成多个相关查询 | 多角度检索 | MultiQuery | 15-25% |
| HyDE | 生成假设答案嵌入 | 语义鸿沟大 | HyDE | 20-30% |
直接改写是最直观的查询优化方式,主要通过规则或模型对查询进行语法层面的优化。例如,将口语化的我想知道怎么学习Python改写为Python学习方法,去除口语化表达,使查询更加规范化。这种方式实现简单,但对于复杂的语义鸿沟问题效果有限。
抽象改写则更进一步,通过提取查询背后的通用原理或高层次概念来扩展检索范围。例如,当用户询问2023年某城市的房价走势时,抽象改写可能将其提升为房地产市场影响因素分析,从而检索到更具普适性的经济学原理。这种方式特别适合需要深度推理的复杂问题。
8.1.2 Rewrite-Retrieve-Read (RRR) 框架
RRR(Rewrite-Retrieve-Read)框架由Ma等人于2023年提出,是查询重写领域的里程碑式工作。该框架将传统的Retrieve-then-Read范式扩展为Rewrite-Retrieve-Read三阶段流程,在检索前增加了一个专门的查询重写阶段。RRR框架的核心创新在于将查询重写视为一个可学习的任务,通过训练专门的 rewriter 模型来优化查询质量。
RRR框架的工作流程如下:首先,rewriter模型接收用户原始查询,生成多个候选改写版本;然后,通过一个小型的评估模型对这些候选进行打分,选择最优的改写查询;接着,使用改写后的查询执行检索;最后,将检索到的文档与原始查询一起输入阅读器生成答案。这种端到端的训练方式使得rewriter能够学习到对下游任务最有利的改写策略。
# RRR框架伪代码示意
def rrr_pipeline(original_query, rewriter, retriever, reader):
# Step 1: Rewrite - 生成候选改写
candidates = rewriter.generate_candidates(original_query, num_candidates=5)
# Step 2: 评估并选择最佳改写
best_rewrite = None
best_score = -inf
for candidate in candidates:
score = evaluator.score(candidate)
if score > best_score:
best_score = score
best_rewrite = candidate
# Step 3: Retrieve - 使用改写后的查询检索
documents = retriever.retrieve(best_rewrite, top_k=10)
# Step 4: Read - 生成答案
answer = reader.generate(original_query, documents)
return answer
[2] Ma X, Gong Y, He P, et al. Query Rewriting for Retrieval-Augmented Large Language Models. arXiv:2305.14283, 2023.
实验结果表明,RRR框架在多个问答数据集上均取得了显著提升。在Natural Questions数据集上,RRR相比基线模型提升了约12%的准确率;在HotpotQA多跳问答数据集上,提升幅度更是达到了18%。这些结果充分证明了查询重写在RAG系统中的重要作用。
8.1.3 Query2doc伪文档扩展
Query2doc是由Wang等人于2023年提出的伪文档扩展方法,其核心思想是利用大语言模型的生成能力,为短查询生成一段相关的伪文档,然后将伪文档与原始查询拼接后进行检索。这种方法特别适用于信息稀疏的短查询场景,能够有效缓解查询与文档之间的词汇不匹配问题。
Query2doc的工作流程分为三步:首先,使用大语言模型基于原始查询生成一段伪文档;然后,将生成的伪文档与原始查询进行拼接;最后,使用拼接后的文本进行稠密向量检索。伪文档的生成prompt通常设计为根据查询生成相关背景信息。
# Query2doc实现示例
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class Query2Doc:
def __init__(self, model_name="gpt2"):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(model_name)
def expand_query(self, query, max_length=100):
"""为查询生成伪文档"""
prompt = f"Provide relevant background information for: {query}\n\n"
inputs = self.tokenizer(prompt, return_tensors="pt")
outputs = self.model.generate(
**inputs,
max_length=max_length,
num_return_sequences=1,
temperature=0.7,
do_sample=True
)
pseudo_doc = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
# 移除prompt部分
pseudo_doc = pseudo_doc[len(prompt):].strip()
# 拼接查询和伪文档
expanded_query = f"{query} {pseudo_doc}"
return expanded_query
# 使用示例
q2d = Query2Doc()
original = "machine learning applications in healthcare"
expanded = q2d.expand_query(original)
print(f"原始查询: {original}")
print(f"扩展查询: {expanded}")
[3] Wang L, Yang N, Wei F. Query2doc: Query Expansion with Large Language Models. arXiv:2303.07678, 2023.
在MS-MARCO passage ranking数据集上的实验表明,Query2doc方法相比BM25基线提升了3-15%的MRR@10指标。特别是在短查询场景下,提升效果更为显著。这是因为短查询往往缺乏足够的上下文信息,而伪文档的生成能够有效补充这些信息,帮助检索系统更好地理解查询意图。
8.1.4 Step-Back Prompting
Step-Back Prompting是由Zheng等人于2023年提出的一种抽象改写技术。其核心思想是引导大语言模型从具体问题后退一步,提取出背后的通用原理或高层次概念,然后基于这些原理进行推理和回答。这种方法特别适用于需要深度领域知识的复杂问题。
Step-Back Prompting包含两个关键步骤:首先是抽象(Abstraction),即提取问题背后的通用原理;然后是推理(Reasoning),即基于提取的原理回答原始问题。例如,面对为什么我的植物叶子变黄了这个问题,抽象步骤可能提取出植物叶子变黄的可能原因包括水分不足、营养缺乏、光照过强、病虫害等;然后推理步骤基于这些原理分析具体情况。
# Step-Back Prompting示例
STEP_BACK_PROMPT = """You are an expert at reasoning. When given a question,
you first extract high-level concepts and principles, then answer based on them.
Question: {question}
Let's think step by step:
1. What are the high-level concepts and principles relevant to this question?
2. Based on these principles, what is the answer to the question?
Step-Back Analysis:"""
def step_back_reasoning(llm, question):
# 第一步:提取通用原理
prompt = STEP_BACK_PROMPT.format(question=question)
principles = llm.generate(prompt)
# 第二步:基于原理回答
answer_prompt = f"""Based on the following principles:
{principles}
Answer this question: {question}
Answer:"""
answer = llm.generate(answer_prompt)
return answer
[4] Zheng H S, Mishra S, Chen X, et al. Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models. arXiv:2310.06117, 2023.
在MMLU(Massive Multitask Language Understanding)基准测试上,Step-Back Prompting方法在多个学科领域取得了7-27%的性能提升,特别是在物理和化学等需要深度推理的学科上表现尤为突出。这表明抽象改写能够有效激活大语言模型的深层推理能力。
8.1.5 工程实践考量
在实际工程应用中,查询重写技术的选择需要综合考虑多个因素。首先是延迟与质量的权衡:基于LLM的改写方法虽然效果好,但会增加额外的推理延迟;而基于规则的改写虽然速度快,但灵活性不足。其次是成本考量:调用大语言模型API会产生额外费用,需要在效果和成本之间找到平衡点。
实践中常用的优化策略包括:缓存常用查询的改写结果、使用轻量级模型进行初步改写、根据查询复杂度动态选择改写策略等。此外,改写效果的好坏需要通过A/B测试进行验证,建立完善的评估指标体系,包括检索准确率、答案相关性、用户满意度等。
8.2 查询分解:复杂问题的拆解
查询分解(Query Decomposition)是处理复杂问题的关键技术。当面对多跳推理、多条件约束或需要综合多个信息源才能回答的复杂查询时,直接检索往往难以获得满意的结果。查询分解技术通过将复杂问题拆解为一系列简单的子问题,逐步求解并最终合并答案,有效解决了这一难题。
8.2.1 查询分解的定义与动机
复杂查询通常具有以下特征:需要多步推理才能得出答案、涉及多个实体或概念的关系、包含时间或空间上的复杂约束、需要综合不同类型信息等。例如,2022年诺贝尔文学奖得主的作品中,哪一部被改编成了获得奥斯卡最佳外语片的电影?这个问题就需要先确定2022年诺奖得主,然后查找其作品,最后确认哪部作品被改编并获奖。
查询分解的核心价值在于降低单次检索的复杂度。通过将复杂问题分解为多个简单的子问题,每个子问题都可以独立进行检索和回答,大大降低了检索系统的负担。同时,分解后的子问题更容易与知识库中的信息匹配,提高了检索的准确性和召回率。
表8-2 查询分解策略对比
| 分解策略 | 执行方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| Least-to-Most | 顺序执行 | 逻辑依赖强的问题 | 保证推理连贯性 | 延迟较高 |
| 平行分解 | 并行执行 | 独立子问题 | 响应速度快 | 需要合并逻辑 |
| IRCoT | 检索与推理交织 | 多跳问答 | 动态调整检索 | 实现复杂 |
| 递归分解 | 自顶向下 | 层次化问题 | 结构化清晰 | 可能过度分解 |
| 动态分解 | 按需分解 | 不确定性问题 | 灵活适应 | 控制难度大 |
8.2.2 Least-to-Most Prompting
Least-to-Most Prompting是由Zhou等人于2022年提出的一种顺序查询分解方法。其核心思想是将复杂问题分解为一系列从简单到复杂的子问题,每个子问题的解答依赖于前面子问题的答案。这种方法模仿了人类学习新知识的过程,通过循序渐进的方式解决复杂问题。
Least-to-Most Prompting包含两个阶段:分解(Decomposition)和求解(Solution)。在分解阶段,模型将复杂问题拆解为子问题列表;在求解阶段,模型依次回答每个子问题,并将前面的答案作为上下文来回答后续问题。这种链式推理的方式保证了推理的逻辑连贯性。
# Least-to-Most Prompting示例
DECOMPOSITION_PROMPT = """Break down the following complex problem into
a list of simpler sub-problems that need to be solved step by step.
Problem: {problem}
Sub-problems (from least to most complex):
1."""
SOLUTION_PROMPT = """Solve the following sub-problem using the context
from previous answers.
Previous answers:
{context}
Current sub-problem: {sub_problem}
Answer:"""
def least_to_most_solving(llm, problem):
# 阶段1:分解问题
decomp_prompt = DECOMPOSITION_PROMPT.format(problem=problem)
sub_problems = llm.generate(decomp_prompt).strip().split('\n')
# 阶段2:依次求解
context = ""
answers = []
for i, sub_problem in enumerate(sub_problems):
sol_prompt = SOLUTION_PROMPT.format(
context=context,
sub_problem=sub_problem
)
answer = llm.generate(sol_prompt)
answers.append(answer)
context += f"\nQ{i+1}: {sub_problem}\nA{i+1}: {answer}"
return answers[-1] # 返回最终答案
[5] Zhou D, Schärli N, Hou L, et al. Least-to-Most Prompting Enables Complex Reasoning in Large Language Models. arXiv:2205.10625, 2022.
8.2.3 IRCoT检索与思维链交织
IRCoT(Interleaving Retrieval with Chain-of-Thought)是由Trivedi等人于2023年提出的一种动态查询分解方法,发表在ACL 2023上。该方法的核心创新在于将检索与思维链推理交织在一起,根据推理的进展动态决定何时需要检索新的信息。这种方法特别适合多跳问答场景,能够有效处理推理过程中信息需求不断变化的情况。
IRCoT的工作流程如下:首先,模型生成思维链的下一个推理步骤;然后,判断是否需要检索新信息;如果需要,则基于当前推理状态生成查询并执行检索;将检索结果融入思维链,继续下一步推理。这个过程迭代进行,直到得出最终答案。这种交织式的检索策略避免了传统方法中一次性检索可能遗漏关键信息的问题。
# IRCoT伪代码实现
class IRCoT:
def __init__(self, llm, retriever):
self.llm = llm
self.retriever = retriever
def solve(self, question, max_iterations=10):
reasoning_chain = [f"Q: {question}"]
retrieved_docs = []
for i in range(max_iterations):
# 生成下一步推理
context = "\n".join(reasoning_chain)
next_step = self.llm.generate(f"{context}\nNext thought:")
# 判断是否需要检索
if "[SEARCH]" in next_step:
# 提取搜索查询
query = next_step.replace("[SEARCH]", "").strip()
docs = self.retriever.retrieve(query, top_k=3)
retrieved_docs.extend(docs)
# 将检索结果加入推理链
doc_text = "\n".join([d.content for d in docs])
reasoning_chain.append(f"Retrieved: {doc_text}")
reasoning_chain.append(f"Thought {i+1}: {next_step}")
# 检查是否得到答案
if "Answer:" in next_step:
return next_step.split("Answer:")[1].strip()
# 生成最终答案
final_prompt = "\n".join(reasoning_chain) + "\nFinal Answer:"
return self.llm.generate(final_prompt)
[6] Trivedi H, Balasubramanian N, Khot T, et al. Interleaving Retrieval with Chain-of-Thought Reasoning for Knowledge-Intensive Multi-Step Questions. ACL 2023. arXiv:2212.10509.
在HotpotQA和2WikiMultiHopQA等多跳问答数据集上的实验表明,IRCoT相比传统的先检索后推理方法在检索准确率上提升了21个百分点,最终答案的F1分数也有显著提升。这证明了动态交织检索与推理的有效性。
8.2.4 递归式vs平行式分解
查询分解的执行策略主要分为递归式和平行式两种。递归式分解采用自顶向下的方式,将问题逐层分解为更细的子问题,直到子问题可以直接回答为止。这种策略适合具有明显层次结构的问题,如数学证明、复杂决策分析等。平行式分解则将问题分解为相互独立的子问题,并行执行检索和回答,最后合并结果。这种策略响应速度快,适合子问题之间没有强依赖关系的场景。
在实际应用中,两种策略常常结合使用。对于复杂的多跳问题,可以先进行递归式分解确定整体解决路径,然后对相互独立的子问题采用平行式执行以提高效率。合并策略的选择也很重要,常见的方法包括:基于置信度的加权合并、基于逻辑关系的结构化合并、以及通过LLM进行智能综合等。
8.2.5 子问题合并策略
子问题答案的合并是查询分解的最后一步,也是决定最终答案质量的关键环节。常见的合并策略包括:简单拼接、逻辑整合、冲突消解和综合生成。简单拼接直接将各子问题的答案按顺序组合;逻辑整合则根据子问题之间的逻辑关系(如因果、并列、递进)组织答案结构;冲突消解处理不同子问题答案之间的矛盾;综合生成则使用LLM基于所有子问题答案生成连贯的最终答案。
8.3 多查询生成:多角度检索
多查询生成(Multi-Query Generation)是一种通过生成多个相关查询来扩大检索覆盖面的技术。其核心思想是:用户原始查询往往只表达了信息需求的一个角度,通过生成多个从不同角度表达的查询,可以更全面地检索到相关信息。这种方法特别适合信息需求复杂、单一查询难以完整表达的场景。
8.3.1 多查询生成的核心思想
多查询生成的理论基础是信息需求的多样性和文档表达的多样性。同一信息需求可以用多种方式表达,同一主题的内容也可以用多种方式描述。例如,对于人工智能在医疗领域的应用这个主题,可以从技术角度(机器学习算法)、应用角度(疾病诊断)、影响角度(医疗成本变化)等多个角度进行检索。
多查询生成的关键在于平衡查询的多样性和相关性。生成的查询既要与原始查询相关,又要具有足够的差异性,以覆盖不同的信息维度。同时,查询数量也需要控制,过多的查询会增加检索成本并引入噪声。
8.3.2 查询多样性的控制
控制查询多样性是多查询生成的核心技术挑战。常用的控制策略包括:基于语义相似度的过滤、基于聚类的选择、基于覆盖度的优化等。语义相似度过滤通过计算生成查询与原始查询的语义相似度,剔除偏离主题的查询;聚类选择将生成的查询聚类,从每个类别中选择代表性查询;覆盖度优化则通过最大化检索结果的覆盖范围来选择最优查询组合。
表8-3 查询扩展与多查询检索对比
| 对比维度 | 查询扩展 | 多查询检索 |
|---|---|---|
| 核心思想 | 扩展单个查询的表达能力 | 生成多个独立查询 |
| 查询数量 | 1个扩展查询 | 多个并行查询 |
| 检索方式 | 单次检索 | 多次检索后融合 |
| 适用场景 | 短查询、词汇不匹配 | 多角度信息需求 |
| 计算成本 | 较低 | 较高 |
| 代表方法 | Query2doc、RM3 | MultiQueryRetriever |
| 结果融合 | 不需要 | 需要RRF等融合算法 |
8.3.3 结果合并与去重(RRF融合)
多查询检索会产生多个结果列表,需要有效的合并策略。RRF(Reciprocal Rank Fusion)是一种广泛使用的结果融合算法,它基于文档在不同结果列表中的排名进行加权融合,不需要相关性分数,对不同的检索系统具有良好的兼容性。
RRF的计算公式为:RRF_score(d) = Σ(1 / (k + r_i)),其中r_i是文档d在第i个结果列表中的排名,k是常数(通常取60)。这个公式给予排名靠前的文档更高的权重,同时考虑了文档在多个列表中的出现情况。相比简单的分数加权,RRF更加鲁棒,不易受个别检索系统分数分布的影响。
# RRF融合算法实现
def reciprocal_rank_fusion(rankings, k=60):
"""
RRF融合多个检索结果列表
Args:
rankings: 列表的列表,每个内部列表是文档ID的有序列表
k: RRF常数,默认60
Returns:
按RRF分数排序的文档列表
"""
from collections import defaultdict
rrf_scores = defaultdict(float)
for ranking in rankings:
for rank, doc_id in enumerate(ranking, start=1):
rrf_scores[doc_id] += 1.0 / (k + rank)
# 按分数降序排序
sorted_docs = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
return [doc_id for doc_id, score in sorted_docs]
使用示例
ranking1 = ["doc_A", "doc_B", "doc_C"] # 查询1的结果
ranking2 = ["doc_B", "doc_D", "doc_A"] # 查询2的结果
ranking3 = ["doc_C", "doc_A", "doc_E"] # 查询3的结果
fused_result = reciprocal_rank_fusion([ranking1, ranking2, ranking3])
print(f"融合结果: {fused_result}")
8.3.4 LangChain MultiQueryRetriever实现
LangChain提供了MultiQueryRetriever实现,可以方便地将多查询生成集成到RAG pipeline中。该组件使用LLM生成多个查询变体,对每个变体执行检索,然后使用RRF算法融合结果。
# LangChain MultiQueryRetriever使用示例
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# 初始化基础组件
llm = ChatOpenAI(temperature=0)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(documents, embeddings)
base_retriever = vectorstore.as_retriever()
# 创建MultiQueryRetriever
multi_query_retriever = MultiQueryRetriever.from_llm(
retriever=base_retriever,
llm=llm,
parser_key="lines" # 解析生成查询的方式
)
# 使用多查询检索
docs = multi_query_retriever.get_relevant_documents(
"What are the approaches to task decomposition?"
)
# 自定义查询生成prompt
from langchain.prompts import PromptTemplate
QUERY_PROMPT = PromptTemplate(
input_variables=["question"],
template="""You are an AI language model assistant. Your task is to
generate five different versions of the given user question to retrieve
relevant documents from a vector database. By generating multiple
perspectives on the user question, your goal is to help the user overcome
some of the limitations of the distance-based similarity search.
Provide these alternative questions separated by newlines.
Original question: {question}"""
)
multi_query_retriever = MultiQueryRetriever.from_llm(
retriever=base_retriever,
llm=llm,
prompt=QUERY_PROMPT
)
[11] LangChain. MultiQueryRetriever Documentation. python.langchain.com/docs/module…
8.3.5 查询扩展vs多查询检索对比
查询扩展和多查询检索都是解决查询表达不足问题的技术,但它们的实现方式和适用场景有所不同。查询扩展通过丰富单个查询的内容来提高检索效果,适合处理短查询和词汇不匹配问题;多查询检索通过生成多个不同角度的查询来扩大检索覆盖面,适合处理复杂的信息需求。
在实际应用中,两种技术可以结合使用。例如,可以先对原始查询进行扩展,然后基于扩展后的查询生成多个角度变体,最后融合所有检索结果。这种组合策略能够同时解决词汇不匹配和多角度覆盖的问题,但也会增加计算成本,需要根据具体场景进行权衡。
8.4 假设性文档嵌入(HyDE)
HyDE(Hypothetical Document Embeddings)是由Gao等人于2022年提出的一种创新性查询优化技术。其核心思想是利用大语言模型的生成能力,为查询生成一个假设性的理想答案文档,然后使用这个假设文档的嵌入进行检索,而不是直接使用查询的嵌入。这种方法巧妙地避开了查询-文档语义鸿沟问题,在零样本场景下表现尤为出色。
8.4.1 HyDE的核心原理
传统的稠密检索方法将查询和文档编码到同一向量空间,通过向量相似度来衡量相关性。然而,查询通常很短且缺乏上下文,而文档则较长且信息丰富,这种不对称性导致了语义鸿沟。HyDE通过生成假设文档来解决这一问题:假设文档在长度、风格和信息密度上都与真实文档更接近,因此其嵌入向量更容易与相关文档对齐。
HyDE的工作流程包含三个步骤:首先,使用指令微调的大语言模型(如InstructGPT)基于查询生成假设文档;然后,使用无监督编码器(如Contriever或GTR)对假设文档进行编码;最后,使用生成的向量在文档库中进行相似度检索。值得注意的是,生成的假设文档本身并不需要准确,即使包含错误信息,其语义向量仍然能够指向正确的文档区域。
# HyDE实现示例
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import HypotheticalDocumentEmbedder
# 初始化基础组件
base_embeddings = OpenAIEmbeddings()
llm = ChatOpenAI(temperature=0)
# 创建HyDE嵌入器
hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
llm=llm,
base_embeddings=base_embeddings,
prompt_key="web_search" # 使用预定义的prompt模板
)
# 使用HyDE进行检索
# hyde_embeddings会先生成假设文档,然后返回其嵌入向量
query = "What is task decomposition in LLM agents?"
hyde_vector = hyde_embeddings.embed_query(query)
# 使用生成的向量进行检索
# 这里hyde_vector可以直接用于向量数据库的相似度搜索
[7] Gao L, Ma X, Lin J, et al. Precise Zero-Shot Dense Retrieval without Relevance Labels. arXiv:2212.10496, 2022.
8.4.2 HyDE vs 传统查询嵌入
HyDE与传统查询嵌入方法的本质区别在于嵌入对象的选择。传统方法直接对查询文本进行编码,而HyDE对生成的假设文档进行编码。这一差异带来了几个显著优势:首先,假设文档的长度与真实文档更接近,缓解了短查询与长文档之间的不对称性;其次,假设文档包含了LLM的参数知识,可以补充查询中缺失的上下文信息;最后,假设文档的风格与真实文档一致,有助于更好的语义对齐。
表8-4 HyDE与传统查询嵌入对比
| 对比维度 | 传统查询嵌入 | HyDE假设文档嵌入 |
|---|---|---|
| 嵌入对象 | 查询文本本身 | 生成的假设文档 |
| 文本长度 | 短(通常<20词) | 长(与文档相当) |
| 信息密度 | 低 | 高 |
| 上下文信息 | 仅查询本身 | 包含LLM补充的上下文 |
| 零样本性能 | 一般 | 优秀 |
| 计算开销 | 低 | 较高(需要生成文档) |
| 对LLM依赖 | 无 | 强 |
8.4.3 HyDE的零样本特性
HyDE最突出的特点是其强大的零样本检索能力。传统的检索模型通常需要大量的标注数据进行微调,而HyDE在没有任何领域标注数据的情况下就能取得优异的性能。这得益于大语言模型的通用知识能力:即使面对陌生领域的查询,LLM也能生成合理的假设文档,其语义向量仍然能够指向相关的文档区域。
实验表明,HyDE在多个零样本检索基准上超越了有监督的检索模型。在BEIR基准测试的18个数据集上,HyDE平均优于BM25基线约10个百分点,在部分数据集上甚至超过了经过微调的稠密检索模型。这一结果表明,HyDE为大语言模型时代的零样本检索提供了一个强有力的解决方案。
8.4.4 HyDE的局限性与改进方向
尽管HyDE表现出色,但它也存在一些局限性。首先,HyDE依赖大语言模型生成假设文档,这带来了额外的计算开销和延迟;其次,对于某些特定类型的查询(如实体查找、事实性问题),生成的假设文档可能与真实答案存在偏差;最后,HyDE的效果受限于基础LLM的能力,对于LLM不熟悉的领域,生成的假设文档质量可能下降。
针对这些局限性,研究者提出了多种改进方向。一种思路是结合多个假设文档的嵌入,通过平均或加权的方式获得更稳定的查询表示;另一种思路是使用领域特定的prompt来引导假设文档的生成;还有一种方法是将HyDE与传统的查询扩展技术结合,在保留HyDE优势的同时降低对LLM的依赖。
8.4.5 HyDE与Query2doc的对比
HyDE和Query2doc都是利用LLM生成能力来增强查询的技术,但它们的应用场景和实现方式有所不同。Query2doc将生成的伪文档与原始查询拼接后进行检索,本质上是一种查询扩展技术;而HyDE直接使用生成文档的嵌入向量进行检索,是一种全新的查询表示方法。
从效果上看,HyDE在零样本场景下表现更优,而Query2doc在有检索模型微调的情况下也能取得不错的效果。从计算效率看,Query2doc只需要生成一次伪文档,而HyDE需要对生成的文档进行编码,开销稍大。在实践中,可以根据具体场景选择合适的方法,或者将两者结合使用以获得更好的效果。
8.5 查询路由:智能选择知识库
查询路由(Query Routing)是RAG系统在面对多个知识库或数据源时,智能选择最合适的检索目标的技术。在企业级应用中,数据通常分散在不同的存储系统中,如向量数据库、关系型数据库、图数据库、搜索引擎等。查询路由技术能够根据查询的特征自动选择最优的检索路径,提高检索效率和准确性。
8.5.1 查询路由的定义与分类
查询路由的核心任务是将用户查询分配给最合适的检索器或数据源。根据决策机制的不同,查询路由可以分为以下几类:语义路由基于查询的语义特征进行匹配选择;LLM函数调用路由利用大语言模型的函数调用能力进行决策;分类器路由训练专门的分类模型进行路由判断;规则路由则基于预定义的规则进行简单匹配。
表8-5 查询路由策略对比
| 路由类型 | 决策机制 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 语义路由 | 向量相似度匹配 | 无需训练、响应快 | 表达能力有限 | 数据源语义差异明显 |
| LLM函数调用 | LLM决策 | 灵活强大 | 延迟高、成本高 | 复杂路由逻辑 |
| 分类器路由 | 训练分类模型 | 准确率高 | 需要标注数据 | 路由规则明确 |
| 规则路由 | 预定义规则 | 简单高效 | 维护困难 | 规则清晰的场景 |
| 混合路由 | 多策略组合 | 兼顾效果与效率 | 实现复杂 | 企业级复杂场景 |
8.5.2 Adaptive-RAG自适应检索
Adaptive-RAG是由Jeong等人于2024年提出的自适应检索框架,发表在NAACL 2024上。该框架的核心创新在于根据查询的复杂度动态选择检索策略,对于简单查询直接由LLM回答,对于复杂查询则触发多步检索。这种自适应机制在保证回答质量的同时,有效降低了不必要的检索开销。
Adaptive-RAG包含一个分类器组件,用于判断查询的复杂度级别。分类器可以基于规则(如查询长度、关键词),也可以基于训练好的模型。根据复杂度判断结果,系统选择不同的处理路径:简单查询直接生成答案,中等复杂度查询执行单次RAG,高复杂度查询则触发迭代式多跳检索。这种分层策略显著提高了系统的整体效率。
# Adaptive-RAG伪代码实现
class AdaptiveRAG:
def __init__(self, llm, retriever, classifier):
self.llm = llm
self.retriever = retriever
self.classifier = classifier
def answer(self, query):
# 步骤1:判断查询复杂度
complexity = self.classifier.classify(query)
if complexity == "simple":
# 简单查询:直接回答
return self.llm.generate(query)
elif complexity == "moderate":
# 中等复杂度:单次RAG
docs = self.retriever.retrieve(query, top_k=5)
context = "\n".join([d.content for d in docs])
prompt = f"Context: {context}\n\nQuestion: {query}\nAnswer:"
return self.llm.generate(prompt)
else: # complex
# 复杂查询:迭代式检索
return self.iterative_rag(query)
def iterative_rag(self, query, max_iterations=3):
context = ""
for i in range(max_iterations):
docs = self.retriever.retrieve(query + " " + context, top_k=3)
new_info = "\n".join([d.content for d in docs])
context += new_info
# 检查是否已足够回答
check_prompt = f"Can you answer '{query}' with: {context}?"
if "yes" in self.llm.generate(check_prompt).lower():
break
prompt = f"Context: {context}\n\nQuestion: {query}\nAnswer:"
return self.llm.generate(prompt)
[8] Jeong S, Baek J, Cho S, et al. Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models through Question Complexity. NAACL 2024. arXiv:2403.14403.
8.5.3 Semantic Router开源库
Semantic Router是一个开源的查询路由库,它利用向量相似度来实现高效的语义路由。该库允许用户为不同的路由目标定义示例查询,然后通过计算用户查询与示例查询的语义相似度来决定路由目标。这种方法无需训练专门的分类模型,部署简单且响应速度快。
# Semantic Router使用示例
from semantic_router import Route
from semantic_router.encoders import OpenAIEncoder
from semantic_router.routers import SemanticRouter
# 定义路由
chitchat_route = Route(
name="chitchat",
utterances=[
"你好",
"最近怎么样",
"今天天气不错",
"谢谢你的帮助",
],
)
product_route = Route(
name="product",
utterances=[
"这个产品多少钱",
"有什么功能",
"怎么使用",
"支持退换货吗",
],
)
technical_route = Route(
name="technical",
utterances=[
"API怎么调用",
"返回错误代码500",
"怎么集成到系统",
"支持哪些编程语言",
],
)
# 初始化编码器和路由器
encoder = OpenAIEncoder()
routes = [chitchat_route, product_route, technical_route]
router = SemanticRouter(encoder=encoder, routes=routes)
# 路由查询
query = "怎么调用你们的API接口?"
result = router(query)
print(f"路由结果: {result.name}") # 输出: technical
[13] Semantic Router. GitHub: github.com/aurelio-lab…
8.5.4 LlamaIndex RouterQueryEngine
LlamaIndex提供了RouterQueryEngine组件,支持基于LLM的智能查询路由。该组件可以为不同的查询引擎定义描述信息,然后使用LLM根据查询内容选择最合适的引擎。这种方式灵活性高,能够处理复杂的路由逻辑,但相比语义路由有更高的延迟和成本。
# LlamaIndex RouterQueryEngine使用示例
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.selectors import LLMSingleSelector
from llama_index.core.tools import QueryEngineTool
# 创建不同的查询引擎
vector_query_engine = index.as_query_engine()
summary_query_engine = index.as_query_engine(response_mode="tree_summarize")
# 定义工具
tools = [
QueryEngineTool.from_defaults(
query_engine=vector_query_engine,
description="用于回答关于文档具体内容的问题",
),
QueryEngineTool.from_defaults(
query_engine=summary_query_engine,
description="用于回答关于文档整体总结的问题",
),
]
# 创建路由器
router_engine = RouterQueryEngine(
selector=LLMSingleSelector.from_defaults(),
query_engine_tools=tools,
)
# 使用路由器
response = router_engine.query("这篇文档主要讲了什么?")
# LLM会根据查询内容自动选择合适的查询引擎
[12] LlamaIndex. RouterQueryEngine Documentation. docs.llamaindex.ai/en/stable/e…
8.6 CRAG:带纠错机制的增强检索
CRAG(Corrective Retrieval Augmented Generation)是由Yan等人于2024年提出的一种带纠错机制的检索增强生成框架。传统的RAG系统假设检索到的文档总是相关且准确的,但在实际应用中,检索结果可能包含大量无关或错误信息,严重影响生成质量。CRAG通过引入检索结果评估和纠错机制,有效解决了这一问题。
8.6.1 检索结果置信度评估
CRAG的核心创新之一是轻量级的检索结果置信度评估机制。该机制使用一个小型的评估器模型(如T5-large)来判断检索到的文档与查询的相关性。评估器为每个查询-文档对输出一个相关性分数,然后根据分数分布决定后续的处理路径。
CRAG采用三分支决策策略:如果所有文档的相关性分数都高于阈值,则进入正确分支,直接使用检索结果;如果所有文档的分数都低于阈值,则进入错误分支,触发外部搜索进行纠错;如果分数分布混合,则进入模糊分支,对检索结果进行分解重组处理。这种细粒度的决策机制使得CRAG能够针对不同质量的检索结果采取不同的处理策略。
表8-6 CRAG三分支决策策略
| 分支 | 触发条件 | 处理策略 | 适用场景 |
|---|---|---|---|
| 正确分支 | 所有文档相关性>阈值 | 直接使用检索结果 | 检索质量高 |
| 错误分支 | 所有文档相关性<阈值 | 触发外部搜索纠错 | 检索完全失败 |
| 模糊分支 | 文档相关性混合 | 分解重组处理 | 部分相关 |
8.6.2 低置信度结果的二次检索与外部搜索触发
当检索结果被判定为低置信度时,CRAG会触发纠错机制。对于错误分支,CRAG会执行大规模网络搜索,从互联网获取补充信息。这种外部搜索能力使得CRAG能够突破本地知识库的限制,获取最新、最全面的信息。
对于模糊分支,CRAG采用Decompose-then-Recompose算法进行处理。该算法首先将检索到的文档分解为细粒度的知识片段,然后使用一个知识筛选器去除不相关的片段,最后将筛选后的片段重组为结构化的上下文输入生成模型。这种细粒度的处理方式能够有效提取检索结果中的有用信息,同时过滤掉噪声。
# CRAG Decompose-then-Recompose算法
class CRAGProcessor:
def __init__(self, evaluator, web_searcher, knowledge_filter):
self.evaluator = evaluator
self.web_searcher = web_searcher
self.knowledge_filter = knowledge_filter
def decompose_recompose(self, query, documents):
"""分解-重组处理流程"""
# 步骤1:分解文档为知识片段
knowledge_snippets = []
for doc in documents:
snippets = self.decompose_document(doc)
knowledge_snippets.extend(snippets)
# 步骤2:知识筛选
filtered_snippets = []
for snippet in knowledge_snippets:
relevance = self.knowledge_filter.score(query, snippet)
if relevance > self.filter_threshold:
filtered_snippets.append((snippet, relevance))
# 步骤3:按相关性排序
filtered_snippets.sort(key=lambda x: x[1], reverse=True)
# 步骤4:重组为结构化上下文
context = self.recompose_context(filtered_snippets)
return context
def decompose_document(self, document):
"""将文档分解为知识片段"""
# 可以基于句子、段落或语义块进行分解
sentences = document.split(". ")
return [s.strip() for s in sentences if len(s) > 10]
def recompose_context(self, snippets_with_scores):
"""重组知识片段为上下文"""
# 保留top-k片段,并添加相关性标记
top_snippets = snippets_with_scores[:10]
context_parts = []
for snippet, score in top_snippets:
context_parts.append(f"[Relevance: {score:.2f}] {snippet}")
return "\n".join(context_parts)
8.6.3 纠错机制的实现与最佳实践
CRAG的纠错机制设计遵循即插即用(Plug-and-Play)原则,可以方便地集成到现有的RAG系统中。纠错流程包含四个步骤:评估(Evaluation)、决策(Decision)、纠错(Correction)和生成(Generation),形成一个完整的闭环。这种设计使得CRAG不仅能够纠正检索错误,还能通过反馈机制持续优化检索质量。
在实际部署CRAG时,有几个最佳实践需要注意。首先是评估器的选择和阈值设定,需要根据具体应用场景进行调整;其次是外部搜索的触发策略,要考虑成本和延迟的平衡;最后是知识筛选的粒度控制,过细可能导致信息碎片化,过粗则无法有效过滤噪声。建议通过A/B测试来优化这些超参数。
# CRAG完整实现示例
class CRAG:
def __init__(self, retriever, llm, evaluator, web_searcher):
self.retriever = retriever
self.llm = llm
self.evaluator = evaluator
self.web_searcher = web_searcher
self.threshold_high = 0.8
self.threshold_low = 0.3
def retrieve_and_generate(self, query):
# 步骤1:初始检索
docs = self.retriever.retrieve(query, top_k=5)
# 步骤2:评估检索结果
scores = [self.evaluator.score(query, doc) for doc in docs]
max_score = max(scores)
min_score = min(scores)
# 步骤3:三分支决策
if min_score >= self.threshold_high:
# 正确分支
context = "\n".join([d.content for d in docs])
elif max_score < self.threshold_low:
# 错误分支:触发外部搜索
web_results = self.web_searcher.search(query)
context = "\n".join(web_results)
else:
# 模糊分支:分解重组
context = self.decompose_recompose(query, docs)
# 步骤4:生成答案
prompt = f"Context: {context}\n\nQuestion: {query}\nAnswer:"
return self.llm.generate(prompt)
[9] Yan S, Gu J, Zhu Y, et al. Corrective Retrieval Augmented Generation. arXiv:2401.15884, 2024.
[14] LangChain. CRAG Tutorial. langchain-ai.github.io/langgraph/t…
8.7 Self-RAG:自反思检索增强生成
Self-RAG(Self-Reflective Retrieval-Augmented Generation)是由Asai等人于2023年提出的一种创新性RAG框架。与传统的RAG系统不同,Self-RAG在生成过程中引入了自我反思机制,通过特殊的反思令牌(reflection tokens)来控制检索和生成过程,实现按需检索和自适应检索频率。这种方法不仅提高了生成质量,还能为生成的内容提供可追溯的引用来源。
8.7.1 生成过程中的自我反思与评分
Self-RAG的核心创新是引入了四种反思令牌,用于在生成过程中进行自我评估和控制:[Retrieve]令牌决定是否需要进行检索;[IsRel]令牌评估检索文档与查询的相关性;[IsSup]令牌评估生成内容是否得到检索文档的支持;[IsUse]令牌评估生成内容的整体有用性。这些令牌使得模型能够在生成过程中动态地进行自我反思和质量控制。
反思令牌的引入是通过对语言模型进行特殊训练实现的。训练数据包含带有反思令牌的文本,模型学习在适当的位置生成这些令牌。在推理阶段,模型通过生成反思令牌来控制检索和生成流程,形成一个自我增强的闭环系统。这种设计使得Self-RAG能够在不依赖外部控制逻辑的情况下,自主决定何时检索、如何使用检索结果。
表8-7 Self-RAG反思令牌说明
| 反思令牌 | 功能 | 取值 | 作用时机 |
|---|---|---|---|
| [Retrieve] | 控制检索触发 | yes/no/continue | 每个生成段落前 |
| [IsRel] | 评估文档相关性 | relevant/irrelevant | 检索后 |
| [IsSup] | 评估支持程度 | fully/partially/no | 生成每个句子后 |
| [IsUse] | 评估整体有用性 | useful/partially/no | 完整回答后 |
8.7.2 检索决策的动态控制
Self-RAG的检索决策是动态和自适应的。模型根据当前生成状态和查询特征,自主决定是否需要检索。对于模型已经掌握的知识,[Retrieve]令牌输出no,直接生成答案;对于需要外部信息的问题,输出yes触发检索;对于需要持续检索的多步推理问题,输出continue保持检索状态。
这种按需检索机制带来了两个显著优势:一是减少了不必要的检索调用,降低了系统开销;二是避免了过度检索引入的噪声,提高了生成质量。实验表明,Self-RAG在保持高准确率的同时,检索次数比传统RAG减少了约30%,实现了效率与效果的最佳平衡。
# Self-RAG反思令牌使用示例
SELF_RAG_PROMPT = """Answer the question based on the given context.
Use reflection tokens to guide the retrieval and generation process.
Reflection Tokens:
- [Retrieve]: yes/no/continue - whether to retrieve documents
- [IsRel]: relevant/irrelevant - whether retrieved docs are relevant
- [IsSup]: fully/partially/no - whether the sentence is supported by docs
- [IsUse]: useful/partially/no - overall usefulness of the answer
Question: {question}
Let's think step by step and use reflection tokens:
[Retrieve] yes
Retrieved documents:
{documents}
[IsRel] relevant
Answer generation with citations:
"""
def self_rag_generate(model, question, retriever):
context = ""
output = []
# 初始检索决策
retrieve_decision = model.generate(f"Question: {question}\n[Retrieve]")
if "yes" in retrieve_decision.lower():
# 执行检索
docs = retriever.retrieve(question)
context = "\n".join([d.content for d in docs])
# 评估相关性
rel_check = model.generate(f"Docs: {context}\nQuestion: {question}\n[IsRel]")
# 生成答案(带引用)
generation_prompt = SELF_RAG_PROMPT.format(
question=question,
documents=context
)
answer = model.generate(generation_prompt)
# 评估支持度
for sentence in answer.split(". "):
sup_check = model.generate(f"Sentence: {sentence}\nDocs: {context}\n[IsSup]")
output.append({
"text": sentence,
"supported": sup_check
})
return output
8.7.3 引用标记与幻觉自检
Self-RAG的另一个重要特性是内联引用(inline citation)。在生成过程中,模型会为每个陈述添加引用标记,指明该陈述来源于哪个检索文档。这种设计不仅提高了答案的可信度,还使得用户能够验证信息的来源,增强了系统的可解释性。
引用标记与[IsSup]令牌配合,形成了有效的幻觉自检机制。当模型生成一个陈述时,[IsSup]令牌评估该陈述是否得到检索文档的支持。如果评估结果为no,说明该陈述可能是幻觉,系统可以触发重新检索或要求模型修正。这种机制显著降低了生成内容的幻觉率,提高了事实准确性。
# Self-RAG训练数据格式示例
# Input: What is the capital of France?
# Output:
# [Retrieve] yes
# Retrieved: [1] France is a country in Western Europe. [2] Paris is the capital and most populous city of France.
# [IsRel] relevant
# Paris is the capital of France. [IsSup] fully [1][2]
# [IsUse] useful
#
# Input: What is machine learning?
# Output:
# [Retrieve] no
# Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed. [IsSup] fully
# [IsUse] useful
# Self-RAG推理时的生成示例
def generate_with_citations(model, query, retriever):
"""生成带引用的回答"""
# 检索决策
need_retrieve = model.predict_retrieve(query)
citations = []
if need_retrieve:
docs = retriever.retrieve(query)
citations = [f"[{i+1}] {doc.content[:100]}..." for i, doc in enumerate(docs)]
# 生成带引用的答案
answer = model.generate_with_reflection(query, citations)
# 解析引用标记
parsed_answer = parse_citations(answer)
return {
"answer": parsed_answer["text"],
"citations": parsed_answer["citations"],
"supported": parsed_answer["support_scores"]
}
[10] Asai A, Wu Z, Wang Y, et al. Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. arXiv:2310.11511, 2023.
Self-RAG在多项基准测试中取得了突破性成果。在开放域问答任务上,使用7B参数的Self-RAG模型超越了参数量大得多的ChatGPT(175B),在事实准确性和引用完整性方面表现尤为突出。这一结果表明,通过巧妙的训练策略和自我反思机制,相对较小的模型也能实现强大的检索增强生成能力。
[15] DeepLearning.AI. Agentic RAG Course. www.deeplearning.ai/short-cours…
8.8 本章总结
本章系统介绍了RAG系统中的查询优化技术,涵盖了从查询重写、查询分解到多查询生成、假设性文档嵌入、查询路由以及纠错和自反思机制的完整技术体系。这些技术从不同角度解决查询质量问题,在实际应用中常常组合使用,形成强大的查询优化pipeline。
查询重写技术通过直接改写、抽象改写和伪文档扩展等方式,提升查询的表达能力和检索友好性;查询分解技术将复杂问题拆解为可管理的子问题,通过顺序或并行求解降低问题复杂度;多查询生成技术通过生成多角度查询扩大检索覆盖面,配合RRF融合算法整合多源结果;HyDE技术利用LLM的生成能力创建假设文档,有效缓解查询-文档语义鸿沟;查询路由技术智能选择检索目标,提高多数据源场景下的检索效率;CRAG和Self-RAG则引入了纠错和自反思机制,实现了更高质量的检索增强生成。
表8-8 查询优化技术综合对比
| 技术 | 核心机制 | 主要优势 | 适用场景 | 计算开销 |
|---|---|---|---|---|
| 查询重写 | 改写查询表达 | 提升检索友好性 | 口语化/模糊查询 | 中 |
| 查询分解 | 拆解复杂问题 | 降低单次检索难度 | 多跳/复杂问题 | 中高 |
| 多查询生成 | 多角度检索 | 扩大信息覆盖面 | 信息需求复杂 | 高 |
| HyDE | 假设文档嵌入 | 零样本效果好 | 语义鸿沟大 | 中 |
| 查询路由 | 智能选择数据源 | 提高检索效率 | 多数据源 | 低中 |
| CRAG | 纠错机制 | 提升检索可靠性 | 检索质量不稳定 | 中高 |
| Self-RAG | 自反思令牌 | 按需检索、有引用 | 高质量生成需求 | 高 |
在实际工程实践中,查询优化技术的选择需要综合考虑效果、延迟、成本等多个因素。建议从简单的技术开始(如查询重写),根据实际效果逐步引入更复杂的技术。同时,建立完善的评估体系,持续监控和优化查询优化pipeline的效果。随着大语言模型能力的不断提升,查询优化技术也将持续演进,为RAG系统带来更大的价值。
下一章我们将探讨RAG系统的评估与优化,包括检索质量评估、生成质量评估、端到端评估方法以及系统性能优化策略。掌握这些评估和优化技术,将帮助您构建更加可靠和高效的企业级RAG系统。
参考文献
[1] Gao Y, Xiong Y, Gao X, et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv:2312.10997, 2023.
[2] Ma X, Gong Y, He P, et al. Query Rewriting for Retrieval-Augmented Large Language Models. arXiv:2305.14283, 2023.
[3] Wang L, Yang N, Wei F. Query2doc: Query Expansion with Large Language Models. arXiv:2303.07678, 2023.
[4] Zheng H S, Mishra S, Chen X, et al. Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models. arXiv:2310.06117, 2023.
[5] Zhou D, Schärli N, Hou L, et al. Least-to-Most Prompting Enables Complex Reasoning in Large Language Models. arXiv:2205.10625, 2022.
[6] Trivedi H, Balasubramanian N, Khot T, et al. Interleaving Retrieval with Chain-of-Thought Reasoning for Knowledge-Intensive Multi-Step Questions. ACL 2023. arXiv:2212.10509, 2023.
[7] Gao L, Ma X, Lin J, et al. Precise Zero-Shot Dense Retrieval without Relevance Labels. arXiv:2212.10496, 2022.
[8] Jeong S, Baek J, Cho S, et al. Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models through Question Complexity. NAACL 2024. arXiv:2403.14403, 2024.
[9] Yan S, Gu J, Zhu Y, et al. Corrective Retrieval Augmented Generation. arXiv:2401.15884, 2024.
[10] Asai A, Wu Z, Wang Y, et al. Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. arXiv:2310.11511, 2023.
[11] LangChain. MultiQueryRetriever Documentation. python.langchain.com/docs/module…
[12] LlamaIndex. RouterQueryEngine Documentation. docs.llamaindex.ai/en/stable/e…
[13] Semantic Router. GitHub Repository. github.com/aurelio-lab…
[14] LangChain. CRAG Tutorial with LangGraph. langchain-ai.github.io/langgraph/t…
[15] DeepLearning.AI. Agentic RAG Short Course. www.deeplearning.ai/short-cours…