检索优化(一):混合检索与查询构建
在前面的章节中我们学会了如何构建向量索引、如何将文本和图像编码为向量。但一个真正的RAG系统光能“找到相似的”是远远不够的。现实世界的检索需求往往更加复杂既要理解语义又要精确匹配关键词既要搜文本又要按元数据过滤既要查非结构化数据又要联结构化数据库。本节我们将深入两大核心优化技术混合检索解决“怎么搜更准”和查询构建解决“怎么搜更复杂”让你的RAG系统从“能搜”进化到“搜得准、搜得全、搜得灵活”。第一部分混合检索 —— 当“精确”遇见“语义”一、为什么需要混合检索想象一个场景你在公司知识库里搜索“MacBook Pro 16寸 2023款 评测”。如果只用稠密向量检索语义搜索系统可能理解“苹果笔记本电脑”的语义召回一堆MacBook Air或MacBook Pro 14寸的内容但可能漏掉精确匹配“16寸”和“2023款”的文档。如果只用稀疏向量检索关键词搜索系统能精确匹配“MacBook”、“16寸”、“2023款”这些词但可能搜不到那些用“苹果笔记本”、“旗舰机型”等变体表达的相关内容。这就是经典的信息检索困境“精确”与“语义”难以兼得。混合检索Hybrid Search正是为了解决这个困境而生。它同时利用稀疏向量的关键词精确匹配能力和密集向量的语义理解能力取两者之长补各自之短。二、稀疏向量 vs 密集向量为了更好地理解混合检索首先需要厘清两种向量的本质区别。2.1 稀疏向量Sparse Vector—— 关键词的“精确账本”稀疏向量也常被称为“词法向量”是基于词频统计的传统信息检索方法的数学表示。核心特点维度极高与词汇表大小相当如30万维极度稀疏绝大多数元素为零只有文档中实际出现的词才有非零值可解释性强每个维度都对应一个具体的词非零值代表该词在文档中的重要性经典算法BM25BM25是目前最广泛使用的稀疏向量排序算法其核心公式如下其中通俗理解BM25一个词在文档里出现得越多词频高、在整个文档集合里出现得越少IDF高、文档本身越短长度归一化则这个词对这篇文档的“贡献分”就越高。稀疏向量的存储格式由于稀疏向量绝大多数元素为零实际存储时只记录非零值。常见的两种格式格式表示方式示例8维向量[0,0,0,5,0,0,0,9]字典/键值对{索引: 值}{3: 5, 7: 9}坐标列表COO(维度, [索引列表], [值列表])(8, [3, 7], [5, 9])实例假设在一个包含5万个词的词汇表中“西红柿”在第88位“炒”在第666位“蛋”在第999位它们的BM25权重分别是1.2、0.8、1.5。那么“西红柿炒蛋”这个文档的稀疏表示字典格式就是json { 88: 1.2, // 西红柿的权重 666: 0.8, // 炒的权重 999: 1.5 // 蛋的权重 }优点✅ 可解释性极强每个维度都代表一个确切的词✅ 无需训练开箱即用✅ 精确匹配能力强对专业术语和特定名词效果好缺点❌ 无法理解语义“汽车”和“轿车”被当作完全不同的词❌ 存在“词汇鸿沟”同义词、近义词无法关联❌ 对拼写错误和变体敏感2.2 密集向量Dense Vector—— 语义的“抽象画”密集向量是通过深度学习模型学习到的数据如文本、图像的低维、稠密的浮点数表示。核心特点维度固定且较低通常为128~1024维数值稠密几乎所有维度都有非零值语义连续在语义空间中向量之间的距离和方向代表了概念之间的关系经典案例Word2Vec 的著名发现text vector(国王) - vector(男人) vector(女人) ≈ vector(女王)这个等式表明模型在向量空间中学会了“性别”和“皇室”这两个抽象维度——这是稀疏向量永远无法做到的。密集向量的存储格式密集向量所有维度都有值因此直接用数组表示# 这是一个1024维的浮点数向量 # 每个维度没有直接的、可解释的含义 [0.89, -0.12, 0.77, 0.34, -0.56, 0.91, ... , -0.45]实例预训练模型在读取“西红柿炒蛋”后输出的密集向量在语义空间中可能与“番茄鸡蛋面”、“洋葱炒鸡蛋”等菜肴的向量非常接近——因为模型理解了它们共享“鸡蛋类菜肴”、“家常菜”等核心概念。当我们搜索“蛋白质丰富的家常菜”时即使查询中没有出现任何原文关键词密集向量也很有可能成功匹配到这份菜谱。优点✅ 理解同义词、近义词和上下文关系✅ 泛化能力强能处理未曾见过的表达方式✅ 在语义搜索任务中表现卓越缺点❌ 可解释性差每个维度通常没有具体的物理意义❌ 需要大量数据和算力进行模型训练❌ 对未登录词OOV的处理仍然有挑战什么是OOV未登录词OOVOut-of-Vocabulary指在模型训练时没有出现在词汇表中但在实际使用时遇到的新词汇。例如2018年之前训练的模型词汇表中没有ChatGPT这个词那么在实际应用中遇到它时就是OOV。传统稀疏向量方法如BM25对OOV词汇会完全忽略而现代密集向量方法通过子词分割如BPE、WordPiece可以部分解决OOV问题。2.3 直观对比维度稀疏向量BM25为代表密集向量BERT为代表维度极高~30万维较低~1024维数值分布极度稀疏绝大多数为0稠密几乎所有维度都有值可解释性⭐⭐⭐⭐⭐ 每个维度对应一个词⭐☆☆☆☆ 维度无明确含义精确匹配⭐⭐⭐⭐⭐ 专有名词完美命中⭐⭐☆☆☆ 可能模糊匹配语义理解⭐☆☆☆☆ 不识同义词⭐⭐⭐⭐⭐ 理解上下文和概念训练需求无需训练需要大量数据和算力OOV处理完全忽略通过子词部分解决三、混合检索的原理与融合方法混合检索通过并行执行稀疏检索和密集检索然后将两组异构的结果集融合成一个统一的排序列表。3.1 核心思路text 用户查询: MacBook Pro 16寸 2023款 评测 ↓ ┌───────────┴───────────┐ ↓ ↓ 稀疏检索 (BM25) 密集检索 (Dense) 精确匹配关键词 语义理解概念 MacBook 16寸 苹果笔记本 2023款 评测 旗舰机型 开箱 ↓ ↓ 结果集 A 结果集 B (关键词高度匹配) (语义高度相关) └───────────┬───────────┘ ↓ 结果融合 (RRF/加权) ↓ 最终排序结果 (兼顾精确与语义)3.2 融合策略一倒数排序融合RRFRRFReciprocal Rank Fusion是当前最流行的融合算法。它的核心思想是不关心不同检索系统的原始得分只关心每个文档在各自结果集中的排名。一个文档在不同检索系统中的排名越靠前其最终得分就越高。计分公式其中为何选择RRF优点说明无需归一化不需要处理不同检索系统的得分尺度问题抗异常值排名比原始得分更稳定不受极端值影响简单高效计算成本极低适合实时检索3.3 融合策略二加权线性组合这种方法需要先将不同检索系统的得分进行归一化例如统一到0-1区间然后通过一个权重参数 α 进行线性组合。通过调整 α 的值可以灵活地控制语义相似性与关键词匹配在最终排序中的贡献比例α 值倾向适用场景α0.5侧重语义智能问答、内容推荐α0.5侧重关键词产品搜索、法律文档检索α0.5平衡通用场景3.4 混合检索的优势与局限✅ 优势⚠️ 局限召回率与准确率双高能同时捕获关键词和语义显著优于单一检索计算资源消耗大需要同时维护和查询两套索引灵活性强可通过融合策略和权重调整适应不同业务场景参数调试复杂融合权重等超参数需要反复实验调优容错性好关键词检索可部分弥补向量模型对拼写错误或罕见词的敏感性可解释性仍是挑战融合后的结果排序理由难以直观分析鲁棒性强当某一路检索效果不佳时另一路仍能提供有效结果-第二部分查询构建 —— 让LLM帮你“翻译”查询在前面的章节中我们探讨了如何通过向量嵌入和相似度搜索从非结构化数据中检索信息。然而在实际应用中我们常常需要处理更加复杂和多样化的数据——包括结构化数据如SQL数据库、半结构化数据如带有元数据的文档以及图数据。用户的查询也可能不仅仅是简单的语义匹配而是包含复杂的过滤条件、聚合操作或关系查询。查询构建Query Construction正是应对这一挑战的关键技术。它利用大语言模型LLM的强大理解能力将用户的自然语言查询“翻译”成针对特定数据源的结构化查询语言或带有过滤条件的请求。这使得RAG系统能够无缝地连接和利用各种类型的数据极大地扩展了其应用场景。一、文本到元数据过滤器在构建向量索引时我们常常会为文档块Chunks附加元数据Metadata例如文档来源、发布日期、作者、章节、类别等。这些元数据为我们提供了在语义搜索之外进行精确过滤的可能。自查询检索器Self-Query Retriever是LangChain中实现这一功能的核心组件。它的工作流程如下text 用户查询: 关于2022年发布的机器学习的论文 ↓ ┌──────────┴──────────┐ ↓ ↓ 查询字符串 元数据过滤器 机器学习的论文 → year 2022 ↓ ↓ └──────────┬──────────┘ ↓ 向量数据库语义搜索 元数据过滤 ↓ 精确结果1.1 完整代码实战B站视频检索以下示例以B站视频为例演示如何使用SelfQueryRetriever实现带元数据过滤的语义检索。步骤1加载数据并提取元数据import os import logging from langchain_deepseek import ChatDeepSeek from langchain_community.document_loaders import BiliBiliLoader from langchain.chains.query_constructor.base import AttributeInfo from langchain.retrievers.self_query.base import SelfQueryRetriever from langchain_community.vectorstores import Chroma from langchain_huggingface import HuggingFaceEmbeddings logging.basicConfig(levellogging.INFO) # 1. 初始化视频数据 video_urls [ https://www.bilibili.com/video/BV1Bo4y1A7FU, https://www.bilibili.com/video/BV1ug4y157xA, https://www.bilibili.com/video/BV1yh411V7ge, ] bili [] loader BiliBiliLoader(video_urlsvideo_urls) docs loader.load() for doc in docs: original doc.metadata # 提取并扁平化元数据 metadata { title: original.get(title, 未知标题), author: original.get(owner, {}).get(name, 未知作者), source: original.get(bvid, 未知ID), view_count: original.get(stat, {}).get(view, 0), length: original.get(duration, 0), # 视频时长秒 } doc.metadata metadata bili.append(doc)注意BiliBiliLoader返回的原始元数据结构较为复杂如作者和观看数信息嵌套在其他字典中因此我们手动提取需要的字段构建一个干净、扁平化的新metadata字典。这确保了后续的自查询检索器能够直接、可靠地访问这些字段。步骤2配置元数据字段信息并创建检索器# 2. 配置元数据字段信息 metadata_field_info [ AttributeInfo( nametitle, description视频标题字符串, typestring, ), AttributeInfo( nameauthor, description视频作者字符串, typestring, ), AttributeInfo( nameview_count, description视频观看次数整数, typeinteger, ), AttributeInfo( namelength, description视频长度以秒为单位的整数, typeinteger, ) ] # 3. 创建向量存储 embed_model HuggingFaceEmbeddings(model_nameBAAI/bge-small-zh-v1.5) vectorstore Chroma.from_documents(bili, embed_model) # 4. 创建自查询检索器 llm ChatDeepSeek( modeldeepseek-chat, temperature0, # 设为0确保输出确定性和可复现性 api_keyos.getenv(DEEPSEEK_API_KEY) ) retriever SelfQueryRetriever.from_llm( llmllm, vectorstorevectorstore, document_contents记录视频标题、作者、观看次数等信息的视频元数据, metadata_field_infometadata_field_info, enable_limitTrue, verboseTrue )关于temperature0这个参数控制模型输出的随机性。值越高如0.8输出越随机、越有创意值越低输出越确定、越集中。在自查询这种需要精确地将自然语言转换为结构化查询的场景下temperature0可以确保转换结果的稳定和可复现。步骤3执行查询# 5. 执行查询示例 queries [ 时间最短的视频, 时长大于600秒的视频 ] for query in queries: print(f\n--- 查询: {query} ---) results retriever.invoke(query) if results: for doc in results: title doc.metadata.get(title, 未知标题) author doc.metadata.get(author, 未知作者) view_count doc.metadata.get(view_count, 未知) length doc.metadata.get(length, 未知) print(f标题: {title}) print(f作者: {author}) print(f观看次数: {view_count}) print(f时长: {length}秒) print( * 50)输出结果text --- 查询: 时长大于600秒的视频 --- INFO:httpx:HTTP Request: POST https://api.deepseek.com/v1/chat/completions HTTP/1.1 200 OK INFO:langchain.retrievers.self_query.base:Generated Query: query filterComparison(comparatorComparator.GT: gt, attributelength, value600) limitNone 标题: 《吴恩达 x OpenAI Prompt课程》【专业翻译配套代码笔记】03.Prompt如何迭代优化 作者: 二次元的Datawhale 观看次数: 7090 时长: 806秒 标题: 《吴恩达 x OpenAI Prompt课程》【专业翻译配套代码笔记】02.Prompt 的构建原则 作者: 二次元的Datawhale 观看次数: 18788 时长: 1063秒1.2 核心机制解析SelfQueryRetriever.from_llm方法在底层执行了两个核心操作加载查询构造器利用传入的llm、document_contents和metadata_field_info创建一个专门的“查询构造链”。这个链的核心职责是将用户的自然语言查询转换为一个通用的、结构化的查询对象。获取内置翻译器检查使用的向量数据库这里是 Chroma并为其匹配一个内置的“翻译器”。这个翻译器负责将上一步生成的通用查询对象翻译成 Chroma 数据库能够理解和执行的过滤语法。最终retriever.invoke向向量数据库发起一个同时包含语义搜索和精确元数据过滤的复合查询返回最相关的结果。1.3 ⚠️ 思考题为什么“时间最短的视频”查询结果是错误的仔细观察输出我们会发现一个令人困惑的现象查询“时间最短的视频”时返回的结果并不是真正的“最短”视频而只是系统返回的第一个结果1063秒。但实际上如果数据集中有更短的视频这个结果显然是不正确的。原因分析SelfQueryRetriever目前不支持排序操作。当用户查询包含“最短”、“最长”、“最新”等排序意图时LLM 会将其解析为limit1即只返回一个结果但不会添加order_by排序条件。换句话说用户期望ORDER BY length ASC LIMIT 1按时长升序取第一条实际执行LIMIT 1直接取第一条不排序因此返回的结果只是向量检索中最相关的那一条而非“时长最短”的那一条。解决方案方案说明适用场景使用LLM进行查询意图识别在自查询之前先用LLM识别用户是否有“排序”意图若有则修改查询逻辑需要灵活处理多种排序场景启用enable_limit并配合排序字段在元数据配置中明确标记可排序字段让LLM生成排序条件排序需求较为固定和明确检索后重新排序先检索出一批候选结果然后在应用层按length字段排序取最短简单直接适合数据量不大的场景使用原生数据库排序对于明确需要排序的查询如“最短”直接走结构化查询而非向量检索排序需求明确且数据结构化程度高在实际生产中建议结合业务场景选择合适的方案。对于本例中的“时间最短”需求最直接的解决方案是检索后重新排序——先召回Top-K结果再在应用层按length字段排序取最小值。二、文本到Cypher连接图数据库除了处理扁平化的元数据查询构建技术还能应用于更复杂的数据结构如图数据库。2.1 什么是CypherCypher 是图数据库如 Neo4j中最常用的查询语言其地位类似于 SQL 之于关系数据库。它采用一种直观的方式来匹配图中的模式和关系。例如下面的 Cypher 语句描述了一个人和一个国家以及他们之间的“居住在”关系cypher(:Person {name:Tomaz})-[:LIVES_IN]-(:Country {name:Slovenia})2.2 “文本到Cypher”的原理与“文本到元数据过滤器”类似“文本到Cypher”技术利用大语言模型LLM将用户的自然语言问题直接翻译成一句精准的 Cypher 查询语句。工作流程text 用户提问: Tomaz住在哪个国家 ↓ LLM根据图谱模式翻译 ↓ Cypher查询: MATCH (:Person {name:Tomaz})-[:LIVES_IN]-(c:Country) RETURN c.name ↓ 在图数据库上执行 ↓ 返回结果: Slovenia ↓ (可选) LLM生成自然语言答案: Tomaz居住在斯洛文尼亚。LangChain 提供了GraphCypherQAChain工具链来支持这一流程。由于生成有效的 Cypher 查询需要准确理解图模式节点类型、关系类型、属性等通常需要使用性能较强的 LLM 来确保转换的准确性。核心优势用户可以用最自然的方式与高度结构化的图数据进行交互极大地降低了数据查询的门槛。第三部分总结核心技术一句话概括主要应用稀疏向量BM25精确的关键词匹配专有名词检索、法律条文查找密集向量Dense Embedding泛化的语义理解语义搜索、同义词处理混合检索稀疏密集并行融合结果兼顾精确与语义的通用检索RRF融合基于排名融合无需归一化多路检索结果合并加权线性融合基于得分加权可调权重需要灵活控制倾向的场景自查询检索器自然语言→语义搜索元数据过滤带过滤条件的文档检索文本到Cypher自然语言→图数据库查询知识图谱、关系查询如果本文对你有帮助欢迎点赞、收藏、转发有任何疑问欢迎在评论区交流讨论