本系列第 1 部分和第 2 部分已经阐明,电子商务搜索为什么需要一个治理层:它是在用户查询与检索引擎之间的决策层,用于识别意图、实施约束,并将查询路由到正确的检索策略(例如 BM25、语义检索、混合检索)。本文展示了如何使用一个简单的架构原语构建该层:将查询解释策略存储为文档,并在查询时通过快速反向匹配进行检索。由于新的检索策略(例如“提升品牌 X”或“仅显示类别 Y”)无需修改代码,最终形成的路由层可以在策略不断演变的同时保持稳定,并让检索引擎在高风险环境中保持安全可控。如果您想在继续阅读之前了解该架构的最终效果,请观看此视频:在数秒内修复搜索相关性:PRISM 简介。
为何查询解释常常是一个挑战
将策略作为代码存储(应用层中的 if/else 块)会产生成千上万行脆弱逻辑,而且没有任何索引可用于在查询时高效检索策略。迭代速度很慢(单个查询行为变更可能需要 6 周的部署周期),责任归属不清(为什么结果会发生变化?),并且业务用户无法在没有工程团队介入的情况下修改搜索行为。下图左侧展示了这一点:

上图右侧展示了将策略作为数据存储在 Elasticsearch 索引中的方式。这种方法解决了硬编码查询求解逻辑所带来的所有问题。然而,要使其奏效,您需要一种方法来快速确定哪些策略适用于用户查询,以及应如何解决冲突。这正是治理型控制平面发挥作用的场合。
控制平面模式
受治理的控制平面位于原始用户查询和 Elasticsearch 检索之间。它接收用户文本作为输入,输出一个包含筛选器、提升规则和检索路由决策的执行计划。

控制平面流水线包括:
- 用户查询:用户输入表示自己要查找内容的字符串,例如 “oranges” 或 “gift for grandpa”。
- 策略查找:将用户查询与策略索引进行匹配。
- 返回匹配策略:从策略索引中返回与用户查询匹配的策略。
- 策略应用:控制平面分析这些返回的策略,并将匹配策略组合成一个单一、连贯的执行计划。该计划包括筛选器、提升规则、覆盖规则和护栏,并会应用适当的检索方法(例如词汇检索、语义检索或混合检索)。
- 执行:修改后的意图感知型 Elasticsearch 查询会传递给应用程序,并针对产品目录索引执行。
- 解释(可选):除了创建能够提供与业务和意图一致结果的查询外,控制平面还会提供一个可选的可解释性数据载荷,用于显示触发了哪些策略,以及这些策略如何组合。
要确定应对用户的搜索字符串应用哪些策略,需要一个快速反向匹配原语;我们使用 percolator 查询来解决这个问题。检索到相关策略后,要将多个匹配策略组合成统一的执行计划,还需要一个判断框架:优先级、冲突策略、已消耗短语跟踪,以及按顺序而非独立地应用策略的级联转换。此外,还需要选择最合适的检索技术(例如针对 “oranges” 使用 BM25,针对 “gift for grandpa” 使用语义搜索)。
策略查找:搜索产品前检查查询语句
当购物者输入查询时,带有受治理控制平面的搜索系统不会直接将该查询发送到产品目录执行。系统会先将查询与一组已存储的策略进行比对,然后根据查询意图和业务优先级对其进行修改。
政策结构
每项策略都是一个简单文档,用于定义两件事:
- 匹配条件:哪些查询文本会触发此策略。它可以是精确短语、单个词、某种模式,或以上内容的组合。
- 操作:策略触发时应执行什么操作。这可以是应用类别筛选器、排除产品、提取价格约束,或更改检索策略。
系统会找到所有匹配的策略,将它们组合成一个执行计划,然后才运行产品搜索。综合来看,各项策略就像一位知识渊博的店员,了解您想要什么,并引导您找到正确的货架。
策略模式
本系列前几篇文章介绍了策略应用的示例:将 “oranges” 限制在蔬果类别,将 “without peanuts” 视为排除条件,并将 “gift for grandpa” 路由到语义检索。关键的架构要点是,在每种情况下,都会先将查询与已存储的策略进行比对,然后才开始产品搜索。这些策略决定要应用哪些约束、修改哪些文本,以及使用哪种检索策略。只有在策略应用完成并创建新的重写查询之后,才会针对产品目录执行查询。
为何它如此快速
企业电子商务系统可能有数百万种产品,但只有数百或数千项策略。策略查找步骤是在一个经过整理的小型索引中搜索,而不是搜索完整产品目录,因此速度很快。此外,由于策略作为数据存储在自己的索引中,商品经理添加新策略时无需改动应用程序代码,工程师优化产品搜索时也无需改动策略索引。这两项职责可以独立演进。
以上例子从概念上描述了所发生的情况。在底层,策略查找是通过 Elasticsearchpercolator 查询类型实现的,该类型专为这种模式设计:将传入文本与一组存储的查询进行匹配。本系列的第 4 部分深入探讨了 percolator 的实现,包括索引映射、边界标记和高亮驱动的短语跟踪。在第 4 部分深入介绍了查找机制之后,我们来探讨策略文档的实际内容,以及控制平面如何将多个策略组合成单一的执行计划。
策略示例
既然我们已经从概念上了解了策略的作用,接下来看看它们实际包含哪些内容。以下两项策略有意设计为相互冲突,用于演示后续章节介绍的冲突解决系统。
廉价巧克力
下面显示的策略会检测用户提交的搜索是否包含短语 “cheap chocolate”。如果包含,则将结果限制在 “Chocolates” 和 “Milk chocolates” 类别中。该策略还会应用 $2 的价格筛选器。此外,请注意,该策略的优先级为 210;我们会在更详细讨论冲突解决时回到这一点。
此处显示的筛选器模式和冲突策略设置(hard_filter、soft_boost、restrict、override)将在下方的冲突解决部分详细说明。

启用上述策略后,搜索 “cheap chocolate” 会遵循 $2 的价格筛选条件,并将结果限制在 “Chocolates” 和 “Milk chocolates” 类别中。示例结果如下所示:

圣诞巧克力
下面显示的策略示例适用于圣诞节场景。此示例会将结果限制在 “Christmas foods and drinks” 和 “Christmas sweets” 类别中,提升同时属于 “Advent calendars” 类别的所有产品,并应用低于 $7 的价格筛选器,以重点展示价格适中的季节性商品。此外,请注意,该策略的优先级为 300。我们会在更详细讨论冲突解决时回到这一点。

在没有任何冲突策略的情况下启用上述策略时,搜索 “chocolate” 会遵循 $7 的价格筛选器,将结果限制在 “Christmas food and drinks” 和 “Christmas sweets” 类别中,并提升任何标记为 “Advent calendars” 的产品。示例结果如下所示:

组合匹配策略
上文所述的策略查找只完成了一半。另一半是当多个策略匹配同一个查询时会发生什么。
在任何较为复杂的部署中,单个查询通常会同时触发多项策略。“Cheap chocolate” 会同时匹配我们在上文演示的两项策略。每项策略单独来看都是正确的。真正的挑战在于将它们组合成一个单一、连贯的执行计划,避免矛盾、重复计算,也避免某项策略悄然抵消另一项策略的作用。
这不是查找问题,而是判断问题。系统必须做出决定:
- 应用顺序:如果否定策略从查询中移除了 “without peanuts”,那么价格策略看到的还是原始文本,还是修改后的文本?
- 筛选器冲突:如果两项策略设置了不同的价格上限,哪一个会生效?未生效的一方会被静默丢弃,还是会平滑降级为软性提升?
- 短语归属权:如果两项策略都匹配同一个词,而第一项策略已经消耗了该词,第二项策略是否仍应触发?
一种朴素实现方式(独立应用所有匹配策略,然后合并结果)会在策略发生交互时失效。该架构需要一个显式模型来描述策略如何组合。接下来的两节将介绍这个模型:优先级和冲突解决框架,以及让策略交互具有确定性的级联转换模型。
核心在于,策略应用不是一组独立操作,而是一个级联转换。每项策略都会接收由所有更高优先级策略生成的重写状态,并在此基础上继续转换:
初始状态 → [策略 A] → 状态' → [策略 B] → 状态'' → … → 执行计划
状态会携带重写后的查询文本、累积的筛选器、当前意图以及所有同义词扩展。高优先级策略可以从查询中移除文本,而每项后续策略看到的都是修改后的查询,而不是原始查询。上下文会不断累积。顺序至关重要。
优先级与冲突解决:确定性至关重要
具体采用哪些冲突策略属于设计选择。不同组织可能会根据自身业务需求,以不同方式解决冲突。下面的方法展示了控制平面所需的一类判断框架。关键不在于这些具体策略本身,而在于系统需要具备明确、确定性的策略,而不是让冲突通过不可预测的交互自行解决。
优先级排序
策略按优先级排序(优先级最高的在前)。当多个策略匹配同一查询时,它们会按优先级顺序应用。如果两个策略尝试设置同一个过滤字段,则优先级更高的策略对该字段声明的策略优先。如果触发了多个具有相同优先级的策略,则优先级最高的策略(ID 最大)将优先;这种选择确保了冲突发生时的确定性行为。
按字段解决,而非按策略
一个关键设计原则是:冲突解决按字段(例如品牌、类别或描述)进行,而不是按策略进行。当两项策略生成的筛选器在特定字段上重叠时,只有这些特定字段会受冲突解决策略影响,并且解决策略由优先级最高的匹配策略定义。两个策略中未发生冲突的字段会完整保留。
这一点很重要,因为如果采用按策略处理的方法,那么即使只有某一个字段发生冲突,系统也必须接受或拒绝整项策略。
按字段解决可以最大限度保留有用的约束信息。
每个过滤器字段有三种设置
每个策略中的筛选字段都有三个独立的设置:
筛选器模式:没有冲突时如何应用筛选器。
hard_filter(默认):作为 Elasticsearchbool.filter子句应用。这适用于完全排除无关产品。例如,将 “oranges” 的搜索范围限制在 produce 类别中,可以排除 orange juice 和 orange marmalade 等搜索结果。不匹配的文档会从结果中完全排除。soft_boost作为 Elasticsearchfunction_score权重应用,并可配置boost_weight。匹配的文档会获得排名提升,但不匹配的文档不会被排除。这适用于提升某个品牌的排名,同时又不排除其他品牌的场景。
冲突策略
当较低优先级的策略设置相同字段时会发生什么:
override:此高优先级策略的值会生效;较低优先级的值会被完全丢弃。适用于所有字段类型。restrict:取限制性更强的数值(例如,price_max 取较低上限,price_min 取较高下限)。仅适用于数值范围字段。merge:将两个值合并为并集。仅适用于非数值字段。soft_boost:将冲突的筛选器转换为具有可配置boost_weight的function_score权重,而不是硬性筛选器。有关 function_score 提升的更多详情,请参阅《在 Elasticsearch 中使用乘法提升影响 BM25 排名》。这仅适用于非否定字段。
值:实际筛选值(例如,类别列表、价格阈值)。
按字段类型划分的策略:并非所有策略都适用于所有字段类型。例如,排除本质上是二元决策,因此不能进行软性提升。下表显示了每种字段类型可用的策略:
| 字段类型 | 可用策略 | 默认值 |
|---|---|---|
| 否定字段 (__not, __match__not) | override、merge | 覆盖 |
| 数值范围字段 (__max, __min, __gt, __lt) | 限制、覆盖、软提升 | 限制 |
| 所有其他字段(关键词、文本) | soft_boost、override、merge | soft_boost |
否定字段不能进行软性提升,因为排除逻辑本质上是二元决策。将 “never show canned foods” 转换为 “slightly prefer not-canned-foods” 会从根本上改变语义;来自 “canned foods” 的产品仍然会出现,只是排名略低,这违背了排除的初衷。
具体示例:圣诞活动期间搜索 “cheap chocolate”
假设商品经理已经创建了我们之前演示的两项巧克力策略:一项是针对 “cheap chocolate” 的较低优先级策略,另一项是在圣诞节期间启用的较高优先级巧克力相关策略。如果这两项策略都已启用,那么它们的组合方式取决于优先级更高策略的筛选器模式和冲突策略。如果前面讨论的两项策略都已启用,它们将按如下方式组合:

这里显示了两个冲突:一个发生在类别上,另一个发生在价格上。值得注意的是,此次转换之后将要执行的查询具有以下特征:
- 仅展示属于 “Christmas foods and drinks” 和 “Christmas sweets” 类别的产品。
- 在这些类别中,如果产品还被标记为 “Advent calendars” 类别,则会获得 3 倍排名提升。
- 应用了 $2 的价格筛选器,该筛选器来自较低优先级策略(因为较高优先级策略指定在发生冲突时使用 “Restrict”)。
- 移除 “cheap” 一词,仅返回与 “chocolate” 匹配的产品。
启用这两项策略后,“cheap chocolate” 返回的结果类似于下图所示:

放宽限制
也许零售商并不希望在圣诞节期间排除 “Chocolates” 和 “Milk chocolates” 类别中的产品。Christmas 策略的设置可能过于强势,无意中移除了 “cheap chocolate” 策略应用的类别。这个示例说明,在某些情况下,将较低优先级策略与存在冲突的较高优先级策略组合起来,可能更符合业务需求。例如,我们可以修改 Christmas chocolates 促销策略,使其在发生冲突时不使用 “Override”,而是采用软性提升。该策略的变更如下:

完成此修改后,“cheap chocolate” 的查询重写转换流水线如下所示:

通过对冲突的软提升,冲突的筛选器被转换为软提升,而不是被丢弃。在此转换之后将在商品目录上执行的查询具有以下特征:
- 由于较高优先级策略的 “On conflict” 设置为 “Soft boost”,冲突将按如下方式转换为提升:
- “圣诞食品和饮料”以及“圣诞甜点”类别的产品将会获得 1 倍的提升。
- “Chocolates” 和 “Milk chocolates” 类别中的产品会获得 3 倍提升。
- 与前面的示例一样,如果产品还被标记为 “Advent calendars” 类别,则会获得 3 倍提升。
- 与前例相同,会应用 $2 的价格筛选器。
- 移除 “cheap” 一词,仅返回与 “chocolate” 匹配的产品。
放宽筛选条件后,结果如下所示:

使用高优先级策略中的价格覆盖原有价格
或者,零售商可能希望通过将最高价格提高到 $7,允许在圣诞节期间展示价格稍高的巧克力。为了确保有人搜索 “cheap chocolates” 时,Christmas chocolates 策略中的最高价格不会被覆盖,我们可以将价格的冲突模式设置为 “override”,而不是 “restrict”,如下所示:

通过这种覆盖,“廉价巧克力”查询忽略了“廉价巧克力政策”中定义的最高价格,仅应用“圣诞巧克力”政策中规定的价格,具体如下:

这与上一个示例类似,不同之处在于最高价格会设置为较高优先级策略中的 $7,因为该策略指定在发生冲突时使用 “Override”。当 Christmas 价格筛选器优先生效时,结果如下所示:

这三种变体(override、soft_boost 和价格覆盖)展示了该系统的一项关键特性:商品经理只需修改单一策略中某个字段的设置,即可改变两项策略的交互方式,而无需部署任何代码。冲突策略是控制业务行为的杠杆。
已消耗短语跟踪
还有一种更微妙的冲突形式:两项策略匹配同一个短语。如果优先级较高的策略从查询中移除了 “without peanuts”,那么同样匹配 “without” 的较低优先级策略就没有可作用的内容。系统会检测重写后的查询中是否已不再存在该匹配短语,并跳过优先级较低的策略。
意图策略不受已消耗短语跟踪影响:它们会根据原始查询匹配结果设置检索策略,而不考虑更高优先级策略移除了哪些文本。
优先级排序、每字段冲突解决以及消耗短语跟踪共同为控制平面提供了一个确定性组合模型。有了这个基础,系统可以做出在没有它的情况下可能存在风险的路由决策。
治理让检索策略更安全
关于路由到正确检索方法(文本、语义或混合),一个重要见解是:这一过程发生在治理之后。如果您的策略已经强制应用 “produce category”,那么语义检索的风险会低得多,因为候选集已经受到约束。对 500 个产品项执行语义搜索,与对 500,000 个 SKU 执行语义搜索,完全是两种不同的情况。治理会在检索开始前缩小爆炸半径,从而降低风险影响范围。
例如,如果没有治理,对 “Fruit high in vitamin C under $4” 进行语义查询时,除了水果之外,可能还会返回瓶装维生素、胡萝卜和青椒。控制平面会确保这些不需要的结果甚至不会被纳入语义扩展的考虑范围。

在该约束生效后,控制平面会应用务实的路由逻辑:
- 词汇检索用于导航型查询和高频头部查询,即确定性精度至关重要的场景。
- 语义检索用于描述性发现查询,即概念匹配能够发挥作用的场景。
- 在约束已执行且业务接受更广泛召回的情况下,选择性使用混合检索。
从架构到实施
受治理的控制平面会将业务意图转化为确定性、可组合的执行计划,而无需将该逻辑嵌入应用代码。策略就是数据:在查询时进行匹配,通过显式的逐字段冲突策略解决冲突,并作为级联转换应用,从而生成可解释的结果。Elastic Services Engineering 已为企业电子商务团队构建并部署了这种架构,并使用可复用的模式和加速器,缩短从概念到生产落地的路径。您可以在 YouTube 上观看我们控制平面实现的演示:在数秒内修复搜索相关性:PRISM 简介。
本系列内容预告
下一篇文章将通过实际操作介绍实现过程:Elasticsearch percolator 如何驱动策略查找,包括索引映射、边界标记、高亮驱动的短语跟踪,以及具体查询示例。
将受治理的电子商务搜索付诸实践
本文介绍的控制平面架构(逐字段冲突解决、级联策略转换和受治理约束的检索路由)由 Elastic Services Engineering 设计并构建。本系列展示的每个模式、截图和转换流水线,均来自由 Elastic Services Engineering 构建,并已针对企业级产品目录完成验证的实际运行系统。
如果您希望在 Elasticsearch 上实现一个受治理、由策略驱动的控制平面,Elastic Services 可以帮助您更快达成目标。
加入讨论
对搜索治理、检索策略或电子商务搜索架构有疑问?加入更广泛的 Elastic 社区讨论。




