混合检索:后处理
本文介绍如何在 Doris 混合检索中使用后处理(post_process),包括分数过滤、长文本降权、Fusion 与 Reranker 分数融合、字段衰减、MMR 多样性选择和候选截断。
概览
后处理在 Fusion 或 Reranker 产出候选分数后,对候选结果进行二次调整。后处理只作用于已经生成的候选集,不改变召回、Fusion 或 Reranker 的候选生成方式。未配置 post_process 时,查询行为与原有 Fusion 或 Reranker 完全一致。
1Fusion 结果 / Pure Rerank 候选
2 ↓ candidates
3模型精排 Reranker(可选)
4 ↓ __RANK
5后处理 Post-Process
6 ↓ __RANK
7SQL ORDER BY / LIMIT
8 ↓
9查询结果
前提条件
使用后处理前请确认:
- 查询能够从 Fusion 或 Reranker 获得主分数。
- JSON 顶层的
post_process不是空对象。 - 只要配置
post_process,就同时配置topn_post。 candidates >= topn_post;使用 MMR 时,还需要满足candidates >= mmr.top_n >= topn_post。length_norm.field、decay.field和mmr.vector_column能在当前查询作用域中解析。- 仅在 Fusion + Rerank 模式下使用
blend。
适用场景
后处理适合在相关性分数之上叠加业务策略,例如:
- 按发布时间、距离、价格做新鲜度或距离衰减
- 对过长文本降权
- 丢弃低于阈值的低相关结果
- 融合 Fusion 分数与 Reranker 分数
- 用向量相似度做 MMR 去重和多样性选择
三种检索模式都可以配置后处理:
| 模式 | 后处理主分数 | blend |
|---|---|---|
| Fusion only | 内部 Fusion 分数 | 不支持 |
| Pure Rerank | Reranker 原始 __RANK |
不支持 |
| Fusion + Rerank | Reranker 原始 __RANK |
支持 |
使用示例
Fusion + Rerank + Post-Process
该模式先融合多路召回结果,再调用 Reranker 进行模型精排,最后执行完整的后处理流程。由于同时存在 Fusion 分数和 Reranker 分数,因此可以使用 blend。
1SELECT id, title, content, publish_time
2FROM documents
3ORDER BY hybrid_search(
4 '{
5 "rerank": "RRF",
6 "c": 60,
7 "candidates": 50,
8 "reranker": {
9 "model": "jina-reranker-v2-base-multilingual",
10 "query": "苹果公司市值",
11 "text_columns": ["title", "content"]
12 },
13 "post_process": {
14 "min_score": 0.35,
15 "length_norm": {
16 "field": "content",
17 "min_length": 50
18 },
19 "blend": {
20 "fusion_weight": 0.3,
21 "reranker_weight": 0.7
22 },
23 "decay": {
24 "function": "gauss",
25 "field": "publish_time",
26 "origin": "2026-04-20",
27 "scale": "7d",
28 "offset": "1d",
29 "decay": 0.5
30 },
31 "mmr": {
32 "lambda": 0.7,
33 "vector_column": "vec",
34 "distance_type": "cosine",
35 "top_n": 10
36 },
37 "topn_post": 10
38 }
39 }',
40 ann_distance(vec, [0.1, 0.2, 0.3, 0.4]),
41 bm25_score(content, 'any', '苹果公司市值')
42)
43LIMIT 10;
Pure Rerank + Post-Process
Pure Rerank 的候选集来自上游查询,Reranker 完成模型精排后再执行后处理。由于没有 Fusion 分数,因此不能配置 blend。
1SELECT id, title, content, publish_time
2FROM (
3 SELECT d.id, d.title, d.content, d.publish_time, d.vec, a.name AS author_name
4 FROM documents d
5 JOIN authors a ON d.author_id = a.id
6 WHERE d.tenant_id = 100
7 ORDER BY d.publish_time DESC
8 LIMIT 200
9) t
10ORDER BY hybrid_search(
11 '{
12 "candidates": 100,
13 "reranker": {
14 "model": "jina-reranker-v2-base-multilingual",
15 "query": "苹果公司市值",
16 "text_columns": ["title", "content", "author_name"]
17 },
18 "post_process": {
19 "min_score": 0.2,
20 "decay": {
21 "function": "exp",
22 "field": "publish_time",
23 "origin": "2026-04-20",
24 "scale": "7d",
25 "decay": 0.5
26 },
27 "topn_post": 20
28 }
29 }'
30)
31LIMIT 10;
Fusion only + Post-Process
没有 Reranker 时,后处理基于内部 Fusion 分数。此时 decay、mmr、topn_post 最有价值;min_score 可用,但阈值要按 Fusion 分数尺度设置;blend 必须禁止。
1SELECT id, title, content
2FROM documents
3ORDER BY hybrid_search(
4 '{
5 "rerank": "RRF",
6 "c": 60,
7 "candidates": 50,
8 "post_process": {
9 "decay": {
10 "function": "gauss",
11 "field": "publish_time",
12 "origin": "2026-04-20",
13 "scale": "7d"
14 },
15 "mmr": {
16 "vector_column": "vec",
17 "top_n": 20
18 },
19 "topn_post": 10
20 }
21 }',
22 ann_distance(vec, [0.1, 0.2, 0.3, 0.4]),
23 bm25_score(content, 'any', '苹果公司市值')
24)
25LIMIT 10;
查看后处理分数
在 SELECT 列表中增加虚拟列 __RANK,可以查看后处理后的最终排序分数:
1SELECT id, title, __RANK
2FROM documents
3ORDER BY hybrid_search('{...}', bm25_score(content, 'any', '苹果公司市值'))
4LIMIT 10;
| 虚拟列 | 说明 |
|---|---|
__RANK |
最终排序分数。未配置 post_process 且存在 Reranker 时,为 Reranker 原始相关性分数;配置 post_process 时,为后处理后的最终分数。 |
Fusion 原始分数不作为输出列暴露。最终 SQL 仍按 ORDER BY hybrid_search() 对应的 __RANK DESC 排序,再由外层 LIMIT 截断。
执行完成后,应重点核对:
__RANK是否按从高到低的顺序排列。min_score是否过滤了低于阈值的候选。- 时间、距离或价格等字段是否按照
decay规则影响排序。 - MMR 是否降低重复结果,并保留与查询相关的候选。
- 最终条数是否同时受到实际候选数、
topn_post和外层LIMIT的限制。
执行顺序
后处理按固定顺序执行,与 JSON 字段书写顺序无关。用户只需选择启用哪些阶段。
| 顺序 | 阶段 | 作用 |
|---|---|---|
| 1 | length_norm |
按文本长度修正当前分数 |
| 2 | blend |
融合 Fusion 分数与当前分数 |
| 3 | decay |
按时间、距离、价格等字段衰减 |
| 4 | min_score |
丢弃当前分数低于阈值的候选 |
| 5 | mmr |
按相关性和向量多样性选择候选 |
| 6 | topn_post |
截断进入最终 SQL 排序的候选数 |
设计要点:
length_norm、blend、decay先把分数调整成可排序的业务相关性。min_score过滤的是衰减后的分数,而不是原始模型分。mmr基于最终相关性做多样性选择。topn_post必须最后执行,避免过早截断影响过滤和多样性。
有 mmr 时,__RANK 仍是进入 MMR 前经过分数调整和 min_score 过滤后的分数。topn_post 先截断 MMR 选出的集合,最终 SQL 再按 __RANK DESC 排序,因此不同 topn_post 的结果不保证前缀关系。
参数选择建议
| 业务目标 | 建议配置 | 说明 |
|---|---|---|
| 减少长文本因词项较多而获得的额外优势 | length_norm |
按照文本长度降低当前分数 |
| 同时保留 Fusion 和模型精排的信号 | blend |
仅适用于 Fusion + Rerank |
| 优先展示较新、较近或价格更合适的结果 | decay |
根据连续值字段对当前分数进行衰减 |
| 去掉相关性过低的候选 | min_score |
阈值需要按照当前模式的分数尺度设置 |
| 减少内容重复、提高结果多样性 | mmr |
需要指定可用的向量列 |
| 限制后处理输出规模 | topn_post |
配置 post_process 时必填 |
JSON 参数
顶层参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
rerank |
string | Fusion 模式必填 | - | Fusion 方式,取值 RRF / WEIGHT |
c |
int | 否 | 60 |
RRF 参数 |
weights |
array | WEIGHT 模式必填 | - | WEIGHT 模式各路权重 |
candidates |
int | 否 | 10 |
进入 Reranker 或后处理的最大候选行数,必须写在 JSON 顶层 |
reranker |
object | 使用模型精排时必填 | - | Reranker 模型精排配置 |
post_process |
object | 否 | - | 后处理配置;存在时必须能从 Fusion 或 Reranker 获得主分数 |
candidates 范围是 [1, max_candidates]。默认值和上限分别由 hybrid_search_reranker_default_candidates、hybrid_search_reranker_max_candidates 控制,上限默认 4000。不要把 candidates 写进 reranker 对象。
post_process 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
min_score |
double | 否 | - | 过滤低于当前后处理分数的文档,必须为有限数值 |
length_norm |
object | 否 | - | 文档长度归一化 |
blend |
object | 否 | - | Fusion 分数与 Reranker 分数融合;仅 Fusion + Rerank 可用 |
decay |
object | 否 | - | 字段衰减 |
mmr |
object | 否 | - | MMR 多样性选择 |
topn_post |
int | 存在 post_process 时必填 |
- | 后处理输出给最终 SQL 排序的候选条数,范围 [1, candidates] |
post_process 不能是空对象。只要配置了后处理,就必须显式写 topn_post;其余阶段可按需组合。
length_norm 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
field |
string | 是 | - | 文本列名,必须为 VARCHAR / CHAR / TEXT / STRING |
min_length |
int | 否 | 1 |
归一化下限,范围 [1, 1000000] |
计算公式:
1normalized_score = current_score / sqrt(max(text_length, min_length) / min_length)
text_length按 UTF-8 字节长度计算。- NULL 按空串处理。
- 文本越长,当前分数降权越多;短于
min_length的文档按min_length计算,不再额外放大。
Fusion only 模式下,它修正的是 Fusion 分数,更适合 BM25 占比较高、长文本更容易命中的场景。
blend 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
fusion_weight |
double | 是 | - | Fusion 分数权重,范围 [0, 1] |
reranker_weight |
double | 是 | - | Reranker 分数权重,范围 [0, 1] |
计算公式:
1blended_score = fusion_weight * normalize(fusion_score) + reranker_weight * current_score
- 仅 Fusion + Rerank 可用。Fusion only 或 Pure Rerank 配置
blend会报错。 fusion_weight + reranker_weight必须大于0。normalize(fusion_score)在当前候选集内做 min-max 归一化到[0, 1];若所有 Fusion 分数相同,归一化值统一为1.0。current_score是执行到blend时的当前分数,通常已经过length_norm。
decay 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
field |
string | 是 | - | 连续值列名,支持 DATE / DATETIME / INT / BIGINT / FLOAT / DOUBLE / DECIMAL |
origin |
string | 是 | - | 衰减基准值,必须能转换为 field 对应类型 |
scale |
string | 是 | - | 衰减尺度,必须为正值。时间字段支持 d / h / m / s 后缀,数值字段为数字字符串 |
offset |
string | 否 | 0 |
无衰减偏移量,必须非负;单位规则同 scale |
function |
string | 否 | gauss |
衰减函数,取值 gauss / exp / linear |
decay |
double | 否 | 0.5 |
distance == scale 时的衰减因子,范围 (0, 1) |
衰减距离:
1distance = max(abs(field_value - origin) - offset, 0)
衰减函数:
| function | 公式 | 适用说明 |
|---|---|---|
gauss |
(\mathrm{decay}^{(distance / scale)^2}) | 平滑渐降,适合通用搜索 |
exp |
(\mathrm{decay}^{distance / scale}) | 近处衰减更明显,适合时效性内容 |
linear |
(\max(1 - (1 - \mathrm{decay}) \times distance / scale,\ 0)) | 线性下降,超过 scale 后继续降到 0 |
最终分数:
1score_after_decay = current_score * decay_factor
当 distance == scale 时,三种函数的 decay_factor 都等于 decay。默认 decay = 0.5,表示在 scale 处把当前分数降到 50%。调小 decay 会强化降权,调大则放缓降权。字段值为 NULL 时不衰减,decay_factor = 1.0。
mmr 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
lambda |
double | 否 | 0.7 |
相关性与多样性权重,范围 [0, 1] |
vector_column |
string | 是 | - | 向量列名,必须为 ARRAY<FLOAT> 或当前向量检索支持的向量列类型 |
distance_type |
string | 否 | cosine |
本期仅支持 cosine |
top_n |
int | 是 | - | MMR 输出条数,范围 [1, candidates],且必须 >= topn_post |
选择公式:
1argmax doc_i: lambda * relevance_score_i - (1 - lambda) * max_similarity(doc_i, selected_docs)
cosine_distance = 1 - dot(a, b) / (norm(a) * norm(b))max_similarity使用1 - cosine_distancelambda越大越偏向相关性,越小越偏向与已选文档不相似- 候选行数小于
top_n时,输出实际候选行数 - 任一候选向量为 NULL、零向量或维度不一致时查询报错
数量约束
| 约束 | 说明 |
|---|---|
candidates >= topn_post |
后处理输出不能超过进入后处理的候选上限 |
candidates >= mmr.top_n >= topn_post |
先由 MMR 选出 top_n 个候选,再由 topn_post 截断 |
SQL LIMIT 不参与约束 |
外层 LIMIT 在最终排序后再截取 |
实际候选数小于配置值时,以实际数量为准,不报错。需要稳定的 Pure Rerank 输入顺序时,应在子查询中显式 ORDER BY / LIMIT。
字段解析
length_norm.field、decay.field、mmr.vector_column 按 hybrid_search() 所在查询作用域的输出列名解析,规则与 reranker.text_columns 相同。
- 支持子查询、JOIN、CTE、UNION 的输出列。
- 存在同名列时,应在子查询中使用别名消歧。
- 字段可以不出现在最终
SELECT列表中,但仍会参与后处理计算。 - 找不到列时报错:
Unknown column 'xxx' in post_process.<field>。
常见问题
配置了 post_process 但没有生效
确认 JSON 顶层存在非空 post_process,并且已经设置 topn_post。可用 EXPLAIN 查看后处理概要,例如:
1VHYBRID_SEARCH_RERANK_NODE
2 mode: fusion_rerank
3 candidates: 50
4 reranker: model=jina-reranker-v2-base-multilingual, text_columns=[title, content]
5 post_process: min_score, length_norm(field=content), blend, decay(field=publish_time,function=gauss), mmr(vector_column=vec,distance_type=cosine,top_n=10), topn_post=10
blend 报错
blend 只适用于 Fusion + Rerank。Fusion only 会报缺少 Reranker 分数,Pure Rerank 会报缺少 Fusion 分数。
min_score 过滤后结果为空
这是正常行为,不会报错。请按当前模式的分数尺度调整阈值:Reranker 分数和 Fusion 分数的范围不同。
MMR 查询报错
检查 vector_column 是否存在、是否为向量类型,以及候选向量是否为 NULL、零向量或维度不一致。distance_type 目前只能是 cosine。
最终条数不是 topn_post
topn_post 只控制进入最终 SQL 排序的候选数。外层 LIMIT 还会再次截断;min_score 过滤后实际候选数也可能更少。
能力速查
| 能力 | Fusion only | Pure Rerank | Fusion + Rerank |
|---|---|---|---|
length_norm |
支持 | 支持 | 支持 |
blend |
不支持 | 不支持 | 支持 |
decay |
支持 | 支持 | 支持 |
min_score |
支持 | 支持 | 支持 |
mmr |
支持 | 支持 | 支持 |
topn_post |
必填 | 必填 | 必填 |
评价此篇文章
