大模型应用系列:Query 变换的示例浅析 1. 引言当大模型遇上检索Query 变换为何成为关键过去两年大语言模型LLM与检索增强生成RAG的结合几乎重塑了 AI 应用的开发范式。从企业知识库问答到智能客服从代码助手到学术研究RAG 架构让模型能够“引用”外部知识大幅降低了幻觉提升了答案的准确性。然而在大量实践落地过程中一个看似简单却极其核心的问题逐渐浮出水面用户的原始查询Query往往并不适合直接用于检索。这就需要一种重要的技术手段——Query 变换Query Transformation。Query 变换顾名思义就是在将用户问题送入检索系统之前对原始问题进行一系列处理、改写、拆分或扩展从而生成更有利于检索的查询语句。它就像一座桥梁一端连接着用户自然、模糊甚至不完整的表达另一端连接着结构化、精确的知识库索引。没有这座桥梁RAG 系统的召回率、准确率和回答质量都会大打折扣。本文是“大模型应用系列”中的一篇将围绕 Query 变换这一主题从原理、分类、典型实现到实际代码示例进行深入浅出的解析。全文约 2 万字力求覆盖以下核心内容Query 变换产生的背景与解决的核心矛盾主要变换范式的分类查询扩展、查询重写、查询分解、查询压缩、查询规范化每种范式的典型算法、实现思路和适用场景结合大模型如 GPT-4、Llama 等进行 Query 变换的工程实践多个可运行的 Python 示例基于 LangChain、LlamaIndex 及自定义实现性能评估、优化策略与未来展望通过本文你将不仅理解 Query 变换的理论还能获得可以直接落地的代码片段为自己的 RAG 应用添上这块关键拼图。2. 为什么需要 Query 变换从用户问题到检索查询的鸿沟在深入技术细节之前我们先来直观感受一下用户提出的问题与知识库中存储的文档片段之间存在着怎样的“错配”。2.1 词汇与语义的错配用户提问通常使用日常用语而知识库文档可能由专业术语编写。例如用户问“那个开源的、能跑大模型的工具叫什么” 而文档中可能使用“大语言模型推理框架”、“LLM Serving Engine”等表述。如果直接使用原始问题进行关键词检索结果可能非常不理想。这就需要通过查询扩展或同义词替换来弥合差异。2.2 问题复杂度与检索粒度的矛盾一个多跳推理问题比如“2023 年诺贝尔物理学奖得主的博士论文题目是什么” 需要先检索出“2023 年诺贝尔物理学奖得主是谁”再根据该信息检索“该得主的博士论文题目”。如果直接检索整个问题可能找不到任何直接匹配的文档。此时需要查询分解将复杂问题拆解为多个子查询。2.3 上下文依赖与模糊性在多轮对话中用户常常省略上下文例如第二轮问“那它的训练成本呢” 这里的“它”指代上一轮提到的某个模型。如果直接检索“那它的训练成本呢”结果必然混乱。必须通过查询重写将对话历史融入当前查询生成独立、明确的查询。2.4 检索效率与精度的权衡有时用户的问题描述过于冗长包含大量无关细节直接检索会导致噪声引入降低检索质量。此时需要查询压缩提取核心查询意图。另一方面如果想提高召回率可能需要故意生成多个不同角度的查询查询扩展以覆盖更多相关文档。所有这些场景都指向同一个结论Query 变换是 RAG 系统中不可或缺的预处理步骤。它使得检索系统能更好地理解用户意图从知识库中捞出最相关、最精准的信息。3. Query 变换的分类与全景图根据变换的目标和方式我们可以将常见的 Query 变换方法归纳为以下几大类查询扩展 (Query Expansion)通过添加同义词、相关词、上位词或生成多个变体增加检索的覆盖面。查询重写 (Query Rewriting)利用对话历史、上下文或特定规则将原始查询转化为更清晰、更精确的查询语句。查询分解 (Query Decomposition)将复杂问题拆解为多个简单子问题分别检索后再合并答案。查询压缩 (Query Compression)从冗长的用户输入中提取核心查询意图去除噪声。查询规范化 (Query Normalization)纠正拼写错误、统一大小写、处理缩写、去除停用词等使查询形式标准化。这些分类并非互斥在实际应用中常常组合使用。例如一个多轮对话系统可能先进行查询重写融入历史再进行查询分解最后对每个子查询做查询扩展。下面我们将逐一深入剖析每种变换并给出具体的代码示例。4. 查询扩展 (Query Expansion)让检索系统“联想”起来查询扩展是最早被研究的 Query 变换技术之一在传统信息检索中已有广泛应用如基于同义词词表WordNet、相关反馈Relevance Feedback等。在大模型时代LLM 为查询扩展提供了更强大的语义生成能力。4.1 基于同义词库的扩展最简单的实现方式是维护一个领域相关的同义词、缩写、常用词汇映射表。例如# 简单的同义词扩展示例 synonym_dict { 大模型: [LLM, 大语言模型, 大型语言模型], 推理: [inference, 推断], GPU: [图形处理器, 显卡] } def expand_query_with_synonyms(query: str) - str: expanded query for word, synonyms in synonym_dict.items(): if word in query: expanded .join(synonyms) return expanded print(expand_query_with_synonyms(大模型推理需要什么GPU)) 输出大模型推理需要什么GPU LLM 大语言模型 大型语言模型 inference 推断 图形处理器 显卡这种方法简单直接但灵活性有限依赖人工维护词表。4.2 基于词向量/嵌入的扩展利用预训练词向量或句子嵌入可以找到与查询语义最接近的词语或短语进行扩展。例如使用 Word2Vec 或 GloVe 为查询中的关键词找到 k 个近邻词再将这些词加入查询。更进一步可以使用 Sentence-BERT 对整个查询生成嵌入然后从知识库中检索出与该查询嵌入最相似的文档标题或摘要再将其中高频关键词作为扩展词。不过在 RAG 系统中我们通常更关注如何利用 LLM 直接生成扩展查询。4.3 基于 LLM 的查询扩展生成多个视角的查询利用大语言模型强大的文本生成能力我们可以让它根据原始查询生成多个表述不同但意图相同的查询变体。这种方法尤其适用于提高召回率因为不同表述可能匹配到不同的文档片段。下面是一个使用 LangChain 的示例from langchain.prompts import ChatPromptTemplate from langchain.chat_models import ChatOpenAI llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.7) prompt ChatPromptTemplate.from_template( 请根据以下用户问题生成3个语义相同但表述不同的查询变体 用于检索系统。确保每个变体都保留原问题核心意图。\n 原始问题: {query}\n 查询变体: ) chain prompt | llm query 如何降低大模型推理的延迟? response chain.invoke({query: query}) print(response.content) 输出示例 1. 减少大语言模型推理延迟的方法有哪些 2. 怎样提升LLM推理速度降低响应时间 3. 降低大模型推理时延的技术手段。在实际 RAG 流程中我们可以生成多个扩展查询依次检索并将结果合并去重作为最终的上下文。LlamaIndex 提供了 MultiQueryRetriever 直接封装了这种模式下面是一个更完整的示例from llama_index.core import VectorStoreIndex, SimpleDirectoryReader from llama_index.core.query_engine import MultiQueryRetriever 假设索引已构建 documents SimpleDirectoryReader(data).load_data() index VectorStoreIndex.from_documents(documents) 使用 MultiQueryRetriever 自动生成多个查询 retriever MultiQueryRetriever.from_defaults( index.as_retriever(), num_queries3 # 生成3个变体 ) query_engine index.as_query_engine(retrieverretriever) response query_engine.query(如何降低大模型推理的延迟?) print(response)这种方式的优点是实现简单召回率高缺点是多个查询会增加检索延迟和算力成本且可能引入噪声需要配合结果重排Rerank来提升精度。4.4 基于假设文档的查询扩展 (HyDE)一种更前沿的查询扩展方法是 HyDEHypothetical Document Embeddings。其核心思想是让 LLM 根据用户问题先生成一个假设的答案文档然后用这个假设文档的嵌入去检索真实文档。因为假设文档更像真实文档的表述可以弥补查询与文档之间的语义鸿沟。实现步骤使用 LLM 生成一个假设文档该文档是对用户问题的理想回答。将该假设文档向量化。用该向量去检索知识库中相似的文档。示例代码from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma 假设已有向量数据库 vectorstore embeddings OpenAIEmbeddings() def hyde_retrieve(query: str, vectorstore, k4): # 第一步生成假设文档 llm ChatOpenAI(modelgpt-3.5-turbo) hypothesis_prompt ChatPromptTemplate.from_template( 请用一段话回答以下问题即使你不知道确切答案也请编造一个合理的回答。\n 问题: {query}\n 回答: ) hypothesis_chain hypothesis_prompt | llm hypothesis_doc hypothesis_chain.invoke({query: query}).content # 第二步向量化假设文档 hyde_embedding embeddings.embed_query(hypothesis_doc) 第三步检索相似文档 docs vectorstore.similarity_search_by_vector(hyde_embedding, kk) return docs/code/pre HyDE 在学术基准上表现优异尤其适用于查询与文档风格差异较大的场景如搜索式查询与长文档的匹配。但需要注意LLM 生成的假设文档可能包含错误信息不过检索过程只是用它作为嵌入匹配的桥梁最终答案仍由真实文档支撑。 5. 查询重写 (Query Rewriting)让对话中的模糊问题变清晰 查询重写是多轮对话 RAG 系统的关键组件。当用户在多轮对话中提出模糊的、指代性的问题时系统必须能够将对话历史融入当前查询生成一个独立、完整、明确的查询语句。比如 用户介绍一下 Transformer 模型。 助手……介绍 用户它有哪些变体 这里的“它”指代“Transformer 模型”。如果不进行重写直接检索“它有哪些变体”将毫无意义。重写后的查询应为“Transformer 模型有哪些变体” 5.1 基于规则的查询重写 简单的指代消解可以通过规则实现但规则难以覆盖所有情况。更实用的方法是利用 LLM 进行重写。LangChain 提供了 ConversationalRetrievalChain 等组件内部会自动处理历史消息但我们可以显式地实现一个查询重写模块。 5.2 基于 LLM 的查询重写 我们可以设计一个提示词让 LLM 根据对话历史将当前问题重写为一个独立的问题。示例如下 from langchain.memory import ConversationBufferMemory from langchain.prompts import ChatPromptTemplate from langchain.chat_models import ChatOpenAI llm ChatOpenAI(modelgpt-4, temperature0) rewrite_prompt ChatPromptTemplate.from_messages([ (system, 你是一个查询重写助手。根据聊天历史将用户的最新问题重写为一个独立、明确的查询 使其不依赖对话上下文。如果最新问题已经明确直接返回原句。), (human, 聊天历史:\n{chat_history}\n\n最新问题: {question}\n\n重写后的查询:) ]) 示例对话历史 chat_history User: 介绍一下 Transformer 模型。 Assistant: Transformer 是一种基于自注意力机制的神经网络架构... current_question 它有哪些变体 重写 chain rewrite_prompt | llm rewritten chain.invoke({chat_history: chat_history, question: current_question}) print(rewritten.content) # 输出类似Transformer 模型有哪些变体 在真实应用中对话历史会自动维护每次用户提问前都先经过重写模块再送入检索器。 LlamaIndex 也提供了类似的功能例如 CondenseQuestionChatEngine 等。我们可以自定义一个更轻量的重写器 def rewrite_query_with_history(query: str, history: list[dict]) - str: # history 格式: [{role: user, content: ...}, {role: assistant, content: ...}] history_text \n.join([f{msg[role]}: {msg[content]} for msg in history]) prompt f根据以下对话历史将最后一条用户问题重写为一个不需要上下文就能理解的独立问题。如果问题本身已经独立直接返回原句。 对话历史 {history_text} 用户最新问题: {query} 重写后的问题: response llm.invoke(prompt) return response.content 查询重写不仅限于指代消解还可以包含 纠正拼写错误如果用户输入有错别字LLM 可以自动修正。 补全省略信息如 “那它的性能呢” 补全为 “LLaMA-2 模型的性能如何” 标准化将口语化表达转为正式查询例如 “那个跑得很快的模型” 转为 “推理速度最快的模型”。 查询重写虽然简单但它是多轮 RAG 稳定性的基石。 6. 查询分解 (Query Decomposition)化繁为简逐个击破 现实中的用户问题往往不是单一的而是包含多个子问题或需要多步推理才能回答。例如 “对比一下 GPT-4 和 Claude 3 在代码生成和数学推理上的表现并给出一个示例。” 这个问题可以拆解为① GPT-4 在代码生成上的表现② Claude 3 在代码生成上的表现③ GPT-4 在数学推理上的表现④ Claude 3 在数学推理上的表现⑤ 一个具体示例。 “2022 年世界杯冠军球队的主教练是谁” 需要先检索“2022 年世界杯冠军”得到“阿根廷”再检索“阿根廷国家足球队主教练”。 查询分解也叫子问题分解正是解决这类复杂问题的利器。基本思路是让 LLM 将复杂问题拆解为多个简单子问题然后依次检索每个子问题收集所有结果最后再让 LLM 综合这些信息生成最终答案。 6.1 基于 Few-shot 的分解 我们可以提供几个示例引导 LLM 学会分解。下面是一个简单的实现 decomposition_prompt ChatPromptTemplate.from_messages([ (system, 你是一个问题分解专家。请将用户提出的复杂问题分解为若干个独立的、可以直接用于检索的简单子问题。 每个子问题应该简洁明了且整体上覆盖原问题的所有意图。输出格式为 Python 列表。), (human, 示例1:\n问题: 对比一下苹果和特斯拉的市值和创始人。\n分解后的子问题: [苹果公司的市值, 特斯拉公司的市值, 苹果公司的创始人, 特斯拉公司的创始人]), (human, 示例2:\n问题: 2023年诺贝尔文学奖得主的主要作品有哪些\n分解后的子问题: [2023年诺贝尔文学奖得主是谁, 该得主的主要作品列表]), (human, 现在请分解以下问题\n{question}\n分解后的子问题列表:) ]) chain decomposition_prompt | llm question 对比一下 GPT-4 和 Claude 3 在代码生成和数学推理上的表现并给出一个示例。 result chain.invoke({question: question}) 解析字符串为列表实际中需处理输出格式 import ast sub_questions ast.literal_eval(result.content) print(sub_questions) 可能输出 [GPT-4 代码生成表现, Claude 3 代码生成表现, GPT-4 数学推理表现, Claude 3 数学推理表现, 代码生成示例] 6.2 递归分解与多跳检索 对于多跳问题分解可能需要递归进行并且子问题之间有依赖关系。例如问题“2023 年诺贝尔物理学奖得主的博士论文题目是什么”分解为 2023 年诺贝尔物理学奖得主是谁 → 检索得到“Anne LHuillier, Pierre Agostini, Ferenc Krausz” Anne LHuillier 的博士论文题目 → 检索 Pierre Agostini 的博士论文题目 → 检索 Ferenc Krausz 的博士论文题目 → 检索 这种情况下我们需要一个能够处理依赖关系的分解引擎。LlamaIndex 的 SubQuestionQueryEngine 提供了类似功能。其内部流程大致为 LLM 生成子问题并为每个子问题标注是否依赖之前的答案。 按顺序执行子问题如果依赖则用之前检索到的上下文作为输入。 最后将所有子答案合并生成最终答案。 下面是一个使用 LlamaIndex 的简化示例 from llama_index.core.tools import QueryEngineTool, ToolMetadata from llama_index.core.query_engine import SubQuestionQueryEngine 假设已配置好 query_engine 和 llm query_engine_tool QueryEngineTool( query_enginequery_engine, metadataToolMetadata(namekb_search, description搜索知识库) ) sq_query_engine SubQuestionQueryEngine.from_defaults( query_engine_tools[query_engine_tool], llmllm, verboseTrue ) response sq_query_engine.query(2023年诺贝尔物理学奖得主的博士论文题目是什么) print(response) 在实际应用中查询分解可能会显著增加延迟因为需要多次 LLM 调用和检索。因此优化策略包括 并行执行不依赖的子问题。 对子问题也进行查询扩展提高检索质量。 使用缓存避免重复检索。 查询分解是解决复杂 RAG 问题的核心手段也是提升答案准确率的重要方向。 7. 查询压缩 (Query Compression)去除噪声提取核心意图 在 RAG 系统中用户输入有时非常冗长可能包含大量无关的背景描述、客套话或重复信息。如果直接使用这样的长文本进行检索检索系统可能会被噪声误导返回不相关的结果。此外长文本可能会超出嵌入模型的输入长度限制或导致嵌入语义稀释。 查询压缩的目标就是从长文本中提取出最核心的查询意图生成一个简洁的查询语句。 7.1 基于 LLM 的提取式压缩 最直接的方法是让 LLM 对用户输入进行总结提取关键问题。例如 compression_prompt ChatPromptTemplate.from_template( 请从以下用户输入中提取出最核心的问题或查询意图尽量简洁不超过20个字。\n 用户输入: {query}\n 核心查询: ) chain compression_prompt | llm long_query 你好我最近在学习大模型相关知识想了解一下嗯就是如果我们想要部署一个模型让它能够快速响应用户的请求有没有什么比较好的优化方法呢比如从硬件、软件各个层面我听说好像有什么量化、剪枝之类的你能详细讲讲吗 compressed chain.invoke({query: long_query}) print(compressed.content) # 输出类似大模型推理加速优化方法 7.2 使用对话摘要进行压缩 在多轮对话中随着对话轮次增多可能需要将整个对话历史压缩为一个简洁的摘要作为后续检索的上下文。这实际上是查询重写与压缩的结合。LangChain 的 ConversationSummaryMemory 就是这种思路但我们可以单独使用摘要来压缩查询上下文。 7.3 递归压缩 (Recursive Compression) 对于极长的输入可以采用递归分段压缩的策略将长文本切分为多个块分别压缩然后再合并压缩直到得到最终的精简查询。这种策略类似于 Map-Reduce 摘要。 查询压缩在客服场景、邮件摘要、会议记录等长文本输入中尤其有用能显著提升检索的效率和准确性。 8. 查询规范化 (Query Normalization)让查询更标准 查询规范化是传统信息检索中的基础操作主要包括 拼写纠正将“大模形”纠正为“大模型”。 大小写统一全部转为小写或大写。 去除停用词去掉“的”、“是”、“在”等无实意词。 词干提取 / 词形还原将“running”转为“run”将“better”转为“good”。 缩写展开将“RAG”展开为“Retrieval-Augmented Generation”。 在大模型时代这些规范化操作可以部分由 LLM 完成但许多基础操作仍可借助传统 NLP 工具高效实现比如使用 spaCy、nltk 等。 一个简单的规范化管道 import re import nltk from nltk.corpus import stopwords from nltk.stem import WordNetLemmatizer nltk.download(stopwords) nltk.download(wordnet) stop_words set(stopwords.words(english)) lemmatizer WordNetLemmatizer() def normalize_query(query: str) - str: 转小写 query query.lower() 去除标点 query re.sub(r[^\w\s], , query) 分词 words query.split() 去除停用词并词形还原 words [lemmatizer.lemmatize(w) for w in words if w not in stop_words] return .join(words) print(normalize_query(What are the best practices for scaling LLM inference?)) 输出: best practice scaling llm inference 尽管嵌入模型对原始文本的噪声有一定鲁棒性但规范化仍能提升检索的一致性和效率尤其是在处理用户可能输入错误的场景下。 9. Query 变换的全流程架构设计 在实际工程中Query 变换往往不是单独使用而是组合成一个流水线Pipeline。一个典型的 RAG 系统 Query 变换流程可能如下 输入预处理拼写检查、规范化。 多轮对话重写融合历史生成独立查询。 查询压缩如果输入过长提取核心意图。 查询分解如果问题复杂拆解为子问题。 查询扩展对每个子查询生成多个变体或使用 HyDE。 检索与重排执行检索合并结果重排后返回。 下面用 Mermaid 流程图展示一个示例架构 flowchart TD A[用户输入] -- B{是否为多轮对话?} B --|是| C[查询重写模块] B --|否| D[规范化 压缩] C -- D D -- E{问题复杂度判断} E --|简单| F[查询扩展] E --|复杂| G[查询分解] G -- H[子问题1] G -- I[子问题2] G -- J[子问题n] H -- F I -- F J -- F F -- K[检索器] K -- L[结果合并与重排] L -- M[LLM 生成答案] M -- N[返回用户] C -- O[对话历史记忆] O -- C 在这个架构中每个模块都可以独立配置和替换。我们可以使用 LangChain 的 LCEL 或 LlamaIndex 的 Query Pipeline 来构建这样的流水线实现模块化、可观测的 Query 变换系统。 10. 代码实战构建一个完整的 Query 变换 RAG 系统 让我们动手实现一个较为完整的示例整合上述多种变换技术。系统将使用 Chroma 作为向量数据库OpenAI 作为 LLM 和嵌入模型LangChain 作为编排框架。示例将包括查询重写、查询分解、查询扩展并展示如何将它们组合。 首先安装依赖 pip install langchain openai chromadb tiktoken 构建示例知识库和向量存储 from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.docstore.document import Document 示例文档 documents [ Document(page_contentGPT-4 是 OpenAI 开发的大型语言模型在代码生成、数学推理等多项任务上表现优异。), Document(page_contentClaude 3 是 Anthropic 推出的模型强调安全性和诚实性在代码生成和数学方面也有出色表现。), Document(page_contentTransformer 架构由 Vaswani 等人在 2017 年提出基于自注意力机制。), Document(page_content降低大模型推理延迟的常用方法包括模型量化、使用 TensorRT 优化、批处理、以及选用更高效的硬件。), Document(page_content2023 年诺贝尔物理学奖授予 Pierre Agostini、Ferenc Krausz 和 Anne LHuillier以表彰他们在阿秒物理方面的贡献。), Document(page_contentAnne LHuillier 的博士论文题目是 Multiple Harmonic Generation in Gases。), Document(page_contentFerenc Krausz 的博士论文题目是 Generation and Measurement of Attosecond Light Pulses。), Document(page_contentPierre Agostini 的博士论文题目是 Multiphoton Ionization of Atoms。), ] embeddings OpenAIEmbeddings() vectorstore Chroma.from_documents(documents, embeddings) retriever vectorstore.as_retriever(search_kwargs{k: 3}) 接下来实现查询重写、分解和扩展的模块。为简化我们编写一个综合的 Query Transform 类 from langchain.chat_models import ChatOpenAI from langchain.prompts import ChatPromptTemplate import ast class SmartQueryTransformer: def init(self, llm: ChatOpenAI): self.llm llm def rewrite(self, query: str, history: list None) -gt; str: 融入对话历史重写查询 if not history: return query history_text \n.join([f{msg[role]}: {msg[content]} for msg in history]) prompt ChatPromptTemplate.from_template( 根据对话历史将以下问题重写为一个独立的查询。\n 历史:\n{history}\n\n问题: {query}\n\n重写后: ) chain prompt | self.llm return chain.invoke({history: history_text, query: query}).content def decompose(self, query: str) -gt; list[str]: 将复杂问题分解为子问题 prompt ChatPromptTemplate.from_messages([ (system, 将复杂问题分解为多个简单子问题返回 Python 列表。), (human, 问题: {query}\n子问题列表:) ]) chain prompt | self.llm result chain.invoke({query: query}).content try: sub_queries ast.literal_eval(result) return sub_queries if isinstance(sub_queries, list) else [query] except: return [query] # 解析失败则返回原问题 def expand(self, query: str, num_variants: int 3) -gt; list[str]: 生成多个查询变体 prompt ChatPromptTemplate.from_template( 请生成 {num} 个与以下查询语义相同的变体用于检索。\n 查询: {query}\n 变体列表 (Python 列表格式): ) chain prompt | self.llm result chain.invoke({query: query, num: num_variants}).content try: variants ast.literal_eval(result) return variants if isinstance(variants, list) else [query] except: return [query]/code/pre 现在构建主 RAG 流程集成这些变换 from langchain.chains import RetrievalQA from langchain.chat_models import ChatOpenAI llm ChatOpenAI(modelgpt-4, temperature0) transformer SmartQueryTransformer(llm) 模拟对话历史 chat_history [ {role: user, content: 介绍一下 Transformer 模型。}, {role: assistant, content: Transformer 是一种基于自注意力机制的神经网络架构...} ] user_query 它有哪些变体 # 模糊问题 重写 rewritten_query transformer.rewrite(user_query, chat_history) print(重写后查询:, rewritten_query) 输出: Transformer 模型有哪些变体 分解假设问题不复杂直接跳过 实际应用中可以判断是否复杂再分解 sub_queries transformer.decompose(rewritten_query) print(子问题:, sub_queries) 可能输出: [Transformer 模型有哪些变体] (因为本身简单) 扩展 all_queries [] for sq in sub_queries: variants transformer.expand(sq, num_variants2) all_queries.extend(variants) print(所有查询:, all_queries) 检索并合并结果 docs set() for q in all_queries: retrieved retriever.get_relevant_documents(q) docs.update(retrieved) 构造最终答案 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, retrieverretriever, # 这里实际应使用合并后的 docs但为简化示例直接传入检索器 ) 或者手动构建上下文 context \n\n.join([doc.page_content for doc in docs]) final_prompt f根据以下信息回答问题\n\n{context}\n\n问题{rewritten_query}\n答案 answer llm.invoke(final_prompt) print(最终答案:, answer.content) 通过这个示例我们展示了如何将多种 Query 变换技术串联起来形成一个灵活的 RAG 预处理管道。在实际部署中可以进一步使用异步处理、缓存、并行扩展等手段提升性能。 性能评估与优化策略 Query 变换虽然能提升检索质量但也引入了额外的计算开销和延迟。因此如何评估其效果并优化至关重要。 11.1 评估指标 召回率 (Recall)通过变换后相关文档是否被检索到。可以使用标注数据集对比原始查询与变换后查询的召回率变化。 精确率 (Precision)变换是否引入了过多噪声可以计算 Top-K 结果的精确率。 答案质量最终 RAG 答案的准确性、完整性可借助 LLM-as-judge 进行自动评估。 延迟 (Latency)变换步骤增加的耗时需在可接受范围内。 11.2 优化策略 缓存对于相同的重写或扩展查询可以缓存结果避免重复调用 LLM。 条件触发并非所有查询都需要全部变换步骤。可以训练一个轻量级分类器判断是否需要分解、扩展等减少不必要的 LLM 调用。 并行执行子问题分解后的检索、扩展查询的检索都可以并行处理显著降低整体延迟。 模型选择对于简单的重写、规范化可以使用较小的模型如 GPT-3.5 或本地小模型降低成本。 结果重排 (Rerank)在扩展查询可能导致较多召回时使用重排模型如 Cohere Rerank、bge-reranker对合并结果进行精排保留最相关的 Top-K。 11.3 实验设计 进行 A/B 测试对比有无 Query 变换的 RAG 系统在真实业务数据上的端到端表现。关注指标用户满意度、人工评价分数、答案准确率等。也可以使用公开的 RAG 评估基准如 RGB、RECALL 等进行离群测试。 未来展望Query 变换的演进方向 随着大模型和 RAG 技术的持续发展Query 变换也在不断进化 自适应变换系统能够根据查询的类型、知识库的特点自动选择最优变换策略无需人工配置。 多模态查询变换当用户输入包含图片、语音时如何将多模态查询转化为适合检索的表示是一个新兴方向。 与 Agent 的深度融合Query 变换可以作为 AI Agent 的规划环节Agent 自主决定需要进行哪些变换并调用工具执行。 端到端学习通过强化学习或直接偏好优化让变换模块与检索、生成模块联合优化真正实现数据驱动的 RAG 流水线。 隐私与安全在查询变换中如何保护用户隐私防止敏感信息泄露也是需要关注的问题。 可以说Query 变换是 RAG 系统从“可用”走向“好用”的关键一步未来值得持续投入和研究。 总结 本文深入探讨了大模型应用中 Query 变换的重要性、核心技术和实践方法。我们从问题出发介绍了查询扩展、查询重写、查询分解、查询压缩和查询规范化等主要范式并结合代码示例展示了如何在实际 RAG 系统中使用这些技术。通过合理的 Query 变换我们能够显著提升检索的召回率和准确率从而让大模型生成更精准、更可靠的答案。 希望本文能为你搭建高性能 RAG 应用提供有价值的参考。如果你对某个具体方面有更深入的兴趣欢迎在评论区交流讨论。