RAG系统中的查询重写:我是如何把响应速度提升40倍的
一个真实的电商搜索场景重构故事
写在前面
最近在做一个电商搜索系统的优化,遇到了一个很有意思的问题:用户输入"不上火的奶粉",系统应该怎么理解?是搜索"温和配方"?还是"易消化"?或者"低热量"?
更头疼的是,当用户说"这个怎么样"的时候,系统完全不知道"这个"指的是什么。
这就是RAG系统中查询重写(Query Rewrite)要解决的核心问题。今天想跟大家分享一下我们的解决方案,以及踩过的那些坑。
为什么不能直接用LLM重写所有查询?
一开始我们的方案很简单粗暴:所有查询都丢给LLM重写一遍。看起来很完美对吧?
然而现实很骨感:
# 简单查询:"手机"
# LLM处理时间:200ms
# 规则处理时间:5ms
# 性能差距:40倍!
对于"手机"这种简单查询,调用LLM纯属杀鸡用牛刀。更要命的是:
- 成本太高:每次调用都要钱,日均百万查询扛不住
- 不可控:同样的查询可能返回不同结果,用户体验不稳定
- 缺乏领域知识:LLM不知道"苹果"在我们系统里对应哪些具体型号
于是我们开始思考:能不能根据查询的复杂程度,选择不同的处理策略?
分层架构:让合适的工具做合适的事
核心思路很简单:先分析查询特征,再决定用什么策略。
第一步:查询特征分析
我们给每个查询打上三个维度的标签:
1. 主查询类型
- 简单查询:
手机(关键词型) - 复杂查询:
什么牌子的护肤品适合敏感肌肤且价格实惠(描述型) - 对话查询:
这个怎么样(指代型)
2. 意图标签(可以有多个)
- 品牌相关:
苹果手机 - 价格敏感:
便宜的奶粉 - 推荐需求:
什么手机好用 - 指代查询:
这款有其他颜色吗
3. 复杂度评分(0-1)
# 评分逻辑很简单
score = 词数权重 + 意图标签数量权重
# 例子
"手机" → 0.3(简单)
"不上火的奶粉" → 0.5(中等)
"什么牌子的护肤品适合敏感肌肤" → 0.8(复杂)
第二步:策略选择与执行
根据特征分析结果,系统会智能选择一个或多个策略:
策略1️⃣:规则扩展(适合简单查询)
# 输入:"手机"
# 输出:
# - "智能手机"
# - "移动电话"
# - "手机设备"
# 优势:快(5ms)、稳定、可预测
# 劣势:覆盖面有限,需要维护规则库
策略2️⃣:同义词替换(通用兜底)
# 输入:"便宜的手机"
# 输出:
# - "实惠的手机"
# - "性价比高的手机"
# - "经济型手机"
# 这个策略我们设为默认兜底,保证每个查询都能有基本扩展
策略3️⃣:检索增强(适合品牌/品类查询)
# 知识库维护品牌映射
brand_mapping = {
"苹果": ["iPhone", "iPad", "MacBook", "Apple Watch"]
}
# 输入:"苹果手机推荐"
# 输出:
# - "iPhone 推荐"
# - "Apple 手机推荐"
# 同时提取过滤条件:brand=苹果
策略4️⃣:上下文指代(适合对话查询)
这是个有意思的策略。处理指代词的关键是维护对话上下文:
context = {
"last_viewed_product": "iPhone 15 Pro",
"last_category": "手机",
"last_brand": "苹果"
}
# 输入:"这款有其他颜色吗"
# 分析:包含指代词"这款"
# 输出:"iPhone 15 Pro 有其他颜色吗"
一个小坑:不要把"这款"、"那款"直接替换成商品名,会导致重复:
# 错误示范
"这款手机" + "iPhone 15 Pro" = "iPhone 15 Pro款手机" ❌
# 正确做法
"这款手机" → "iPhone 15 Pro 手机" ✅
策略5️⃣:LLM优化(适合复杂查询)
只有在其他策略搞不定的时候,才调用LLM:
# 输入:"什么牌子的护肤品适合敏感肌肤且价格实惠"
# 这种复杂语义理解,还是得靠LLM
# LLM Prompt设计
system_prompt = """
你是电商搜索专家,将用户查询改写为3个不同角度的搜索查询。
要求:
1. 提取核心需求
2. 每个改写3-6个词
3. 只返回JSON,不要废话
"""
第三步:智能决策逻辑
这是整个系统的大脑,决定用哪些策略:
def recommend_strategies(query_characteristics):
strategies = []
# 规则1:指代词优先处理
if has_reference_word(query):
strategies.append(CONTEXT_REFERENCE) # 必须先解决指代
# 规则2:根据复杂度选择主策略
if complexity < 0.4:
strategies.extend([RULE_EXPANSION, SYNONYM]) # 简单快速
elif complexity > 0.6:
strategies.append(LLM_OPTIMIZATION) # 复杂语义
# 规则3:根据意图标签增强
if has_brand_or_category(query):
strategies.append(RETRIEVAL_AUGMENTED) # 知识库加持
# 规则4:同义词兜底
if SYNONYM not in strategies:
strategies.append(SYNONYM)
return strategies
一些实现细节和踩过的坑
坑1️⃣:区分推荐查询 vs 知识查询
这两个很容易混淆:
"什么手机好用" → 推荐查询(要产品列表)
"什么是5G手机" → 知识查询(要概念解释)
解决方案:看"什么"后面跟什么词
def is_recommendation_query(query):
# 明确的推荐词
if any(word in query for word in ["推荐", "选什么", "哪个好"]):
return True
# "什么 + 商品类目"且查询较短
words = jieba.cut(query)
if "什么" in words:
next_word = words[words.index("什么") + 1]
if next_word in PRODUCT_CATEGORIES and len(words) <= 5:
return True # "什么手机好用"
if next_word in ["是", "叫"]:
return False # "什么是5G"
return False
坑2️⃣:上下文管理不要过度设计
一开始我们想做一个超级复杂的对话状态追踪,结果发现根本用不上。
最终方案:只保留三个字段就够了
context = {
"last_viewed_product": "最近查看的商品",
"last_category": "最近关注的类目",
"conversation_history": ["最近3轮对话"]
}
简单有效,维护成本低。
坑3️⃣:重写结果要去重和限制数量
多个策略可能生成重复结果,必须处理:
# 保序去重(保留第一次出现的)
unique_rewrites = list(dict.fromkeys(all_rewrites))
# 限制数量(避免后续检索开销过大)
final_rewrites = unique_rewrites[:3]
效果如何?
上线一个月后的数据:
| 查询类型 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 简单查询(60%) | 200ms | 5ms | 40x |
| 复杂查询(30%) | 200ms | 150ms | 1.3x |
| 对话查询(10%) | 指代解析失败率40% | 指代解析失败率15% | 准确率+25% |
成本节省:
- 60%的简单查询不再调用LLM
用户体验:
- 搜索结果相关性提升18%
- 用户满意度+12%
未来还能做什么?
目前的系统还有优化空间:
1. 策略权重学习 现在的策略选择是硬编码规则,可以用强化学习根据检索效果动态调整。
2. 个性化重写 根据用户画像定制策略。比如高端用户少考虑价格因素。
3. 多语言支持 目前只支持中文,可以扩展到英文、日文等。
总结
查询重写看似简单,实则是个系统工程。核心要点:
✅ 不要迷信LLM - 简单问题用简单方法
✅ 分层设计 - 根据查询特征选择策略
✅ 保持简单 - 过度设计是大敌
✅ 数据驱动 - 用真实数据验证效果
完整代码在Github,欢迎交流探讨。
关于作者
搬砖码农一枚,最近在折腾RAG相关的东西。如果你也在做类似的项目,欢迎交流~
本文基于真实生产环境的重构经验,代码示例经过简化处理,实际生产代码会更复杂一些。