在构建搜索或者 RAG(检索增强生成)系统时,咱们是不是经常遇到下面这种让人挠头的场景?
知识库里明明有一份《企业差旅报销制度 V3.0.pdf》,里面白纸黑字写着打车费用的报销上限。可用户偏偏只输入“打车钱”三个字来搜索。结果呢?要么搜不到,要么搜出来的结果相关度极低,根本对不上号。
这背后的根本原因在于,用户的查询语句和文档内容在语义空间里没有产生有效重叠。用户的输入往往是口语化、碎片化的短句;而文档则是正式、结构化的长文本。这就造成了一道“意图鸿沟”。
查询扩展(Query Expansion)要解决的,就是这道鸿沟。它的核心逻辑很聪明:咱们别傻傻地直接去搜用户输入的那几个字,而是要想办法去搜“用户心里真正想找的那个东西”。下面,咱们就来聊聊三种主流且高效的查询扩展技术方案,看看它们是怎么各显神通的。
一、方案 A:LLM 多路改写 —— 让模型当“翻译官”
这是最直观、最容易想到的方法。既然用户“词不达意”,那就利用大语言模型(LLM)的能力,把用户那个简短的短语,扩充、改写成多个不同角度、不同表达方式的完整句子,然后并行地进行搜索。
1. 原理
用户输入的查询往往是模糊、不完整的。这个方案的思路是,通过设计特定的 Prompt,让 LLM 充当一个“翻译官”或“扩展器”。它的任务是把一个模糊的查询,“翻译”成 3-5 个风格各异但意图明确的精确查询。
2. 典型流程
- 用户输入:例如,“连不上网”。
- LLM 改写指令:Prompt 可以这样设计:“请将上述用户问题,改写成 3 个不同维度的、更专业的搜索查询,可以包含技术术语、同义词变体、具体场景描述等。”
- 生成结果:LLM 可能会输出:
- “网络连接故障诊断与排除方法”
- “无法连接互联网的可能原因及解决方案”
- “Wi-Fi 或以太网连接失败如何处理”
- 执行检索:将原始查询“连不上网”和这 3 个改写后的句子,一共 4 个查询,分别进行向量检索(Vector Search)。
- 结果聚合:收集所有检索返回的文档,然后进行去重(Deduplication) 和重排序(Rerank),最终呈现给用户或交给后续的生成环节。
3. 优势与劣势
- 优点:能极大地提高召回率(Recall)。只要用户的意图落在任何一个改写后的查询范围内,相关文档就能被找出来,有效解决了因关键词不匹配导致的“漏搜”问题。
- 缺点:增加了系统延迟(Latency) 和成本。因为需要额外调用一次 LLM API 来生成改写查询,并且后续要执行多次向量检索,计算开销变大。
当你的系统主要依赖 LLM 来处理复杂语义时,多路改写是一个平衡效果与复杂度的不错起点。
二、方案 B:HyDE(假设文档嵌入)—— “以假乱真”的杀手锏
HyDE(Hypothetical Document Embeddings)是目前向量检索领域公认的一个“大招”。它的思路非常巧妙,是逆向思维的典范:不去扩充问题,而是直接“伪造”一个答案。
1. 要解决的痛点
一个被观察到的现象是:像 BERT 这类经过对比学习训练的向量模型,在计算“问题”和“答案”的相似度时,表现可能一般;但在计算“答案”和“答案”的相似度时,表现却出奇地好。也就是说,模型更擅长识别两段文本是不是在说同一件事(都是答案),而不是识别一个问题和一个答案是否匹配。
2. 核心原理
在正式检索之前,先让 LLM 针对用户的查询,“脑补”一篇答案文档。这篇文档是 LLM “幻想”出来的,内容可能不准确,甚至包含事实错误(Hallucination)。但关键在于,它幻想出来的这篇文档,其句式结构、语气风格、核心关键词,都与真实知识库里的文档高度相似。
然后,我们不去编码用户的问题,而是编码这篇“伪造”的答案文档,用它的向量去知识库里搜索。由于“假答案”和“真答案”在语义和风格上高度接近,检索精准度(Precision)往往会大幅提升。
3. 工作流程
- 用户输入:例如,“如何退货?”
- LLM 生成假设文档:Prompt 可以是:“请针对‘如何退货?’这个问题,生成一段假设性的、详细的答案文档。” LLM 可能会生成:
“如果您需要办理退货,请首先登录您的个人中心,找到对应的订单详情页面,点击‘申请售后’按钮。请注意,商品需满足七天无理由退货条件,且包装完好……(注:这是LLM根据常识‘瞎编’的,但其中包含了‘个人中心’、‘订单详情’、‘申请售后’、‘七天无理由’等关键语义和词汇)”
- 编码:将这段“瞎编的答案”文本,通过嵌入模型(Embedding Model)转化为向量。
- 检索:用这个“答案向量”去向量数据库里进行相似度搜索。
- 结果:因为“瞎编的答案”和知识库里真实的《退货政策文档》在语义和行文上非常接近,所以能精准地将其检索出来。
4. 适用场景
这种方法特别适合跨域检索,或者用户问题极其简短、模糊的场景。当用户的查询与文档库的“语言体系”完全不同时,HyDE 能架起一座高效的桥梁。
三、方案 C:思维链/子问题分解 —— 破解复杂查询
这个方案专门用来对付那些复杂、包含多步逻辑(多跳)的模糊问题。
1. 典型场景
用户问:“对比一下 iPhone 15 和华为 Mate 60 的屏幕参数。”
如果直接用这句话去检索,很可能只能搜到一些泛泛而谈的对比新闻或评测视频,而找不到具体、精确的屏幕参数规格表。
2. 原理
利用 LLM 的推理能力,将复杂的母问题(Parent Query)拆解成若干个独立的、原子化的子问题(Child Queries)。每个子问题都更简单、指向性更强,更容易被精确检索。
3. 流程
- 拆解:
- 子问题 1: “iPhone 15 的屏幕参数有哪些?例如尺寸、分辨率、刷新率、亮度等。”
- 子问题 2: “华为 Mate 60 的屏幕参数有哪些?”
- 并行检索:分别用这两个子问题去向量数据库或全文搜索引擎中检索,获取最精准的数据片段(可能是规格表的一行,或技术文档的一段)。
- 汇总合成:将检索到的关于 iPhone 15 和华为 Mate 60 屏幕参数的信息片段,一并喂给 LLM,让它来组织语言,生成最终的对比性回答。
四、实战技术选型:对症下药,组合出拳
并不是所有场景都需要祭出最复杂的 HyDE。在实际工程中,我们需要根据用户查询的特征来选择最合适(性价比最高)的技术。下表是一个简单的选型参考:
| 用户查询特征 |
推荐技术 |
资源消耗 |
预期效果提升 |
| 拼写错误/近义词/词干变化 |
传统 NLP 扩展(如:同义词库、词干还原、拼写校正) |
低(无需调用LLM) |
中等 |
| 短语简短/口语化/意图单一 |
LLM 多路改写 |
中等(1次LLM调用+多次检索) |
高 |
| 抽象概念/零样本/跨域查询 |
HyDE(假设文档嵌入) |
高(LLM生成长文本较慢) |
极高 |
| 逻辑复杂/多跳/对比类查询 |
子问题分解 |
高(多次检索与合成) |
极高 |
通常,一个健壮的工业级 RAG 系统会采用混合策略。例如,先判断查询复杂度,简单查询走同义词扩展,中等复杂度走多路改写,高复杂度则启用 HyDE 或子问题分解。
五、核心代码逻辑示例(Python 伪代码)
如果你在使用 LangChain 或 LlamaIndex 这类框架,查询扩展的逻辑大致可以这样实现(以下是概念性伪代码):
def query_expansion_search(user_query):
# 1. 调用 LLM 生成 N 个相关的扩展问题
prompt = f"将 '{user_query}' 改写为3个更专业、更详细的搜索查询。"
augmented_queries = llm.generate([prompt])
# 加上原始查询,组成查询集合
all_queries = [user_query] + augmented_queries
search_results = []
# 2. 并行执行向量检索
for q in all_queries:
vector = embedding_model.encode(q) # 将查询编码为向量
results = vector_db.search(vector, k=5) # 检索最相似的5个文档
search_results.extend(results)
# 3. 结果去重 (基于文档ID或内容哈希)
unique_results = deduplicate(search_results)
# 4. (可选) 使用重排序器(Reranker)进行精排
# 例如使用 Cross-Encoder 模型,综合考虑原始查询与每个文档的相关性
final_results = reranker.rank(user_query, unique_results)
return final_results
总结
用户查询太模糊,本质是“用户意图”和“系统语料”之间的语义鸿沟。这道鸿沟有不同的表现形式,也需要不同的“桥梁”来跨越:
- 如果是词汇鸿沟(用户不懂专业术语),用 LLM 多路改写来丰富查询表达。
- 如果是语义鸿沟(“问题”和“答案”文本形态差异太大),用 HyDE 来“伪造”答案,实现同质匹配。
- 如果是逻辑鸿沟(问题复杂,包含多个子意图),用子问题分解来化繁为简。
我们不可能指望所有用户都去学习“提示词工程”。查询扩展,就是系统在后台主动为用户完成提示词优化的智能过程。 它让搜索和 RAG 系统变得更聪明、更贴心,也更强大。
希望这些方案能为你优化搜索系统带来一些启发。在实际应用中,如何组合、调试这些策略,并将其无缝集成到你的架构中,是更值得深入探讨的工程问题。欢迎在 云栈社区 分享你的实战经验或遇到的挑战。