工业级大模型学习之路011:RAG 零基础入门教程(第七篇):查询优化技术

一、查询优化的必要性

1.1 检索失败的头号元凶:查询 - 文档语义错位

现在的系统虽然有了重排序,但仍然会遇到以下问题:

  • 用户问 "JVM 怎么调优",但文档里写的是 "Java 虚拟机性能优化实践"
  • 用户问 "RAG 能解决什么问题",但文档里写的是 "检索增强生成技术的应用场景"
  • 用户问 "怎么部署大模型",但文档里写的是 "LLM 生产环境部署指南"

根本原因

  • 用户的查询往往是口语化、简短、有歧义
  • 文档中的表述往往是书面化、专业、详细
  • 即使是最好的嵌入模型和重排序模型,也无法完全弥补这种语义鸿沟

数据统计 :在工业级 RAG 系统中,超过 40% 的检索失败都是由查询 - 文档语义错位导致的,而不是文档本身没有相关信息。

1.2 主流查询优化技术全景

目前最常用的查询优化技术可以分为 4 大类,按照效果和复杂度排序:

技术名称 核心思想 效果提升 实现难度 适用场景
查询重写 让大模型将用户查询改写为更专业、更适合检索的查询 15%-25% 所有场景,尤其是口语化查询多的系统
多查询检索 生成 3-5 个不同角度的查询,分别检索后合并结果 20%-30% 知识库规模大、查询复杂的系统
RAG-Fusion 对多个查询的检索结果进行倒数排序融合(RRF) 25%-35% 追求极致召回率的系统
HyDE(假设文档嵌入) 让大模型先生成一个假设的回答,再用这个回答去检索 30%-40% 专业领域知识库、查询非常简短的系统

学习顺序:查询重写 → 多查询检索 → RAG-Fusion → HyDE

工业界标准组合:查询重写 + RAG-Fusion + 重排序

1.3 查询重写:最基础也最有效的优化

查询重写的核心是让大模型做 "翻译",把用户的自然语言查询翻译成 "检索语言"。

2026 年最新的查询重写策略

  1. 扩展式重写 :补充查询中缺失的背景信息和专业术语
    • 用户查询:"JVM 调优" → 重写后:"Java 虚拟机性能优化方法、参数配置、常见问题解决方案"
  2. 简化式重写 :去除查询中的冗余信息和口语化表达
    • 用户查询:"我想知道怎么才能让我的 Java 程序跑得更快一点" → 重写后:"Java 程序性能优化方法"
  3. 歧义消除式重写 :消除查询中的歧义,明确查询意图
    • 用户查询:"苹果的系统怎么样" → 重写后:"苹果公司的 macOS 操作系统特点和优缺点"
  4. 结构化重写 :将自然语言查询转换为结构化查询语言(如 SQL、Cypher)
    • 适用于结构化知识库和混合检索系统

1.4 多查询检索:从 "单点搜索" 到 "多点搜索"

多查询检索的核心是从多个角度理解用户的查询意图,生成多个互补的查询,分别检索后合并结果。

2026 年最佳实践

  • 生成 3-5 个查询,太多会增加延迟,太少效果不明显
  • 每个查询应该从不同的角度描述同一个问题
  • 避免生成重复或过于相似的查询

示例

  • 用户查询:"检索增强生成和语义搜索有什么区别?"
  • 生成的多查询:
    1. 检索增强生成技术与语义搜索技术的核心区别
    2. RAG 和语义搜索的优缺点对比
    3. 检索增强生成和语义搜索的适用场景
    4. 语义搜索和 RAG 的技术原理差异

1.5 RAG-Fusion:解决多查询结果的排序问题

多查询检索会返回多个结果列表,如何将它们合并成一个有序的结果列表是关键。

传统方法的问题

  • 简单合并:会导致重复结果,而且无法区分不同查询的结果质量
  • 加权合并:需要手动设置权重,效果不稳定

2026 年工业界标准方法:倒数排序融合(RRF)

RRF 的核心思想是:一个文档在多个查询的结果中排名越靠前,它的最终得分就越高

RRF 公式

bash 复制代码
RRF(d) = Σ 1 / (k + rank(d, q))
  • d:文档
  • q:查询
  • rank(d, q):文档d在查询q的结果中的排名
  • k:常数,通常设置为 60

RRF 的优势

  • 不需要手动设置权重
  • 对不同查询的结果质量不敏感
  • 效果稳定,在各种场景下都能取得不错的效果

1.6 HyDE:用生成来提升检索

HyDE(Hypothetical Document Embeddings)的核心思想是:让大模型先生成一个假设的、理想的回答,然后用这个回答去检索文档

工作流程

  1. 用户输入查询
  2. 大模型生成一个假设的回答(不需要准确,只需要语义相似)
  3. 将这个假设的回答编码为向量
  4. 用这个向量去检索相似的文档

HyDE 的优势

  • 可以极大地提升短查询和模糊查询的召回率
  • 可以捕捉到查询和文档之间的深层语义关系
  • 效果比传统的查询重写更好

HyDE 的局限性

  • 增加了一次大模型调用,延迟更高
  • 如果大模型生成的假设回答偏离了主题,会导致检索结果变差

二、从零开始构建查询优化流水线

2.1 环境准备与本地大模型下载

我们将使用DeepSeek-V2-Lite-7B-Instruct作为查询优化的本地大模型,它是 2026 年最适合本地运行的轻量级大模型,中文效果好,速度快,16G 内存可以流畅运行。

第一步:安装最新依赖
python 复制代码
pip install transformers==4.45.0 accelerate==0.34.0 torch==2.4.1
第二步:下载本地模型
  1. 访问 ModelScope 官方地址:https://www.modelscope.cn/models/deepseek-ai/deepseek-llm-7b-chat

  2. 点击右上角 "下载模型",下载完整的模型文件(至少 电脑至少20GB 内存 才不卡顿)

  3. 解压到模型目录:

    python 复制代码
    C:\Users\87624\.cache\modelscope\hub\models\deepseek-ai\deepseek-llm-7b-chat

2.2 实现通用大模型客户端

新建文件 llm_client.py,复制以下代码:

python 复制代码
from llm_client import LLMClient
import json

class QueryOptimizer:
    def __init__(self, llm_client: LLMClient):
        """
        初始化查询优化器
        :param llm_client: 大模型客户端
        """
        self.llm = llm_client
    
    def rewrite_query(self, query: str) -> str:
        """
        查询重写:将用户查询改写为更适合检索的专业查询
        :param query: 原始用户查询
        :return: 重写后的查询
        """
        prompt = f"""你是一个专业的查询重写专家。请将用户的查询改写为更专业、更详细、更适合信息检索的查询。
要求:
1. 补充查询中缺失的背景信息和专业术语
2. 去除口语化表达和冗余信息
3. 消除查询中的歧义
4. 只返回重写后的查询,不要返回任何其他内容

原始查询:{query}
重写后的查询:"""
        
        rewritten_query = self.llm.generate(prompt, max_new_tokens=256, temperature=0.1)
        print(f"🔄 查询重写:{query} → {rewritten_query}")
        return rewritten_query
    
    def generate_multiple_queries(self, query: str, num_queries: int = 4) -> list:
        """
        多查询生成:生成多个不同角度的查询
        :param query: 原始用户查询
        :param num_queries: 生成的查询数量
        :return: 生成的查询列表
        """
        prompt = f"""你是一个专业的查询生成专家。请根据用户的原始查询,生成{num_queries}个不同角度的查询。
要求:
1. 每个查询应该从不同的角度描述同一个问题
2. 查询应该专业、准确、适合信息检索
3. 不要生成重复或过于相似的查询
4. 以JSON数组的形式返回结果,不要返回任何其他内容

原始查询:{query}
生成的查询列表:"""
        
        response = self.llm.generate(prompt, max_new_tokens=512, temperature=0.3)
        
        try:
            # 解析JSON数组
            queries = json.loads(response)
            # 确保是列表类型
            if not isinstance(queries, list):
                raise ValueError("生成的结果不是列表")
            # 去重
            queries = list(set(queries))
            # 确保数量正确
            if len(queries) < num_queries:
                queries.append(query)
            print(f"🔄 生成{len(queries)}个多查询:{queries}")
            return queries
        except Exception as e:
            print(f"❌ 多查询生成失败,使用原始查询:{e}")
            return [query]
    
    def generate_hypothetical_answer(self, query: str) -> str:
        """
        HyDE:生成假设的回答
        :param query: 原始用户查询
        :return: 假设的回答
        """
        prompt = f"""请根据你的知识,生成一个关于以下问题的简短回答。回答不需要完全准确,只需要语义相关即可。
问题:{query}
回答:"""
        
        hypothetical_answer = self.llm.generate(prompt, max_new_tokens=512, temperature=0.7)
        print(f"🔄 HyDE生成假设回答:{hypothetical_answer[:100]}...")
        return hypothetical_answer

# 测试代码
if __name__ == "__main__":
    from llm_client import LLMClient
    llm = LLMClient(device="cpu")
    optimizer = QueryOptimizer(llm)
    
    query = "检索增强生成和语义搜索有什么区别?"
    
    # 测试查询重写
    rewritten = optimizer.rewrite_query(query)
    
    # 测试多查询生成
    multi_queries = optimizer.generate_multiple_queries(query)
    
    # 测试HyDE
    hyde_answer = optimizer.generate_hypothetical_answer(query)

2.3 实现查询重写模块

新建文件 query_optimizer.py,复制以下代码:

python 复制代码
from llm_client import LLMClient
import json

class QueryOptimizer:
    def __init__(self, llm_client: LLMClient):
        """
        初始化查询优化器
        :param llm_client: 大模型客户端
        """
        self.llm = llm_client
    
    def rewrite_query(self, query: str) -> str:
        """
        查询重写:将用户查询改写为更适合检索的专业查询
        :param query: 原始用户查询
        :return: 重写后的查询
        """
        prompt = f"""你是一个专业的查询重写专家。请将用户的查询改写为更专业、更详细、更适合信息检索的查询。
要求:
1. 补充查询中缺失的背景信息和专业术语
2. 去除口语化表达和冗余信息
3. 消除查询中的歧义
4. 只返回重写后的查询,不要返回任何其他内容

原始查询:{query}
重写后的查询:"""
        
        rewritten_query = self.llm.generate(prompt, max_new_tokens=256, temperature=0.1)
        print(f"🔄 查询重写:{query} → {rewritten_query}")
        return rewritten_query
    
    def generate_multiple_queries(self, query: str, num_queries: int = 4) -> list:
        """
        多查询生成:生成多个不同角度的查询
        :param query: 原始用户查询
        :param num_queries: 生成的查询数量
        :return: 生成的查询列表
        """
        prompt = f"""你是一个专业的查询生成专家。请根据用户的原始查询,生成{num_queries}个不同角度的查询。
要求:
1. 每个查询应该从不同的角度描述同一个问题
2. 查询应该专业、准确、适合信息检索
3. 不要生成重复或过于相似的查询
4. 以JSON数组的形式返回结果,不要返回任何其他内容

原始查询:{query}
生成的查询列表:"""
        
        response = self.llm.generate(prompt, max_new_tokens=512, temperature=0.3)
        
        try:
            # 解析JSON数组
            queries = json.loads(response)
            # 确保是列表类型
            if not isinstance(queries, list):
                raise ValueError("生成的结果不是列表")
            # 去重
            queries = list(set(queries))
            # 确保数量正确
            if len(queries) < num_queries:
                queries.append(query)
            print(f"🔄 生成{len(queries)}个多查询:{queries}")
            return queries
        except Exception as e:
            print(f"❌ 多查询生成失败,使用原始查询:{e}")
            return [query]
    
    def generate_hypothetical_answer(self, query: str) -> str:
        """
        HyDE:生成假设的回答
        :param query: 原始用户查询
        :return: 假设的回答
        """
        prompt = f"""请根据你的知识,生成一个关于以下问题的简短回答。回答不需要完全准确,只需要语义相关即可。
问题:{query}
回答:"""
        
        hypothetical_answer = self.llm.generate(prompt, max_new_tokens=512, temperature=0.7)
        print(f"🔄 HyDE生成假设回答:{hypothetical_answer[:100]}...")
        return hypothetical_answer

# 测试代码
if __name__ == "__main__":
    from llm_client import LLMClient
    llm = LLMClient(device="cpu")
    optimizer = QueryOptimizer(llm)
    
    query = "检索增强生成和语义搜索有什么区别?"
    
    # 测试查询重写
    rewritten = optimizer.rewrite_query(query)
    
    # 测试多查询生成
    multi_queries = optimizer.generate_multiple_queries(query)
    
    # 测试HyDE
    hyde_answer = optimizer.generate_hypothetical_answer(query)

2.4 实现 RAG-Fusion 融合算法

query_optimizer.py 中添加以下代码:

python 复制代码
def rrf_fuse(self, results_list: list, k: int = 60) -> list:
    """
    倒数排序融合(RRF):融合多个查询的检索结果
    :param results_list: 多个查询的检索结果列表,每个元素是一个检索结果列表
    :param k: RRF参数,通常设置为60
    :return: 融合后的结果列表
    """
    if not results_list:
        return []
    
    # 计算每个文档的RRF得分
    doc_scores = {}
    for results in results_list:
        for rank, doc in enumerate(results):
            doc_id = doc["id"]
            if doc_id not in doc_scores:
                doc_scores[doc_id] = {
                    "doc": doc,
                    "score": 0.0
                }
            # RRF公式:1 / (k + rank)
            doc_scores[doc_id]["score"] += 1.0 / (k + rank + 1)  # rank从0开始,所以+1
    
    # 按RRF得分降序排序
    fused_docs = sorted(doc_scores.values(), key=lambda x: x["score"], reverse=True)
    
    # 提取文档信息
    final_results = []
    for item in fused_docs:
        doc = item["doc"].copy()
        doc["rrf_score"] = item["score"]
        final_results.append(doc)
    
    print(f"🔄 RAG-Fusion融合完成,从{len(results_list)}个查询结果中融合出{len(final_results)}个唯一结果")
    return final_results

2.5 集成到 HybridRetriever 中

打开 hybrid_retriever.py,进行以下修改:

第一步:导入查询优化器
python 复制代码
from query_optimizer import QueryOptimizer
from llm_client import LLMClient
第二步:在__init__方法中初始化查询优化器
python 复制代码
def __init__(self, chunks, collection_name="rag_knowledge_base", db_path="./chroma_db", device="cpu"):
    self.chunks = chunks
    self.device = device

    print("正在初始化Chroma语义检索器...")
    self.client = chromadb.PersistentClient(path=db_path)
    self.bge_embedding = load_bge_model(device=device)
    self.collection = self.client.get_or_create_collection(
        name=collection_name,
        embedding_function=self.bge_embedding,
        metadata={"hnsw:space": "cosine"}
    )

    # 每次启动都重新全量入库,确保新文档一定能搜到
    print("正在全量更新向量库...")
    self.client.delete_collection(name=collection_name)
    self.collection = self.client.create_collection(
        name=collection_name,
        embedding_function=self.bge_embedding,
        metadata={"hnsw:space": "cosine"}
    )

    ids = [chunk["id"] for chunk in chunks]
    documents = [chunk["text"] for chunk in chunks]
    metadatas = [chunk["metadata"] for chunk in chunks]
    self.collection.add(ids=ids, documents=documents, metadatas=metadatas)

    print("正在初始化BM25检索器...")
    self.bm25_retriever = BM25Retriever(chunks)
    
    print("正在初始化重排序器...")
    self.reranker = Reranker(device=device)
    
    # 【新增】初始化大模型客户端和查询优化器
    print("正在初始化查询优化器...")
    self.llm_client = LLMClient(device=device)
    self.query_optimizer = QueryOptimizer(self.llm_client)
    
    print("✅ 混合检索器初始化完成")
第三步:添加完整的查询优化检索流水线
python 复制代码
def optimized_search(self, query, top_k=5, rerank_top_n=20, use_rewrite=True, use_multi_query=True, use_rrf=True, use_hyde=False):
    """
    完整的查询优化检索流水线
    :param query: 原始用户查询
    :param top_k: 最终返回结果数量
    :param rerank_top_n: 重排序前的粗筛结果数量
    :param use_rewrite: 是否使用查询重写
    :param use_multi_query: 是否使用多查询检索
    :param use_rrf: 是否使用RAG-Fusion融合
    :param use_hyde: 是否使用HyDE
    :return: 最终的检索结果列表
    """
    # 1. 查询预处理
    processed_query = query
    if use_rewrite:
        processed_query = self.query_optimizer.rewrite_query(query)
    
    # 2. 生成查询列表
    queries_to_search = [processed_query]
    if use_multi_query:
        multi_queries = self.query_optimizer.generate_multiple_queries(processed_query)
        queries_to_search.extend(multi_queries)
    
    # 3. HyDE:添加假设回答到查询列表
    if use_hyde:
        hypothetical_answer = self.query_optimizer.generate_hypothetical_answer(processed_query)
        queries_to_search.append(hypothetical_answer)
    
    # 4. 对每个查询进行混合检索
    all_results = []
    for q in queries_to_search:
        results = self.hybrid_search(q, top_k=rerank_top_n)
        all_results.append(results)
    
    # 5. 融合多个查询的结果
    if use_rrf and len(all_results) > 1:
        fused_results = self.query_optimizer.rrf_fuse(all_results)
    else:
        # 如果不使用RRF或只有一个查询,直接合并结果并去重
        fused_results = []
        seen_ids = set()
        for results in all_results:
            for doc in results:
                if doc["id"] not in seen_ids:
                    seen_ids.add(doc["id"])
                    fused_results.append(doc)
    
    # 6. 重排序
    reranked_results = self.reranker.rerank(query, fused_results, top_k=top_k)
    
    print(f"\n✅ 完整检索流水线完成,最终返回{len(reranked_results)}个结果")
    return reranked_results

2.6 修改 RAG 核心类,使用优化后的检索

打开 rag_core.py,找到调用检索的地方,修改为:

python 复制代码
def query(self, question, top_k=5, stream=False, max_context_tokens=12000):
    processed_question = self._preprocess_query(question)
    if not processed_question:
        return "请输入有效的问题。"
    
    # 使用完整的查询优化检索流水线
    retrieved_docs = self.retriever.optimized_search(
        query=processed_question,
        top_k=top_k,
        rerank_top_n=20,
        use_rewrite=True,
        use_multi_query=True,
        use_rrf=True,
        use_hyde=False  # 先关闭HyDE,后面再测试
    )
    
    print(f"\n📝 最终送入大模型的文档数量:{len(retrieved_docs)}")
    
    if not retrieved_docs:
        return "抱歉,知识库中没有找到相关信息,无法回答您的问题。"
    
    # 后续的上下文拼接和大模型生成代码保持不变
    # ...
相关推荐
caijing3651 小时前
全方位解析建筑设备系统解决方案:提升建筑效率与安全的关键
大数据·人工智能·安全
code bean1 小时前
【LangChain】 输出解析器(Output Parsers)完全指南
大数据·人工智能·langchain
薛定猫AI1 小时前
Codex 与 Claude Code 安装配置完整教程(Windows/Mac/Linux)
人工智能
TDengine (老段)1 小时前
TDengine 集群拓扑深度解析 — 节点发现、EP 机制与负载均衡
大数据·数据库·人工智能·重构·负载均衡·时序数据库·tdengine
Kiyra1 小时前
异步任务不用 Kafka 也行:用 Redis Stream 搭一套轻量级 Producer/Consumer 框架
数据库·人工智能·redis·分布式·后端·缓存·kafka
城事漫游Molly1 小时前
定量研究设计清单:问卷、实验与变量操作化怎么做?
大数据·人工智能·算法·ai写作·论文笔记
涤生大数据1 小时前
大数据凉了?速看4月的就业数据新鲜出炉!AI时代岗位不会原地消失,而是岗位的标准会被逐步抬高
大数据·人工智能
七夜zippoe1 小时前
基于 JiuwenClaw AgentTeam 集群模式的年会策划实战:从源码部署到多智能体协作落地
人工智能·agent·openjiuwen·jiuwenclaw·agentteam
Soari1 小时前
科研绘图新纪元:深度拆解 3DCellForge,AI 驱动的交互式 3D 细胞建模神器
人工智能·3d·科研绘图·3dcellforg