向量|指定文档排在最前面,ES 9.1 的 pinned retriever 能和别的检索器组合
检索器(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),文中引用的原文均来自这些文件。示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

图 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 的对比都有。

图 2:官方文件 pinned_retriever.csv(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 pinned_retriever.csv
它做什么
官方描述(pinned-retriever.md @ v9.4.2):
A
pinnedretriever returns top documents by always placing specific documents at the top of the results, with the remaining hits provided by a secondary retriever.
两件事:
- 指定的文档永远排在最前;
- 剩下的由另一个检索器提供。
两句话缺一不可——只有第一句是"置顶查询",两句合起来才是"置顶检索器"。
文档标注的适用版本是 GA 9.1(文档头部 applies_to: stack: ga 9.1)。

图 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:

图 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 失效问题(这一点未确认行为)。另外置顶和加权是两回事,选错方向会出问题——判断依据是"业务能接受它不在最前吗"。
另外诚实标注两处:示例按文档描述构造,未执行;被钉文档删除后的行为未确认。
参考
- changelog 9.1.0:Add pinned retriever(第 862–865 行)@ v9.4.2
- pinned retriever 文档:GA 9.1、参数说明与示例 @ v9.4.2
- pinned query 文档:作为对比 @ v9.4.2
pinned_retriever.csv:transport 层定义 @ v9.4.2
本文对照 Elasticsearch
v9.4.2标签的 changelog 与官方文档核对,引文逐字取自原文。示例查询已在本地 9.4.2 单节点实例上跑通,嵌套键是retriever(不是organic),置顶后的 id 顺序变化见图 4。被置顶文档删除后的行为仍未确认。
- 点赞
- 收藏
- 关注作者
评论(0)