陕西旅游必去景点排行榜,10大精华推荐
前面我们已经聊完了怎么加载数据、切分文本、生成向量,再把它们扔进向量数据库,最终把知识库搭建起来了。到这里,一切看起来都挺顺的。但接下来要聊的,才是真正拉开差距的一个环节:检索前处理。说白了,就是在正式动手查之前,先把用户的问题好好“梳洗打扮”一番。这步做得好,系统的效率和准确性才能真正上去。
为什么非得在检索前“动手脚”?
在看具体技术之前,我们先聊一个更根本的问题:为什么 RAG 系统非要搞个检索前处理?
Lewis 用一个很赞的类比来解释这件事。他特别推崇《高效能人士的七个习惯》里的一个核心观点:人的反应并不是简单由刺激驱动的;在“刺激”和“回应”之间,其实存在一个自主选择的空间。这个概念,Viktor Frankl 在《活出生命的意义》里也提过,后来被 Stephen Covey 等人推广,比喻成高级智能体的选择自由。Lewis 管这叫回应前的预处理。
图 5.1:刺激—选择—回应序列,展示外部刺激如何通过自由选择被处理,并产生行为回应。
把这个思路搬到 RAG 系统里,道理是一样的。系统收到用户的原始查询后,完全可以不直接拿去检索,而是先过一道“预设规则”或者“大模型”的处理,引入一个回应前预处理的环节。这个环节就像是给系统腾出了一个检索前的思考空间,用来优化查询、过滤噪音、或者决定下一步到底怎么走。这不仅能提升系统的灵活性和检索效率,更重要的是,输出质量和准确性也能跟着涨一大截。
下面这张工作流图,就清晰地展示了 Query Construction、Query Routing、Query Translation 和 Evaluation 这几个步骤,是如何嵌入到检索前这个预处理阶段的。
检索前处理,到底包含哪些技术?
检索前处理可不是一个单一的技巧,而是一整套方法的集合。它的核心目的,就是在系统真正动手检索之前,先把用户的查询准备得明明白白。主要包括下面三类技术:
Query Construction:这项工作,是把用户口中的自然语言问题,翻译成检索系统能听懂、会执行的查询指令。这里面包括从关系型数据库里读数据,也就是大名鼎鼎的 Text-to-SQL(也叫 Text2SQL);也包括从图数据库里查复杂关系,这就是 Text-to-Cypher。就算是对向量数据库,虽然可以直接拿自然语言去 embedding 检索,但本节也会介绍一种从查询里自动提取 metadata filter 的方法,本质上就是个简化版的 Text-to-SQL。
Query Translation:也常被叫作 Query Rewriting 或 Query Optimization。这个阶段的任务,是对用户输入的问题进行改造,让它变得更好检索。具体的招式很多,比如替换、分解、抽象或者扩展,把一个复杂问题拆成几个子查询,甚至直接生成一个假设性的答案来辅助检索。
Query Routing:这个问题解决的是“该去哪查”。系统需要决定,是走向量数据库的路,还是去查关系型数据库,或者交给图数据库?它主要分为 Logical Routing(逻辑路由)和 Semantic Routing(语义路由)两种。
这三类技术最终的目标只有一个:把用户的问题,精准地转换成系统最擅长处理的形式,再选一条最优的检索路径,让后续的检索和生成环节,变得更准、更全、更高效、更顺畅。
Query Construction:用自然语言,问数据库要答案
Query Construction 是检索前处理里最核心的技术之一。它的本质,就是在人类的自然语言和机器能理解的结构化查询之间,搭起一座桥。不管是给关系型数据库的 SQL,还是给图数据库的 Cypher,甚至只是给向量数据库的 metadata filter,系统都不再只依赖语义相似度,而是能直接生成精准的查询命令。
现代企业的知识库,庞大又复杂。最终目标只有一个:让用户能更轻松地访问和检索信息。这些信息,可能以非结构化的向量形式存在,可能老老实实地躺在传统关系型数据库里,也可能藏在图数据库中。无论是 SQL 还是 Cypher,都有自己特定的语法和逻辑,直接上手写,门槛很高。Query Construction 技术,也就是 Text-to-Query 技术,大大降低了这个门槛。用户只需要用自然语言提问,系统自动就能把问题翻译成对应的数据库查询指令或者向量搜索条件。
下面这三种技术,展示了自然语言问题如何被转换,以适配不同类型的数据源:
Text-to-SQL:把自然语言问题转成 SQL 查询,适用于 MySQL、PostgreSQL 这类关系型数据库。比如,用户问“Find the best-selling product”,系统会自动生成对应的 SQL 语句。
SELECT product_name FROM sales ORDER BY total_sold DESC LIMIT 1
Text-to-Cypher:把自然语言问题翻译成图数据库(比如 Neo4j)的 Cypher 查询语言。比如,用户问“Find the management relationship of employees”,系统会生成类似下面的查询:
MATCH (e:Employee)-[:MANAGES]->(m:Manager) RETURN e, m
Self-Query Retriever:这是 LangChain 提供的一个高级检索前工具。它能在检索之前,根据用户查询自动生成 metadata filter conditions。这样,在生成 embedding 向量并执行检索时,就能通过这些条件进一步过滤结果。比如,对于查询“Find videos with more than 100,000 views”,它可能会生成这样的过滤条件:
"filter": "gt("view_count", "100000")"
Text-to-SQL:把大白话,翻成 SQL
第一种 Query Construction 技术是 Text-to-SQL,当你要查的数据存在关系型数据库里时,它就派上用场了。
在关于 RAG 系统的大多数讨论里,重点往往落在非结构化数据上,比如文本、图片。但现实中的企业,手里既有非结构化文档,也有各种关系型数据库——MySQL、PostgreSQL、SQL Server,还有各种数据仓库。因此,一个成熟的 RAG 系统,必须在做好向量检索的同时,也为结构化数据提供一套好用的方案。把 Text-to-SQL 纳入 RAG 工作流,就能让用户用自然语言表达需求,系统自动执行 SQL 查询,然后把结构化的查询结果和非结构化的知识整合在一起,生成最终的答案。这其实是在构建一个能同时搞定非结构化和结构化数据的统一问答与推理平台,也是未来智能信息系统的重要方向之一。
说起来,Text-to-SQL 可不是 RAG 系统火了才冒出来的新概念。早在大模型和深度学习兴起之前,基于规则模板的 NL2SQL(自然语言到 SQL)系统就已经存在了。只不过,随着大模型和 RAG 的流行,Text-to-SQL 这项能力在统一的 RAG 框架里重新被重视起来,借助新技术又焕发了生机。
Text-to-SQL 本身就是一个专业领域,衡量它的数据集也有很多,比如 WikiSQL,里面全是单表查询的例子,相对简单,适合做初步实验。而耶鲁大学团队发布的 Spider 数据集,则覆盖了多表、多数据库、多关系的复杂查询,是公认最具挑战性的开源数据集之一,也更真实地反映了实际数据库环境的难度。
传统 Text-to-SQL 是怎么干的?
传统的 Text-to-SQL 系统,主要靠基于规则或模板匹配的方法,处理的也是相对固定或受限的领域。它的工作流程一般有下面几个步骤:
自然语言理解(NLU):先对用户的自然语言问题进行分词、命名实体识别、依存句法分析等操作,提取出用户的真实意图。比如,是要“查询销售额”、“返回产品名称”,还是“按某个指标排序”。
数据库 Schema 映射:把数据库里的表名和字段名,映射到自然语言里提到的概念。比如,用户说的“product”可能对应着数据库里 product_name 字段,“sales revenue”可能对应着 sales 或 total_sold 字段。
SQL 结构生成:根据用户的意图和数据库的 schema,生成完整的 SQL 语句,包括 SELECT、FROM、WHERE、GROUP BY、ORDER BY 和 LIMIT 等子句。如果涉及多表查询,还需要生成 JOIN 条件。
SQL 语句验证和执行:系统可能会在生成后做一次简单的语法或逻辑检查,也可能直接把它交给数据库引擎去运行,并返回结果。
传统方法有个很大的问题,就是高度依赖人工配置,尤其是在把自然语言匹配到数据库 schema 的环节上。比如,用户问“Among the sales records, which product has the highest sales volume?”,系统需要判断“sales volume”到底是 sales_table.total_sold,还是 sales_table.sales_amount,甚至要不要去 orders_table 里查数据。这种匹配主要靠命名实体识别,然后通过计算词向量或字典条目之间的相似度来选一个最佳映射。缺少深层的上下文理解,很容易出错,也难应对复杂的库结构或多变的查询方式。
深度学习的时代,又有什么新变化?
深度学习崛起后,基于 Transformer 的神经网络方法逐渐成了 Text-to-SQL 任务的主流。早期的系统通常用 Seq2Seq 或 Encoder-Decoder 架构,把自然语言编码成向量,再直接解码成 SQL token 序列。后来,研究者们更多关注如何用 Embedding 来表示自然语言描述和字段名,并引入注意力机制来优化映射过程。虽然这些方法比传统方式好了不少,但仍然少不了大量的人工特征工程,在处理复杂查询或跨领域场景时,还是不够灵活。
为了进一步破局,研究人员会在特定领域的 Text-to-SQL 数据集(比如 Spider 和 WikiSQL)上微调模型,让模型更好地适应不同数据库的结构特征和查询模式。微调后的模型能更精准地理解领域术语以及不同字段之间的关系,生成更符合实际需求的 SQL 语句,尤其是在处理复杂多表查询或嵌套查询时,效果提升很明显。
大模型时代的 Text-to-SQL:简单直接
ChatGPT 和 GPT-4 这类大语言模型的出现,彻底改变了 Text-to-SQL 的实现方式。通过这些大规模预训练,模型已经自己学到了自然语言和 SQL 之间的复杂映射关系,哪怕不做专门的微调,也能生成高质量的 SQL 语句。跟传统方法那种“分步骤”的复杂流程相比——要手动实现 NLU、Schema 映射、SQL 结构生成等一堆环节——大模型时代的 Few-shot Learning 和 In-context Learning 提供了全新的可能性。只要在 Prompt 里提供数据库 Schema、解释信息和对应的 SQL 示例,模型就能快速适应新的数据库结构和查询类型。
动手试试:搭一个简单的 Text-to-SQL 工作流
假设我们有两张表,一张是景点信息表(scenic_spots),另一张是城市信息表(city_info)。
表 5.1:scenic_spots 表结构,包括景点标识、名称、位置、等级和月游客量。
| Field Name | Data type | Description | Example value |
|---|---|---|---|
scenic_id | INT | 主键,景点唯一标识 | 1 |
scenic_name | VARCHAR | 景点名称 | Jinci |
city | VARCHAR | 所在城市 | Taiyuan City |
level | VARCHAR | 景区等级 | AAAAA level |
monthly_visitors | INT | 当月游客数量 | 50000 |
表 5.2:city_info 表结构,包括城市标识、城市名称和年度旅游收入。
| Field name | Data type | Description | Example value |
|---|---|---|---|
city_id | INT | 主键,城市唯一标识 | 1 |
city_name | VARCHAR | 城市名称 | Taiyuan City |
annual_tourism_income | INT | 年度文旅收入,单位:元 | 200000000 |
我们先创建数据库表,然后插入一些示例数据。代码实现很简单:用 SQLite 连接数据库,创建两个表,再往里塞几条样例数据。
数据库表准备好之后,就可以构建 Text-to-SQL 程序了。核心思路是,把数据库的 Schema 描述作为上下文,告诉大模型。然后,把用户的自然语言问题(比如“Query the AAAAA-rated scenic spots in Taiyuan City and their monthly visitors”)连同 Schema 一起发给大模型,让它生成对应的 SQL 语句。为了保证输出的纯净,我们会在 Prompt 里明确要求模型“只返回 SQL 语句,不要解释和格式标记”。
大模型生成的 SQL 语句通常很精准,比如:
SELECT scenic_name, monthly_visitors
FROM scenic_spots
WHERE city = 'Taiyuan' AND level = 'AAAAA';
接着,我们执行这条 SQL,拿到查询结果。最后,还可以把结果再发给大模型一次,让它把干巴巴的记录转成用户能轻松理解的自然语言描述。比如,输出可以是“According to the query results, among the AAAAA-level scenic spots in Taiyuan, the current month’s number of visitors to Jinci reached fifty thousand.”
从用户体验来看,这个流程意味着,学习者完全不需要懂数据库结构和 SQL 语法,也能快速拿到自己想知道的信息。
结合 Agent、SQL Data Function 和 Tool Calls 的方案
前面的示例虽然好用,但在更复杂的业务场景里,直接生成 SQL 的方式往往会遇到瓶颈。比如,生成的 SQL 不稳定,或者涉及复杂业务逻辑时容易出错。这套方案在实际落地中到底怎么样?Lewis 分享了一个在实践中反复迭代出来的折中方案:利用 Agent 的 Tool Calls 能力,配合预先定义好的 Data-Function 模板,实现从自然语言到数据库操作的稳定过渡。
关键概念:Metadata Extraction 和 Data Functions
“Metadata Extraction”在这里指的是提取数据库的元数据,包括字段信息、表结构和视图描述,并且手动补充一些必要的说明文档。只有被处理过的元数据,才能被 embedding 成向量,用于后续的智能调度。
“Data Functions”则是一套函数模板体系,目的是为常见的查询场景预先定义好通用的数据查询函数。这有点像给传统 ORM 框架(比如 MyBatis、Hibernate)开发模板机制。把复杂的 SQL 逻辑封装进模板里,可以大幅提升代码复用率,也降低出错的风险。
接下来的智能调度过程,就是利用 Function Call 机制来充当处理和重路由的角色。大模型先理解用户的意图,然后从元数据中检索出合适的 Data Function,再执行单步或多步的 SQL 组装与查询。
示例代码展示了如何结合 Agent、SQL-Data-Function 和 Tool Calls。核心步骤如下:
首先,把数据库结构描述注入到 System Prompt 里,包括各个表字段的业务含义和它们之间的关系。然后,用 execute_sql 函数作为基础查询层,统一处理 SQL 的执行、参数化查询和结果格式化。
接着,将常见的查询需求封装成三个核心的 Data Function:query_scenic_spots 支持按城市、景区等级、月游客数进行多条件查询;query_city_info 支持查询城市信息并排序;cross_table_query 封装了两张表的关联查询逻辑。
之后,使用大模型的 Tool Calls 功能,将每个预定义的 SQL-Data-Function 模板定义成一个工具。系统实现了一个两轮对话查询流程:第一轮,将用户问题和数据库 Schema 发给大模型,让它选择合适的查询工具;第二轮,根据选定的工具执行数据库查询,再将查询结果发回大模型,生成自然语言的答案。
这个方案的精妙之处在于,通过预定义的 Data Functions 和严格的参数控制(比如景区等级只能选 AAAA 或 AAAAA,参数化查询防止 SQL 注入),在保证灵活性的同时,有效地管理了查询风险。它既保留了自然语言交互的便捷性,又实现了对结构化数据的精准访问。虽然前期需要花一些精力来创建 SQL-Data-Functions,但换来的是更稳定、更可靠的执行效果。对企业用户来说,系统的稳定性和可控性往往比开发阶段的便利性更重要,所以这种折中方案在实际应用中反响很不错。
Text-to-Cypher:从自然语言到图数据库查询
聊完关系型数据库,RAG 系统里另一个重要的结构化数据源就是图数据库了。
理解图数据库为什么“香”
图数据库是基于图结构来存储数据的。当企业的数据从结构化转向多模态、网络化和复杂关联时,图数据库这种强大的关系建模能力就凸显出来了。它特别适合表示复杂实体关系网络,是处理这类数据关系的首选工具。
图 5.2:概念图通过语义关系连接角色、物品、能力、事件和地点。
图数据库的核心组件
图数据库的三大核心组件:Node(节点)、Edge(关系/边)、Property(属性)。节点代表实体(比如“员工”、“产品”),边描述节点如何连接(比如“管理”、“购买”),属性则为节点或边提供更详细的信息(比如“姓名”、“价格”)。
为什么在 RAG 里要用图数据库?
在 RAG 系统里接入图数据库,最大的价值在于可以对实体关系网络进行高效的查询和推理。比如社交网络分析、企业组织架构梳理、物流网络路线优化等场景,图数据库优势明显。引入它之后,系统不仅能检索孤立的文本或数据,还能基于实体之间的关系进行深入推理,给出更有深度的答案。
举个例子,假设你要帮一家物流公司设计问答系统。用户问:“Huaguoshan Fresh Fruit 的供应链风险是什么?” 最初的 RAG 系统可能只检索到产品的销量信息和一些通用的供应链风险文档,给出一个很敷衍的答案:“Huaguoshan Fresh Fruit 通常在四月至五月进入销售高峰。建议提前通知供应商协调供货...” 这根本不是在回答问题。
但如果引入了 Text-to-Cypher 进行图数据库查询,系统就能利用知识图谱,把产品和供应商连接起来,再关联当地天气情况,给出真正有业务洞察的答案:“Huaguoshan Fresh Fruit 由供应商 A Pantao Yuan(位于上海,天气晴朗)和供应商 B Wuzhuangguan(位于广东,有强暴雨)供货。目前,Wuzhuangguan 可能因暴雨面临供应风险。建议监控其库存,并考虑备用供应商。”
通过应用 Community Detection(社区发现)等图分析算法,RAG 系统甚至能识别出图结构里的“子群体”,找到那些关键关系,而不仅仅是孤立的信息片段。这就是图数据库带来的智能决策支持。
图 5.3:Neo4j 浏览器窗口展示一个复杂图,其中包含紫色节点和带标签连接。
Cypher 基础语法:看一眼就懂
图数据库的代表是 Neo4j,而 Cypher 是它的查询语言,某些方面像 SQL,但专门为处理图数据中的节点、关系和属性而设计。基础语法包括:节点匹配 (n:Label),关系匹配 (a)-[r:RELATIONSHIP]->(b),属性过滤用 WHERE 子句。比如,要查找某个员工的所有直接下属,Cypher 查询是这样的:
MATCH (e:Employee)-[:MANAGES]->(sub:Employee)
WHERE e.name = "Alice"
RETURN sub.name;
Text-to-Cypher 的工作流程
Text-to-Cypher 的整体架构和 Text-to-SQL 很像,同样包含自然语言理解、Schema 映射、查询生成和验证执行这几个步骤。关键区别在于,Cypher 查询往往需要考虑更复杂的多跳关系,而且图数据库的 Schema 通常更灵活,这给查询生成带来了更多的不确定性。
篇幅所限,这里没法深入图数据库和 Neo4j 的细节。直接看一个简单的例子,帮你理解自然语言是怎么转成 Cypher 的。假设有一个图,包含 “Yokai”(妖怪)节点和 “BattleScene”(战斗场景)节点。Yokai 有 name 和 strength 属性,BattleScene 有 location 和 difficulty 属性。两者通过 [:PARTICIPATES_IN] 关系连接。
一个合适的大模型 Prompt,只需要描述清楚图库的 Schema,然后给出用户问题。比如:
You are a Text-to-Cypher expert. Below is the schema of a graph database.
Nodes:
- Yokai: Properties: name (string), strength (integer)
- BattleScene: Properties: location (string), difficulty (string)
Relationships:
- PARTICIPATES_IN: From Yokai to BattleScene
User question: “Find all yokai that participate in scenes with a difficulty of 'hard', and return their strength.”
Please generate a Cypher query.
大模型会很快生成对应的 Cypher 查询:
MATCH (yokai:Yokai)-[:PARTICIPATES_IN]->(scene:BattleScene {difficulty: "困难"})
RETURN yokai.name, yokai.strength;
Text-to-Cypher 最大的优势之一,就是它天然支持多跳查询和推理。当问题涉及多个节点和关系时(比如“Find the department budgets of employees managed by Alice”),它能生成跨多个节点的查询,一层一层地找到答案。知识图谱与问答系统的结合,正在成为一个越来越重要的方向。
Self-Query Retriever:从查询里自动挖出过滤条件
对于向量数据,自然语言查询可以直接被 embedding 并用于检索,不需要构造特定的 SQL 语句。但检索前依然有一些优化技术,Self-Query Retriever 就是其中之一。它会自动从自然语言查询里提取信息,生成 metadata filter conditions,从而让向量数据库能先通过关键词对文档做一轮初步过滤。
举个例子,假设我们在做山西文旅的视频检索。每个视频都有自己的一套 metadata 字段,比如 title、description、view_count、publish_date、length 等。
图 5.4:山西文物数字体验馆入口。
Self-Query Retriever 的工作原理是,先定义好这些 metadata 字段的信息,告诉它每个字段的类型和含义。然后,当用户提出一个如“Find videos with more than 100,000 views”的问题时,它不仅能从语义上理解,还能自动生成一个类似 { "filter": "gt("view_count", "100000")", "limit": 2 } 的结构化查询条件。这意味着,在向量检索的同时,系统还执行了一次精准的结构化过滤。这是一种“向量检索 + 结构化过滤”的混合检索方式,非常值得深入研究。
图 5.5:流程图展示一个用于文档搜索的多层自动化查询系统。
需要提醒一点:虽然 Self-Query Retriever 很强大,但 LangChain 的官方的实现存在一些局限性。比如,它支持某些特定的向量数据库(像 Chroma,但 Faiss 和 Milvus 就不支持)。而且在处理某些查询(比如“Find videos published in January 2024”)时,它的表现并不稳定,甚至可能出错。遇到错误时,可能要深入代码去调试。所以,对于生产环境,更稳妥的做法是参考它的设计思路,自己实现一套更稳健的方案。
Query Translation:把用户的问题“翻译”得更好
前面讲的 Query Construction,是用大模型的能力生成各种结构化查询,去访问不一样的数据源。而接下来要聊的 Query Translation,则主要关注通过 Prompt Engineering 对用户输入进行语义上的重构,让查询变得更适合检索。当用户的查询质量不高时——比如充满噪音、术语含糊不清、或者问得不够全面——就需要借助一些优化技术来改进它。
Query Rewriting:把糟糕的问题,改写成漂亮的问题
Query Rewriting 就是把原始问题重新表述一遍,让它更适合检索系统去理解。
最直接的方法之一,就是用一个自定义的 Prompt Template,让大语言模型来帮忙重写。比如,在游戏客服场景下,用户这种啰嗦的提问:“Well, I just started playing this game, it feels really hard, I can’t get past the Putuo Mountain level no matter what. Which skills should I learn first? Any tips for beginners!” 经过大模型重写后,会变得非常简洁清晰:“For the Putuo Mountain level, which skills should beginners prioritize learning?” 这样既省了 token,又能更高效地检索到目标信息。
LangChain 也提供了一个开箱即用的工具 RePhraseQueryRetriever,专门干这个事。它的本质就是调用一个大模型和内嵌的 Prompt,把用户的查询变得更简洁、更精准。不过,它内部的默认 Prompt 是英文的,如果希望它输出中文,可以通过参数自定义 Prompt。
Query Decomposition:把一个大问题,拆成几个小问题
Query Rewriting 的目标是让查询更规范,而 Query Decomposition 则更进一步,它会把一个查询拆解成多个子问题,从不同视角去探索问题的各个方面,让检索回来的内容更丰富。
图 5.7:流程图展示一个 query 被路由到三个 prompts,每个 prompt 都指向 RAG apps 中的大模型。
LangChain 的 MultiQueryRetriever 就是干这个的。它会调用大模型,从不同视角生成多个查询版本。比如,针对“在普陀山关卡,新手应该优先学哪些技能?”这个问题,它可能会自动生成这样几个子问题:
- “What is the difficulty level of this game? How many levels are there? Any tips for the Putuo Mountain stage?”
- “For beginners, which skills should be learned first to better tackle the challenges in the game?”
- “Are there any suggestions or tips to help me successfully pass the Putuo Mountain stage?”
然后,Retriever 会对生成的每个子查询都去向量数据库里找一下,最后把所有结果去重合并,形成一个更丰富的结果集。你还可以自定义 Prompt Template,让它生成更多样化的查询,比如从技能选择、战斗策略、装备搭配等不同角度来提问。
Query Clarification:把模糊的问题,问得更清楚
在论文《Tree of Clarifications: Using Retrieval-Augmented Large Language Models to Answer Ambiguous Questions》里,研究者提出了一个叫 Tree of Clarifications(ToC)的框架。它的思路是,当用户的问题很模糊时(比如“Which country has won the most medals in Olympic history?”),系统会通过递归地构建一个“问题澄清树”,不断生成一系列澄清问题来细化用户的意图。比如,是问哪一届奥运会?哪个运动项目的奖牌?这样一步步把问题问清楚,拿到更多上下文信息,才能给出准确的回答。
图 5.8:使用澄清问题树回答模糊问题的工作流,其中原始 query 被分解为更具体的子问题,不相关分支被剪枝,相关文本段落被检索,最终生成答案。
代码实现中,这套框架通常通过定义 Clarification Node 的数据结构、问题模板、知识库,以及一个识别主要方面和角色的函数来构建。最终生成的树形结构,可以把模糊的用户问题层层拆解成更具体、更清晰的子问题,从而提升最终的检索和回答质量。
使用 HyDE 生成假设文档,进行 Query Expansion
HyDE(Hypothetical Document Embeddings)技术,源自卡内基梅隆大学的一篇论文《Precise Zero-Shot Dense Retrieval without Relevance Labels》。它的核心思想非常巧妙:不是为了查询直接去检索,而是先让大模型为这个查询“想象”出一个假设性的答案文档。然后,对这个“想象出来”的文档做 embedding,用这个 embedding 作为最终的查询表示。
图 5.10:图示展示问题和文档如何作为向量嵌入到 3D 空间中进行比较。
这种方法的亮点在于,它不直接学习相关性,而是通过生成一个假设答案来构建与查询对齐的 embedding。这种新范式为信息检索提供了一个全新的视角。
HyDE 的实现思路很直接,只需要一个基础的 embedding 模型和一个用于生成假设文档的大模型。具体流程是:先定义一个文档生成模板,把用户问题放进去让大模型生成一段假设内容;然后用这个生成的文档去检索;最后,把检索到的文档和原始问题一起,再发给大模型生成最终答案。
从实际效果来看,HyDE 的上下文感知能力很强,能捕捉到查询的深层意图。通过自定义 Prompt Template,还能让它适应不同领域的需求。但要注意,生成假设性文档需要额外的计算资源和时间,而且效果高度依赖所用大模型的质量。在实际应用时,需要权衡好它带来的性能收益和额外的成本。
Query Routing:把问题送到该去的地方
构造或翻译完查询之后,系统接下来要决定一个更根本的问题:这个查询,到底应该去哪儿?
在复杂的 RAG 系统里,向量数据库可能有多种形式,或者同时使用了多个向量数据库,甚至不同类型的数据存储着不同格式的数据。当我们的复杂查询被拆解成子问题后,就需要有机制把它们高效地路由到正确的数据源。这就是 Query Routing 要解决的问题。
而且,不只是因为数据源不同,处理不同类型问题的方式也可能完全不同。比如,用户问的是游戏内容,还是向客服投诉,后续的处理方法可能千差万别。这个过程,本质上就是意图识别(Intent Recognition)。随着系统规模扩大,使用路由机制来分发用户输入,可以确保不同数据源或处理流程都能无缝地集成。
Logical Routing:决定该走哪条路
Logical Routing 负责决定查询应该被发送到哪里。系统从用户输入的问题开始,先通过大模型对它进行分析和语义理解,基于问题特征对其分类,生成结构化的输出,从而指导 Retriever 的选择。
图 5.12:图示展示问题经模型分类后,查询图数据库或向量数据库的过程。
举个例子,问题先被分类,判断它和 Graph Database 相关,还是和 Vector Database 相关。如果涉及复杂实体关系(比如查询组织结构),系统就选图数据库;如果需要语义相似度检索(比如找和某篇文档意思相近的内容),系统就选向量数据库。再比如,如果我们为 Python、JS、Golang 分别建了三个向量数据库,当用户的问题被分类为“Python”时,系统就自动路由到 python_docs 数据库。
LangChain 提供了通过结构化输出路由的示例。核心是定义一个 RouteQuery 模型,用 Literal 类型限定数据源选择,然后用大模型判断当前问题属于哪一类。
Semantic Routing:选对 Prompt,效果翻倍
Logical Routing 选择的是数据源或检索方法,而 Semantic Routing 则是根据用户问题的“语义”,来选择合适的 Prompt Template。
图 5.13:图示展示问题和 prompts 如何被嵌入、选择,并最终导向答案。
实现上也很直观:先定义两个不同的 Prompt Template,比如一个侧重战斗技能,一个侧重故事剧情。然后,把这两个模板 embedding 成向量。当收到用户问题“How does the monkey defeat his enemies?”时,系统也把它 embedding,然后计算这个问题向量和两个 Prompt 向量之间的余弦相似度。选中最相似的 Prompt Template 后,再去调用大模型,就能得到更贴切、更专业的回答。
总结
检索前处理阶段的各种技术,虽然不是 RAG 系统不可或缺的组成部分,但它们为优化系统性能、设计更复杂的系统提供了极其重要的启发。
把结构化数据整合进 RAG 系统,是一个关键挑战。在真实业务里,只依赖非结构化数据的场景其实相当有限。大部分时候,你都需要连接已有的关系型数据库,或者查询 Excel、CSV 文件。核心思路是,保持数据原有的结构清晰性,不要为了塞进向量数据库而破坏它。直接对结构化数据进行 embedding 并检索,效果往往不好,很容易丢失数据,或者难以处理时间序列。Text-to-Query 技术能帮大模型理解问题中的实体、意图和条件,再映射到数据库的表结构和查询逻辑,从而在 RAG 工作流中实现有效的结构化数据查询。
正如 Lewis 强调的,理解底层的原理才是关键。这也是为什么 Text-to-SQL 的需求如此突出——涉及内部数据库的操作,最终还是得靠 SQL 解决,单靠向量检索搞不定。不过,Text-to-SQL 等技术在简单场景里表现不错,一旦遇到复杂逻辑,稳定性就容易出问题。一个比较稳妥的折中方案是:先提取数据库的 metadata,包括字段信息、表结构和视图描述,并补充必要的人工解释;然后建立通用和具体业务相关的 Data Functions,预定义复杂查询逻辑;最后,结合 Agent 的 Tool Calls 功能,让大模型在理解意图后,动态地组装 SQL 并执行查询。
值得特别注意的是 Self-Query Retriever 这种方法。它的设计思路很巧妙,能从查询中自动生成 metadata filter conditions,在检索之前就实现一次精确的结构化过滤。这建立了一种双重过滤机制:一方面用向量存储做语义搜索;另一方面基于 metadata 条件(比如“观看次数大于 100000”)做精确过滤。这种结合,体现了混合检索(Hybrid Retrieval)的精髓。
最后,Query Routing 是复杂系统中实现混合查询的基础。如果系统里有多个数据源,就应该优先使用路由或意图识别来筛选目标数据源,然后定位到合适的路径。例如,它可以判断一个问题是否需要走 Text-to-SQL 或 Text-to-Cypher 去查结构化数据库。这些技术,为跨结构化和非结构化数据源的智能混合查询场景,打下了坚实的基础。









