为什么我在向量搜索之上添加了 LLM 解析器(以及它带来的变化)
Source: Dev.to
我曾以为向量搜索已经足够
我创建了 Queryra – 一个用于 WooCommerce 和 Shopify 的 AI‑search 插件。
它用语义嵌入取代了关键词匹配,这样用户就可以输入类似 “冬天的保暖衣物” 的描述,立即找到毛衣、抓绒外套、毯子等。零结果的查询变得很少。它运行良好……直到有人搜索:
“$80 以下的无线耳机,且不是 Beats”
向量搜索返回了无线耳机,但其中很多价格在 $200,且有几款是 Beats。价格上限和品牌排除在嵌入模型中完全不可见。
这时我意识到:向量搜索只是第 1 层。我遗漏了第 2 层。
纯向量搜索的问题
嵌入擅长一件事:编码 语义相似度。
- “Sneakers” 与 “trainers” 和 “running shoes” 距离很近。
- “Gift for dad” 能找到园艺工具、烧烤套装、手表——即使查询中没有这些词。
但像 “laptop under $1000 for video editing, not Chromebook” 这样的查询包含两种根本不同的信息类型:
| 类型 | 描述 |
|---|---|
| 语义意图 | 客户想要的东西(用于视频工作的强大笔记本电脑) |
| 结构约束 | 如何过滤结果(价格上限、类别排除) |
嵌入能够很好地处理 语义意图,但它们 没有处理结构约束的机制。你无法把 “under $1000” 编码为向量空间中的方向,而 “not Chromebook” 也不是语义概念——它是对搜索系统的指令。所有仅使用向量的实现都有这个盲点,且随着查询变得更具体,这一问题会愈发严重。
谁受影响最大? 意图最高的买家——那些准备立即购买的用户。
解决方案:LLM 解析器作为第二层
我添加了一个 查询解析器,它在向量搜索 之前 运行。它的工作是将查询分解为结构化组件。
示例
{
"semantic_query": "organic shampoo",
"price_max": 25,
"attribute_exclude": ["sulfates"],
"sort_by": "rating"
}
然后,每个组件会被发送到相应的子系统:
| 组件 | 目标 | 目的 |
|---|---|---|
semantic_query | 向量搜索 | 查找语义相关的产品 |
price_max | 数据库过滤 | 硬性上限 $25 |
attribute_exclude | 后置过滤 | 移除含硫酸盐的产品 |
sort_by | 结果重新排序 | 优先展示评分最高的 |
向量层发现 客户的意图;解析器层则执行 客户的要求。
绕过问题(延迟)
解析器会增加约 700–800 ms 的延迟。对于像 “blue t‑shirt” 这样的简单查询,这种开销是没有必要的,因为仅使用嵌入就能很好地处理。
路由快捷方式
import re
def should_parse(query: str) -> bool:
# Price signals
if re.search(r'under \$|below \$|\$\d+|budget|cheap|premium', query, re.I):
return True
# Exclusion signals
if re.search(r'\bnot\b|\bwithout\b|\bno\b|\bexclude\b', query, re.I):
return True
# Sorting signals
if re.search(r'best rated|top rated|newest|cheapest|most popular', query, re.I):
return True
# Brand signals (capitalized words that aren't at sentence start)
if re.search(r'(?<!^)(?<!\. )[A-Z][a-z]+(?:\s[A-Z][a-z]+)*', query):
return True
return False # Simple query — go straight to vector search
简单查询会直接跳过解析器。 复杂查询则进行完整的意图提取。路由对用户是透明的——他们只会得到更好的结果。
变更内容
| 查询 | 之前(仅向量) | 之后(向量 + 解析器) |
|---|---|---|
| “$80 以下的耳机” | 所有耳机 | 仅 $80 以下的耳机 |
| “非 BrandX 品牌” | 包含 BrandX | 排除 BrandX |
| “评分最高的咖啡机” | 随机顺序 | 按评分排序 |
| “有机且不含硫酸盐” | 所有有机洗发水 | 过滤后的无硫酸盐 |
每个表格的第一行都是相同的——简单的语义查询的效果相同。其余每一行展示了解析器填补的差距。
一个意想不到的好处:拼写错误 + 约束
我原本期望解析器能帮助处理结构化查询,但它还解决了一个次要问题:拼写错误与约束的组合。
- 向量搜索 本身对拼写错误的容忍度很高——“moisturiser” 能找到 “moisturizer”。
- “moisturiser under $20 without pareban”(parabens 拼写错误)导致排除失效,因为在拼写错误的词上嵌入相似度下降。
LLM 解析器一次性处理两者:它纠正拼写错误,提取价格约束,并识别排除条件。这种综合的鲁棒性让人惊喜。
权衡
解析器会产生费用,因为它在处理复杂查询时会进行一次 LLM API 调用。我使用 gpt‑4.1‑nano(在此用例下与 gpt‑4o‑mini 质量相同,成本约低 33 %)。通过绕过逻辑,只有一小部分查询会进入解析器,但费用仍随流量而增长。
- 自托管选项:将 API 调用替换为本地模型(例如,Ollama + Mistral 7B 在意图抽取方面表现相当不错)。
- SaaS 产品:将 LLM 使用量计入定价。
接下来会怎样
解析器目前能够提取:
- 价格区间
- 品牌引用
- 属性过滤和排除
- 排序偏好
- 基本否定
接下来要做的: 多意图查询。
示例:“适合办公室的东西和适合健身房的东西”——两个独立的语义搜索,结果合并。仅靠向量搜索无法拆分意图;解析器可以。
如果你正在构建电商搜索并遇到同样的瓶颈——向量结果会忽略前两个有意义的词之后的所有内容——这种两层方法值得增加的复杂性。
我为店主写了一个更长的、非技术性的版本,见此处:Why Vector Search Alone Isn’t Enough for Ecommerce Stores
乐意回答问题!
Queryra 是针对 WooCommerce 和 Shopify 的 AI 搜索。queryra.com