概述
混合搜索在 Fess 中结合了传统的关键词搜索(BM25)与语义(向量)搜索,并通过 Rank Fusion 将两组结果合并,以生成更准确、更相关的排名。Rank Fusion 会将多个搜索器的结果整合为单一的优化排名。
在 Fess 15.9 中,语义搜索(内容分块 + 向量搜索)作为核心功能提供。启用后,语义搜索器会自动 注册到 Rank Fusion 中。有关如何配置,请参阅 语义搜索(内容分块 + 向量搜索)。
Fess 的 Rank Fusion 功能可以整合多个搜索结果, 提供更精确的搜索结果。
什么是 Rank Fusion
Rank Fusion 是一种将多个搜索算法或评分方法(例如关键词/BM25 与语义/向量搜索)的结果 组合起来,生成单一优化排名的技术。
主要优点:
结合不同算法的优势
提高搜索精度
提供多样化的搜索结果
支持的算法
Fess 支持 RRF(Reciprocal Rank Fusion)算法进行 Rank Fusion。
RRF (Reciprocal Rank Fusion)
RRF 通过对每个搜索结果中文档排名的倒数求和来计算分数。 当一个文档被多个搜索器检索到时,其各项分数会被累加。
计算公式:
k: 调整排名影响程度的常数参数(默认值:20)rank(d): 文档 d 在各搜索结果中的排名(从 0 开始)Σ: 对文档 d 出现的所有搜索器求和
Note
由 Fess 执行融合时(默认),融合算法固定为 RRF,也不支持按搜索器设置权重。各搜索器的贡献 会以相同的权重进行合计,唯一能够调整排名倾向的设置是 rank.fusion.rank_constant。 由搜索引擎执行融合时,可以使用其他组合方式,并可按搜索器指定权重(请参阅 在搜索引擎中执行 Rank Fusion)。
配置
fess_config.properties
基本配置:
| 属性 | 默认值 | 说明 |
|---|---|---|
rank.fusion.window_size | 200 | 从各搜索器中获取用于融合的最大结果数量。必须 >= paging.search.page.max.size × 2(默认为 200);若设定值低于此最小值,将自动提升至该最小值(启动时会输出 WARN 日志)。 |
rank.fusion.rank_constant | 20 | RRF 计算公式中的常数 k。值越大,高排名与低排名结果之间的分数差越小。 |
rank.fusion.threads | -1 | 并行运行多个搜索器的固定线程池的线程数。指定 0 或以下时,将自动使用 可用 CPU 核心数 × 3 ÷ 2 + 1(由于是整数运算,小数部分会被舍去。例如:4 核 → 7,5 核 → 8)。 |
rank.fusion.score_field | rf_score | 用于存储融合后分数的结果文档字段名。 |
Note
配置的生效时机
以上 4 项配置的更改均需要重启 Fess 才能生效。从 fess_config.properties 读取的值会被缓存在 JVM 内,因此在运行过程中改写该文件不会生效。
另外,rank.fusion.window_size 仅在启动时读取一次,rank.fusion.threads 则在创建线程池时读取。线程池是在注册了 default 以外的搜索器 (例如语义搜索器)时创建的,因此在语义搜索未启用的情况下,线程池本身不会被创建。
JVM 系统属性
使用的搜索器通过 JVM 系统属性指定。 在 fess.in.sh 中添加如下内容:
如果是 fess.in.bat,则按如下方式添加:
此属性的行为如下:
以 JVM 选项形式设置,而非在
fess_config.properties中配置。键名请直接指定rank.fusion.searchers本身。其他设置中常用的-Dfess.config.或-Dfess.system.前缀形式(例如-Dfess.config.rank.fusion.searchers)不会被识别。除了 JVM 选项之外,也可以在管理界面“系统 > 常规”的“系统属性”栏中,以
rank.fusion.searchers=default,semantic_chunk这样的形式写成一行。 但该栏中的值仅在同名系统属性尚未设置时才会被应用。 因此,若已通过-D指定,则 JVM 选项优先;而要更改已经应用的值, 需要重启 Fess。default是执行标准关键词搜索的搜索器,始终可用。搜索器的名称由其实现类名去掉末尾的
Searcher,再转换为小写蛇形命名(snake_case)得来 (SemanticChunkSearcher→semantic_chunk)。核心集成的语义搜索器 (语义搜索(内容分块 + 向量搜索))注册的名称为semantic_chunk。若未指定此属性,将使用所有已注册的搜索器。若指定的名称与任何已注册搜索器均不匹配,则仅使用
default搜索器。如果您使用核心集成的语义搜索器(语义搜索(内容分块 + 向量搜索)),通常完全不需要设置此属性。结果融合仅在可用搜索器为 2 个或以上时执行。若只有 1 个搜索器可用,则不进行融合,直接返回普通搜索结果。
Warning
如果您此前在 Fess 15.7 或更早版本中使用过 fess-webapp-semantic-search 插件,可能曾被 告知要将此属性设置为 -Drank.fusion.searchers=default,semantic。该插件将其搜索器注册为 名称 semantic,这与 15.9 中引入的核心集成搜索器名称 semantic_chunk 是不同的 搜索器。如果您原样将这个 15.7 时代的设置带入 15.9,允许列表中将永远不包含 semantic_chunk,导致核心集成的语义搜索(内容分块 + 向量搜索)完全无法工作——Fess 会静默地继续返回普通关键词搜索结果(启动时会记录一条警告日志,但每次请求的排除行为本身仅以 DEBUG 级别记录)。如果您的配置中指定了 default,semantic,请移除该设置,或为其添加 semantic_chunk。详情请参阅 语义搜索(内容分块 + 向量搜索) 中的“从 15.7 及更早版本迁移”一节。
在搜索引擎中执行 Rank Fusion
设置 rank.fusion.engine.enabled=true 后,融合将由 OpenSearch 而非 Fess 执行。 各搜索器的查询会合并为一个搜索请求(hybrid 查询),并由 OpenSearch 的搜索管道对分数进行 归一化和组合。由于融合在一次请求中完成,总命中数量和分面数量也反映融合后的结果集。
此功能需要 OpenSearch 的 neural-search 插件。标准发行版的 OpenSearch 和 ghcr.io/codelibs/fess-opensearch 镜像中已包含该插件,但 minimal 发行版中不包含。
配置写入 fess_config.properties(更改后需要重启 Fess 才能生效):
| 属性 | 默认值 | 说明 |
|---|---|---|
rank.fusion.engine.enabled | false | 设为 true 时由搜索引擎执行融合。 |
rank.fusion.combination.technique | rrf | 分数的组合方式:rrf、arithmetic_mean、geometric_mean 或 harmonic_mean。对于 rrf,rank.fusion.rank_constant 会用作常数 k,计算公式与由 Fess 执行融合时相同。 |
rank.fusion.normalization.technique | min_max | 组合前对分数进行归一化的方式:min_max、l2 或 z_score。rrf 不使用此设置。 |
rank.fusion.combination.weights | (空) | 各搜索器的权重,以逗号分隔的 名称:权重 对指定(例如 default:0.7,semantic_chunk:0.3)。每个权重必须在 0.0 到 1.0 之间,权重之和必须为 1.0,并且必须指定参与融合的所有搜索器。为空时各搜索器权重相等。不满足这些规则的值会输出 ERROR 日志,该次搜索将由 Fess 执行融合。 |
rank.fusion.pagination_depth | 1000 | 每个搜索器在每个分片上提供给融合的结果数量。它限制了客户端可以翻页的深度,以及参与融合的文档范围。由搜索引擎融合的搜索最多可以翻页到这么多条结果。发送给搜索引擎的值不会超过 indexer.max.result.window.size。 |
Note
z_score 归一化只能与 arithmetic_mean 组合使用。这是 neural-search 插件的限制。 如果与 geometric_mean 或 harmonic_mean 组合,Fess 会拒绝该组合,输出指明这两个设置的 ERROR 日志,并由 Fess 对该次搜索执行融合。
在以下情况下,不会由搜索引擎执行融合,而是改由 Fess 融合结果。
包含高级搜索参数(
as.*)的搜索指定了排序、位置信息搜索或相似文档搜索的搜索。这些搜索会完全跳过语义搜索,因此结果仅来自 关键词搜索(请参阅 语义搜索(内容分块 + 向量搜索))。
rank.fusion.combination.weights的值不满足上述规则时z_score归一化与geometric_mean或harmonic_mean组合时(会输出指明这两个设置的 ERROR 日志)搜索引擎完全无法执行融合请求时(无法识别
hybrid查询或搜索管道的处理器,例如缺少 neural-search 插件或其版本过旧)。每次这样的搜索都会输出 WARN 日志。下一次搜索会再次请求 OpenSearch 执行融合,因此安装或升级插件后,无需重启 Fess 即可恢复由 OpenSearch 执行融合。
融合请求因其他原因失败时(无效的查询、已关闭的索引、分片故障等),与不由搜索引擎执行融合时一样, 仅针对该次搜索进行报告,下一次搜索会再次由搜索引擎执行融合。
由搜索引擎执行融合时,请注意以下几点。
rank.fusion.timeout不适用。查询的嵌入会在发送搜索请求之前同步计算,因此如果嵌入提供商 响应缓慢或无响应,搜索会一直等待,最长直到提供商自身的超时时间(例如content_chunker.embedding.ollama.timeout)。如果提供商会重试,重试的次数也会累加进来。 例如,内置的opensearch提供商会将content_chunker.embedding.opensearch.timeout(默认60000毫秒)的等待最多重复content_chunker.embedding.opensearch.retry.max(默认3) 次,因此在提供商无响应时,默认情况下搜索可能被延迟 180 秒,再加上重试之间的等待时间。rank.fusion.window_size和rank.fusion.threads仅在由 Fess 执行融合时使用。由搜索引擎融合的搜索最多只能翻页到
rank.fusion.pagination_depth条结果,因为搜索引擎 只融合每个搜索器在每个分片上排名前rank.fusion.pagination_depth的结果。总页数、是否有 下一页以及页码都止于此,请求起始位置(start)大于等于rank.fusion.pagination_depth的 页面时,会返回与其他搜索请求超过index.max_result_window的页面时相同的错误(在 v2 搜索 API 中为 HTTP 400 的invalid_request)。即使在此范围内,从最后一条融合结果之后开始的 页面(例如过时的链接)也会返回空页面。不由搜索引擎融合的搜索仍与以前一样,可以翻页到index.max_result_window。总命中数量低于
rank.fusion.pagination_depth时是精确的。达到或超过该值时,搜索引擎统计的 命中数量可能少于实际匹配的数量,因此会作为下限报告(搜索 API 的record_count_relation为GREATER_THAN_OR_EQUAL_TO)。增大
rank.fusion.pagination_depth可以翻到更深的页面,但每次融合搜索都会按搜索器和分片收集 这么多条结果,因此不仅是深层页面,所有搜索都会变慢。语义搜索器 knn 查询的
k(每个分片的近邻数量)为content_chunker.search.knn.k。 向量搜索即使在相似度较低时也会返回近邻,因此在未设置content_chunker.search.min_score的情况下,总命中数量会包含这些向量搜索的命中,在小规模索引中 可能涵盖用户可见的大部分文档。若要缩小范围,请设置content_chunker.search.min_score(请参阅 语义搜索(内容分块 + 向量搜索))。搜索结果中照常附加
searcher字段,但不会附加rf_score。
与混合搜索的集成
Rank Fusion 在结合关键词搜索与语义搜索的 混合搜索中尤为有效。要使用语义搜索,请在配置内容分块功能之后设置 content_chunker.search.enabled=true。
Warning
content_chunker.enabled 和 content_chunker.search.enabled 等 content_chunker.* 配置属于系统属性,而不是 fess_config.properties。 请将其写入 conf/system.properties,或以 -Dfess.system.content_chunker.search.enabled=true 这样的形式指定为 JVM 选项。 即使写入 fess_config.properties 也不会生效。 另外,content_chunker.search.enabled 仅在启动时评估,因此启用后需要重启 Fess。
详情请参阅 语义搜索(内容分块 + 向量搜索)。
融合结果的确认
要确认 Rank Fusion 是否确实在工作,可以查看搜索结果中附加的以下两个字段。
| 字段 | 内容 |
|---|---|
searcher | 检索到该文档的搜索器名称数组(例如 ["default", "semantic_chunk"])。若两者都包含在内,则表示该文档在关键词搜索和语义搜索中均被命中。 |
rf_score | 通过 RRF 计算出的融合后分数。字段名可通过 rank.fusion.score_field 更改。 |
这两个字段都是在搜索时动态附加的值,不会保存到索引中。 此外,默认情况下它们不会包含在 /api/v2/search 的响应中,若要确认,请在 fess_config.properties 中进行以下设置并重启 Fess:
Note
query.additional.api.response.fields 用于向“允许包含在 v2 搜索 API 响应中的字段” 许可列表中追加条目。如果追加 role 或 virtual_host 等访问控制用字段, 访问控制信息将暴露在搜索 API 的响应中,因此请勿追加这类字段。
对命中数量的影响
由 Fess 执行 Rank Fusion 时,返回的总命中数量并非直接使用主搜索器(注册在首位的 default 搜索器)的数量,而是按如下方式进行修正:
修正值是指:在融合后的前 window_size ÷ 2 条结果中,未包含在主搜索器前 window_size ÷ 2 条结果内的文档数量。也就是说,命中数量会增加仅由语义搜索找到的 文档数量。 因此,即使是相同的查询,启用与不启用混合搜索时的命中数量也可能不同。
另外,当主搜索器的总命中数量以概算值(下限值)返回时,不会进行此修正。 另外,分面(标签等)的数量会原样采用主搜索器的搜索结果。
由搜索引擎执行融合时(请参阅 在搜索引擎中执行 Rank Fusion),总命中数量和分面数量统计的是关键词搜索与 语义搜索两者命中结果的并集。每个搜索器在每个分片上最多提供 rank.fusion.pagination_depth 条 结果,语义搜索器还额外受 content_chunker.search.knn.k 的限制。因此,同一个查询在切换 rank.fusion.engine.enabled 后,命中数量和分面数量都会不同;在小规模索引中,由搜索引擎执行融合 时因为加入了语义搜索的命中,数量往往更大。
使用示例
基本混合搜索
通过关键词搜索计算 BM25 分数
通过语义搜索计算向量相似度
使用 RRF 融合两种结果
生成最终排名
搜索流程:
性能注意事项
内存使用
由于需要保留多个搜索结果,内存使用量会增加。
可使用
rank.fusion.window_size限制融合目标的最大数量。主搜索器(排在首位的default搜索器)最多获取window_size条结果,其他搜索器各获取window_size ÷ 搜索器数量条结果(搜索器数量是包含主搜索器在内的总数,除法会向下取整)。例如,当有 2 个搜索器(
default和semantic_chunk)且window_size=200时,主搜索器获取 200 条、语义搜索器获取 100 条,因此最多会保留 300 条文档。
Warning
rank.fusion.window_size 不能低于 paging.search.page.max.size × 2。 当 paging.search.page.max.size 为默认值 100 时,下限即为 200,这与 rank.fusion.window_size 的默认值相同。也就是说,在默认配置下无法将 window_size 设置为小于默认值。即使设置了更小的值,启动时也会输出 WARN 日志并将其提升至 200。 若要实际减小该值,需要先降低 paging.search.page.max.size,但这同时也会降低搜索界面 和 API 中每页可请求的最大结果数量。
处理时间
由于需要执行多次搜索,响应时间会增加。
使用
rank.fusion.threads设置并行执行的线程数。
Note
由 Fess 执行融合时,主搜索器以外的搜索器最多等待 rank.fusion.timeout 毫秒(默认 10000)。届时仍未响应的搜索器会从该次搜索中排除,搜索结果会被标记为部分结果(超时)。 主搜索器始终会等待至完成。指定 0 或以下时将无限制地等待。由搜索引擎执行融合时,此设置 不适用(请参阅 在搜索引擎中执行 Rank Fusion)。
搜索器故障时的行为
当任一搜索器因异常而失败时,该搜索器的结果会被视为空,在输出 WARN 日志之后, 仅使用其余搜索器的结果继续进行融合。搜索请求本身不会变成错误。
但查询语法错误(InvalidQueryException)和分页上限超出 (ResultOffsetExceededException)除外,它们会直接作为错误返回。 此外,在不进行融合的深层页面(起始位置 × 2 大于等于 rank.fusion.window_size 的位置), 主搜索器中发生的异常会直接作为搜索请求的错误返回。
语义搜索器在无法连接到嵌入提供商或嵌入处理失败时,会返回空结果。 这种情况下同样不会产生错误,而是仅返回关键词搜索的结果。
故障排除
搜索结果与预期不符
症状:Rank Fusion 后的结果与预期不符
检查事项:
确认
searcher字段(请参阅“融合结果的确认”)。如果所有文档都仅为["default"],则说明语义搜索器没有返回结果。确认语义搜索是否被跳过。除了包含短语、通配符等搜索语法的查询之外,在排序搜索、位置信息搜索、 相似文档搜索时,语义搜索器也不会返回结果,仅返回关键词搜索的结果。 跳过条件的详细信息请参阅 语义搜索(内容分块 + 向量搜索)。
分别确认各搜索类型的结果
调整
rank.fusion.rank_constant的值在翻页较深的页面(
起始位置 × 2大于等于rank.fusion.window_size的位置,默认情况下 为第 101 条之后)不会执行融合,仅使用主搜索器进行搜索。若希望在更多页面上获得融合结果, 请增大rank.fusion.window_size。由搜索引擎执行融合时没有此分界, 最多rank.fusion.pagination_depth条结果以内的所有页面都会被融合(请参阅 在搜索引擎中执行 Rank Fusion)。
搜索缓慢
症状:启用 Rank Fusion 时搜索变慢
解决方法:
调整
rank.fusion.threads:减小
rank.fusion.window_size。但由于不能低于下限 (paging.search.page.max.size × 2),在默认配置下需要将以下两项成对设置:请注意,每页可请求的最大结果数量也会随之下降。设置后需要重启。
由搜索引擎执行融合时,减小
rank.fusion.pagination_depth。请注意,融合结果可以翻页的 深度也会随之变浅。设置后需要重启。
内存不足
症状:发生 OutOfMemoryError
解决方法:
按照“搜索缓慢”中的相同步骤减小
rank.fusion.window_size增加 JVM 堆大小
参考信息
语义搜索(内容分块 + 向量搜索) - 语义搜索(内容分块)的配置
脚本概述 - 脚本概述
搜索相关配置 - 高级搜索设置
AI 搜索(RAG)与 LLM 集成概述 - LLM 集成指南(语义搜索)