向量|指定文档排在最前面,ES 9.1 的 pinned retriever 能和别的检索器组合

举报
岳涛 发表于 2026/11/14 08:34:58 2026/11/14
【摘要】 检索器(retriever)体系里加了一个 pinned。

检索器(retriever)体系里加了一个 pinned。作用很直白:把指定的文档顶到结果最前面,其余的交给另一个检索器。它和早年的 pinned query 功能类似,但能和其它检索器直接组合——这才是关键。

说明

实测环境:本地起的 Elasticsearch 9.4.2 单节点实例(官方 tar 包直接跑,堆内存 512MB,安全与机器学习关闭),curl 连本地 9200 端口。本文「实测」小节的命令和输出都出自这个实例。

来源和边界先交代:本文的依据出自 v9.4.2 标签下的官方文件(9.1.0.yml、pinned-retriever.md、query-dsl-pinned-query.md),文中引用的原文均来自这些文件。示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

01-9.1.0

图 1:官方文件 9.1.0.yml(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 9.1.0.yml

官方条目

9.1 的 changelog 标为 enhancement(9.1.0.yml 第 862–865 行 @ v9.4.2),area: Relevance:

Add pinned retriever

只有一行 summary。但这个 retriever 有独立文档,而且写得相当完整——参数、示例、与 pinned query 的对比都有。

02-pinned_retriever

图 2:官方文件 pinned_retriever.csv(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 pinned_retriever.csv

它做什么

官方描述(pinned-retriever.md @ v9.4.2):

A pinned retriever returns top documents by always placing specific documents at the top of the results, with the remaining hits provided by a secondary retriever.

两件事:

  1. 指定的文档永远排在最前;
  2. 剩下的由另一个检索器提供。

两句话缺一不可——只有第一句是"置顶查询",两句合起来才是"置顶检索器"。

文档标注的适用版本是 GA 9.1(文档头部 applies_to: stack: ga 9.1)。

03-pinned-retriever

图 3:官方文件 pinned-retriever.md(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 pinned-retriever.md

和 pinned query 的区别

这是本篇最该说清楚的一点。

官方第二段:

This retriever offers similar functionality to the [pinned query], but works seamlessly with other retrievers.

功能相似,但能和其它检索器直接配合。 这一句是理解本篇的关键。

为什么这个区别重要? 得先说检索器是什么。

检索器(retriever)是 8.x 后期引入的一套可组合的检索抽象。每个检索器负责一种检索方式(standard、knn、rrf、text_similarity_rank 等),可以嵌套组合——一个检索器的输出作为另一个的输入。

pinned query 是查询 DSL 里的一个查询类型——它不在检索器体系内。所以:

  • 用 pinned query 时,你是在查询 DSL 的世界里;
  • 用 pinned retriever 时,你是在检索器的世界里,可以和 rrf、knn 这些直接嵌套。

它的价值就在这里:它解决了"想置顶,又想用检索器"这个以前二选一的问题。两种能力都在,就是接不到一起。

三个参数

文档里列得很清楚:

ids(可选,字符串数组):要置顶的文档 ID 列表,按给出的顺序排列。

docs(可选,对象数组):要置顶的文档列表。每个对象至少要有 _id,跨索引时还可以指定 _index。

retriever(可选,检索器对象):用来提供剩余文档的检索器——可以是 standard,也可以是 rrf 这类专门的检索器。

约束:ids 和 docs 至少给一个。

ids 和 docs 的差别在于能否跨索引:ids 只给 ID,限于当前查询范围;docs 可以带 _index,能钉住别的索引里的文档——做推广位时这个能力有用。

典型用法

按文档的参数结构,大致是这样(下面的例子按文档描述构造,未实际执行):

GET my-index/_search
{
  "retriever": {
    "pinned": {
      "ids": ["doc-1", "doc-2"],
      "retriever": {
        "standard": {
          "query": { "match": { "title": "elasticsearch" } }
        }
      }
    }
  }
}

结果:doc-1、doc-2 排在最前(按这个顺序),后面跟着 standard 检索器命中的文档。

跨索引的版本用 docs:

"pinned": {
  "docs": [
    { "_index": "my-index", "_id": "doc-1" },
    { "_index": "other-index", "_id": "doc-9" }
  ],
  "retriever": { "standard": { "query": { "match_all": {} } } }
}

配 rrf 才是它的价值所在

前面说"能和 rrf 嵌套",这里说清楚为什么这个组合值得用。

RRF(倒数排序融合)用来合并多路检索结果——比如把关键词检索和向量检索的结果融合成一个排序。混合检索的基础设施。

pinned + rrf 的组合意味着:

  • 底层用 RRF 融合多路结果;
  • 上层把指定的几条钉在最前。

这是"业务规则 + 相关性排序"的分层:相关性由 RRF 决定,业务置顶由 pinned 决定,两者互不干扰。

判断是:这个组合才是 pinned retriever 存在的真正理由。单独置顶的话,pinned query 也能做;在混合检索之上做置顶,以前没有干净的办法——要么放弃检索器,要么放弃置顶。

检索器体系是怎么组织起来的

上面反复提到"检索器体系",这里把它讲清楚,因为不理解这个体系就看不懂本篇改动的位置。

检索器解决的是"一次搜索怎么组成"的问题。 把它拆成几个可替换的部分:

环节 由什么检索器负责
怎么召回 standard(关键词)、knn(向量)
多路结果怎么合并 rrf、融合检索
顺序怎么调整 本篇的 pinned
特定字段加权 多字段权重类检索器

每个环节一个检索器,然后嵌套。 外层的结果依赖内层提供的文档。

这种结构的优点是组合自由:换掉某一环不影响其它部分。想从纯关键词改成混合检索,只改召回那一层。

对比一下老做法:查询 DSL 里一个大 JSON 把召回、排序、后置处理全部写在一起,想换一环就得重写一遍。

所以这里的判断是:检索器体系的价值在于"可替换的层次",而不是它提供了什么新能力——它把原本纠缠在一起的东西拆开了。

pinned 进来补上的是"顺序调整"这一层——最后一环。位置正好:召回 → 合并 → 置顶。

什么场景需要它

能想到的几类:

一,运营置顶。 某些文档需要强制曝光(活动页、公告),不管相关性如何。

二,人工干预结果。 用户反馈"搜 X 应该出 Y",把 Y 钉住是最直接的修正。

三,兜底。 某些查询的相关性排序不好,先用置顶保证至少有几条是对的。

四,广告/推广位。 固定位置放指定内容——这是搜索产品最常见的需求之一。

这几类的共同点:结果顺序不完全由相关性决定。检索的本质是"按相关性排序",而现实需求往往要求在相关性之上叠加业务规则——pinned 就是那层业务规则。

一个要小心的地方

置顶会绕过相关性。

这是它的功能,但也是风险:

  • 钉住的文档可能和查询完全无关;
  • 用户看到的前几条如果离题,体验会比不置顶更差。

建议:

  • 置顶数量要少。钉住一两条是干预,钉住十条就是"接管了排序";
  • 优先按查询条件分别配置,而不是全局固定几条——"所有查询都置顶同一批"通常是错的做法;
  • 留意置顶文档的失效。被钉住的 ID 如果删除了,行为如何未确认——值得测一下。

第三条特别标出来:依赖 ID 的配置都有这个隐患。文档删了、ID 变了,置顶就指向了不存在的东西。

置顶和"提高权重"不是一回事

这里有个容易混的点值得说:置顶不等于加权。

  • 置顶:把某些文档放到最前面,顺序是强制的,不受相关性影响;
  • 加权:提高某些文档的得分,让它们更可能排前面,但最终顺序还是由分数决定。

区别在"强制"还是"倾向"。

加权更温和——如果一个被加权的文档和查询完全无关,它可能还是排不进前几;而置顶一定在最前,哪怕零相关性。

什么时候该用哪个? 判断:

  • 业务上必须出现(合规、活动、公告)→ 置顶,因为要的是确定性;
  • 业务上希望优先(新品、高质量内容)→ 加权,因为要的是"倾向"而不是硬排,还能保留相关性的调节空间。

用错方向的后果:

  • 该加权的地方用了置顶 → 结果僵化,用户看到不相关的东西;
  • 该置顶的地方用了加权 → 可能出现"没出现"的情况,而业务要求的是"必须出现"。

判断依据就一条:业务能接受这条不出现在最前吗? 不能 → 置顶;能 → 加权。

和别的检索器放一起看

9.x 在检索器体系上持续投入,前面写过几篇相关的:

篇 内容
那篇 多字段权重的检索器
融合检索那篇 FUSE / RRF 混合检索
本篇 pinned 置顶

共同的模式:把"检索"拆成可组合的几块——怎么召回、怎么融合、怎么调整顺序。每一块单独一个检索器,然后嵌套。

这个方向是对的,理由和前面几篇一致:组合优于庞大配置。一个什么都配的大对象,不如几个各司其职的小对象拼起来——后者的每种组合都是被验证过的。

和分页一起用要注意

如果搜索有分页(深度翻页或无限滚动),置顶会带来一个实际问题需要处理。

问题的形态:置顶的文档占掉了第一页的位置,那么第二页往后会不会再出现它们?

  • 如果会出现 → 用户翻页时看到重复内容;
  • 如果不出现 → 第一页的条数比后续页多(多了置顶的那几条),分页大小不一致。

这两种表现都不理想,但第二种更常见也更可接受。

具体行为未确认——文档没写,也未实测。所以这里不给结论,只提示这是个要验证的点。

建议:有分页的搜索接置顶时,先翻几页看看有没有重复。这是那种"功能上线时没发现、用户用几天才反馈"的问题。

还有一个相关的点:置顶的文档如果本来就会被正常检索命中,那它相当于"提前了",总数不变;如果本来命不中(比如不满足查询条件),那它是"凭空加进去的",总数会多。这两种情况下分页的表现不一样,值得分别测。

这条改动怎么看

这条改动不大,但对做搜索产品的人来说很实用。

评价它的角度是它补上了哪个缺口,不是技术复杂度:

  • 置顶能力以前就有(pinned query);
  • 检索器体系以前也有;
  • 两者结合以前没有。

所以它是一个"打通"型的改动——把两套已有的东西接起来。这类改动在 changelog 里通常只有一行(本篇就是),但对正好卡在这个缺口上的人,价值不小。写它也是因为这类条目容易被漏读。

建议:如果用检索器(尤其是 RRF 做混合检索),并且有置顶需求,用 pinned retriever,不用 pinned query 硬凑。后者不在同一个体系里,组合起来要绕,而且绕出来的结构很难维护。

在 9.4.2 上跑一遍

先插 id 为 2 的文档、再插 id 为 1 的,不加排序时自然序是 2、1:

pinned

图 4:本地 9.4.2 单节点实例上的实测输出。上:嵌套键用 retriever;下:老写法 organic

curl -s -X POST "" \
  -H 'Content-Type: application/json' \
  -d '{"retriever":{"pinned":{"ids":["1"],"retriever":{"standard":{"query":{"match_all":{}}}}}}}'

curl -s -X POST "" \
  -H 'Content-Type: application/json' \
  -d '{"retriever":{"pinned":{"ids":["1"],"organic":{"standard":{"query":{"match_all":{}}}}}}}' \
  | jq -r '"HTTP \(.status): \(.error.root_cause[0].reason)"'

正确写法返回的 id 顺序是 1、2。文档插入顺序是 2 在先,所以这个顺序是 pinned 起作用的结果,不是自然序。

第二条换成 organic,返回 HTTP 400: [1:37] [pinned] unknown field [organic]。嵌套的检索器要放在 retriever 键下,键名错了在解析阶段就拒,不会等到执行才发现"置顶没生效"。

结论是:拼 retriever 树时先拿 match_all 把外层跑通,再逐层往里塞。每层键名错了都会拿到 unknown field,比事后查"为什么没置顶"定位快得多。

写在最后

一个很朴素的检索器:把指定文档钉在最前,其余交给另一个检索器。

一,ids / docs 指定要钉的文档,retriever 提供剩余结果。 ids 和 docs 至少给一个;docs 能带 _index,可跨索引,ids 不行。

二,它和 pinned query 功能相似,但属于检索器体系,能和 rrf、standard 等直接嵌套。这是它存在的理由——置顶能力和检索器体系以前是二选一。

三,置顶会绕过相关性,数量要克制。 并且留意被钉文档的 ID 失效问题(这一点未确认行为)。另外置顶和加权是两回事,选错方向会出问题——判断依据是"业务能接受它不在最前吗"。

另外诚实标注两处:示例按文档描述构造,未执行;被钉文档删除后的行为未确认。

参考

本文对照 Elasticsearch v9.4.2 标签的 changelog 与官方文档核对,引文逐字取自原文。示例查询已在本地 9.4.2 单节点实例上跑通,嵌套键是 retriever(不是 organic),置顶后的 id 顺序变化见图 4。被置顶文档删除后的行为仍未确认。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。