Milvus Function Chain API 设计解析:面向搜索重排的三级流水线架构(L0/L1/L2 Rerank)
2026/9/10 19:50:19 网站建设 项目流程

Milvus Function Chain API 设计解析:面向搜索重排的三级流水线架构(L0/L1/L2 Rerank)

【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus

导读

本文深度解读 Milvus 官方设计文档《MEP: Function Chain API for Search Rerank》(20260624-function-chain-api.md),系统阐述 Function Chain 这一类型化、有序、分阶段的搜索打分与重排流水线:它如何以结构化 protobuf 而非不透明 JSON 字符串下发重排计划、如何在 L0/L1/L2 三个分布式执行边界上运行、如何通过$score系统虚拟列统一重排语义,以及 SDK 如何用FunctionChain/fnDSL 把用户意图编译为可校验的算子序列。读完本文,你将掌握 Function Chain 的完整设计脉络、Python DSL 与 protobuf 数据结构、各阶段算子能力边界、内置表达式(decay/num_combine/round_decimal/rerank_model)的参数细节,以及仓库中对应的源码实现路径,可直接用于设计或落地基于 Milvus 的多因子排序与模型重排方案。

背景与动机:为什么需要 Function Chain

Milvus 此前已有function_score与 ranker 参数两类传统重排入口,它们对预定义的打分公式足够有用,但无法表达由多个重排步骤有序组合而成的通用计划

文档给出一个典型的多因子排序诉求:

  1. 基于时间戳字段计算新鲜度(freshness)分数;
  2. 将原始 ANN 分数、新鲜度、热度(popularity)组合;
  3. 可选地调用外部重排模型做文本相关性打分;
  4. 重写最终分数;
  5. 排序并裁剪候选集。

把这些诉求表达为类型化算子序列(typed operations)后,Milvus 可以获得:

  • 确定的执行顺序(deterministic execution order);
  • 类型化的嵌套参数,杜绝 JSON-in-string 编码带来的类型歧义;
  • 显式的字段依赖分析(explicit field dependency analysis),支撑内部按需取数;
  • 一致的$score语义,最终分数统一通过既有 score/distance 字段回传;
  • 为未来扩展更多阶段与算子预留空间

从源码结构看,internal/util/function/chain包就是这套运行时的主体:expr/子目录存放各内置表达式(decay_expr.go、num_combine_expr.go、round_decimal_expr.go、rerank_model_expr.go 等),operator_*.go文件则实现mapsortlimit等算子。

阶段模型:L0 / L1 / L2 三级分布式重排边界

Function Chain 的核心创新在于把重排拆分到三个不同的分布式执行边界

阶段执行边界首版支持的算子
L0worker QueryNode 内每个 segment 结果上、跨 segment 归约之前map
L1每个 worker QueryNode完成自身跨 segment 归约之后、结果返回 shard leader 之前mapsortlimit
L2Proxy完成分布式/全局归约之后校验规则允许的通用函数链运行时算子

完整的分布式流水线如下:

segment ANN result -> L0 per-segment chain -> worker QueryNode cross-segment reduce / PK dedup / group-aware merge -> L1 per-worker merged-candidate chain -> shard-leader reduce -> Proxy global reduce -> L2 Proxy chain -> final projection

需要特别强调的是:L1 只在每个 worker QueryNode 的归约结果上执行恰好一次,不会在 shard leader 处重复运行。该边界使 L1 能在单个 worker 管辖的多个 segment 之间横向比较候选,同时保持现有分布式 reducer 与线协议不变。从源码看,l1_function_chain.go 中的applyL1Rerank正是 L1 在 worker 侧的 Go 归约实现入口。

文档还明确划定了首版不包含(Non-Goals)的内容:

  • hybrid/advanced search 不支持function_chains
  • 不支持 insert/upsert/ingestion 阶段的函数链;
  • 不在 shard-leader 归约边界或多级 QueryNode 归约层级上执行 L1;
  • L1 首版不支持filterselectgroup_by算子;
  • 不支持任意用户自定义表达式语言;
  • 中间链变量不作为用户可见结果字段返回;
  • 不替换function_score与 legacy rank 参数;
  • 外部模型调用不在客户端执行。

阶段枚举同时为未来的 ingestion、pre-processing、post-processing 预留了位置;普通 Search 在每个L0_RERANK/L1_RERANK/L2_RERANK上至多接受一条链。源码中的阶段常量定义在 types.go,如StageL0Rerank = "L0_rerank"StageL1Rerank = "L1_rerank"StageL2Rerank = "L2_rerank"

公开接口:Python DSL 与 Search API

PyMilvus DSL 构建链

用户通过FunctionChainFunctionChainStagecol以及fn下的辅助函数构建一条链。以下示例(来自设计文档)演示如何组合新鲜度衰减、加权分数合并、小数取整、排序与截断:

from pymilvus import FunctionChain, FunctionChainStage from pymilvus.function_chain import col, fn chain = ( FunctionChain(FunctionChainStage.L2_RERANK, name="fresh_popular_rerank") .map( "freshness", fn.decay( col("published_at"), function="exp", origin=current_time, scale=86400, offset=0, decay=0.5, ), ) .map( "$score", fn.num_combine( col("$score"), col("freshness"), col("popularity"), mode="weighted", weights=[0.7, 0.2, 0.1], ), ) .map("$score", fn.round_decimal(col("$score"), decimal=4)) .sort(col("$score"), desc=True, tie_break_col=col("$id")) .limit(10) ) client.search( collection_name="articles", data=[query_vector], anns_field="embedding", search_params={"metric_type": "IP"}, limit=100, output_fields=["title"], function_chains=chain, )

模型重排示例

外部模型重排同样是一条 L2 链:

chain = ( FunctionChain(FunctionChainStage.L2_RERANK, name="model_rerank") .map( "$score", fn.rerank_model( col("doc"), queries=["renewable energy developments"], provider="voyageai", model_name="rerank-2.5", truncation=True, max_client_batch_size=128, ), ) .sort(col("$score"), desc=True, tie_break_col=col("$id")) )

文档强调:外部模型的凭据由 Milvus 服务端通过 provider 配置解析,SDK 请求不应携带 API Key

Search API 与冲突校验

普通 Search 通过function_chains参数接收链(单个或列表均可):

client.search(..., function_chains=chain) client.search(..., function_chains=[chain])

SDK 与服务端校验会拒绝以下歧义或不受支持的组合:

  • function_chains与 SDKranker/ protofunction_score同时使用;
  • 普通 Search 出现 L0/L1/L2 之外的阶段;
  • hybrid 或 advanced search 使用function_chains
  • Function rerank 与 Search Iterator 或order_by同时使用;
  • L1 与 search aggregation 同时使用。

仓库中 function_chain_validator.go 正是 Proxy 侧校验的实现:validateFunctionChainSearchRequest返回"function_score and function_chains cannot be used together""function_chains is not supported for hybrid search yet"splitFunctionChainsByStage则负责按阶段把链拆分为 L2 链(留在 Proxy)与 L0/L1 链(随物理计划下发 QueryNode)。

Protobuf 契约:链是有序逻辑计划

与“整条链编码为 JSON 字符串”的方案不同,Function Chain 以结构化 protobuf 下发。核心消息结构如下:

enum FunctionChainStage { FunctionChainStageUnspecified = 0; FunctionChainStageIngestion = 1; FunctionChainStagePreProcess = 2; FunctionChainStageL0Rerank = 3; FunctionChainStageL1Rerank = 4; FunctionChainStageL2Rerank = 5; FunctionChainStagePostProcess = 6; } message FunctionChain { string name = 1; FunctionChainStage stage = 2; repeated FunctionChainOp ops = 3; } message FunctionChainOp { string op = 1; FunctionChainExpr expr = 2; repeated string inputs = 3; repeated string outputs = 4; map<string, FunctionParamValue> params = 5; } message FunctionChainExpr { string name = 1; repeated FunctionChainExprArg args = 2; map<string, FunctionParamValue> params = 3; } message FunctionChainExprArg { oneof arg { FunctionChainColumnArg column = 1; FunctionParamValue literal = 2; } } message FunctionChainColumnArg { string name = 1; } message FunctionParamValue { oneof value { bool bool_value = 1; int64 int64_value = 2; double double_value = 3; string string_value = 4; FunctionParamArray array_value = 5; FunctionParamObject object_value = 6; bytes bytes_value = 7; } } message FunctionParamArray { repeated FunctionParamValue values = 1; } message FunctionParamObject { map<string, FunctionParamValue> fields = 1; }

SearchRequest通过repeated schema.FunctionChain function_chains = 24;携带链;hybrid request proto 虽为未来支持保留字段,但首版执行会直接拒绝。

内部表示:ChainRepr 与依赖分析

公开 proto 进入服务端后,首先由 chain 包转换为与调用方无关的内部表示ChainRepr。该结构在 repr.go 中定义:

type ChainRepr struct { Name string Stage string Operators []OperatorRepr Info ChainReprInfo } type ChainReprInfo struct { RequiredInputs []string WrittenNames []string Ops []OperatorReprInfo } type OperatorReprInfo struct { Type string ReadNames []string WriteNames []string }

关键设计是:ChainRepr.Info.RequiredInputs只表达“链在某条先前算子写入之前就读取了这些名字”的结构性依赖,它不决定某个名字究竟是 schema 字段、运行时系统值还是非法名称——该分类权归调用方所有ProtoChainToRepr负责 proto→repr 转换(含 nil 检查、空算子名校验、stage 枚举转换、未知算子拒绝),RefreshInfo通过一次扫描重建RequiredInputs/WrittenNames,并从GetOperatorFactory校验算子类型是否已知。

FuncChainFromRepr/FuncChainFromReprWithContext再从ChainRepr构建可执行的FuncChain:前者使用空FunctionBuildContext,后者用于需要运行时上下文(如模型重排)的构造场景。IsFunctionChainSystemName$前缀识别系统名。

$score$id:系统虚拟列语义

$score系统虚拟列而非集合字段,其运行时行为如下:

  1. 重排输入构建时,$score从当前搜索结果 score/distance 初始化;
  2. 函数可通过col("$score")读取它;
  3. map("$score", expr)覆盖当前分数寄存器;
  4. sort(col("$score"), desc=True)按重写后的分数排序候选;
  5. 最终$score通过既有结果 score/distance 字段序列化;
  6. SDK 用户看到的就是普通 hit distance/score 值。
层级表示形式
Python DSL"$score"
ProtoFunctionChainColumnArg.name = "$score"
运行时score register / DataFrame 列
搜索结果既有 distance/score 字段

$id作为只读系统值用于 tie-break。在公开的 L0/L1 链中,可读的系统列只有$id$score,可写的系统列只有$score;用于 segment 偏移、分组、元素元数据或 L1 provenance 的内部列不属于公开链命名空间,绝不允许出现在结果字段中。Proxy 侧validateL2RerankSystemInput/validateL2RerankSystemOutput在 function_chain_validator.go 中落实了这一约束。

算子语义

map

map(output, expr)计算表达式并把结果写入output

  • L0/L1/L2 均可写入freshness这类临时变量供链内后续算子使用;
  • L0 与 L1 还可在阶段局部 DataFrame 内覆写普通集合列;
  • output可以是可写系统值$score
  • 首版重排不允许$id或未知$xxx值。

sort

sort(by, desc=True, tie_break_col=None)对当前候选块排序:

  • by编码为 op input 与参数;
  • tie_break_col可选,同样编码为 input;
  • 排序是显式的:一旦链中出现了显式排序,Milvus 不再根据向量 metric 类型推断排序方向。

limit

limit(limit, offset=0)在前序算子之后裁剪每个查询块。它是用户计划的一部分,Search 不会隐式追加公开的limit/offset算子。

对 L1 而言,limit每个 worker、每个 query-vector 块独立应用的中间候选预算,并非最终客户端 Search limit。被某 worker 丢弃的候选不会在 shard leader 或 Proxy 处重现,因此L1limit会降低召回率,用户必须按预期的 worker 扇出与召回目标来设置它。

当 L1 链同时含用户sortlimit时,limit 遵循用户定义的顺序。用户整条链执行完毕后,QueryNode 会追加一个非公开的规范化排序:按$score降序、$id升序作为 tie-break。该内部步骤不属于公开链算子,它恢复下游 shard/Proxy reducer 所需的排序契约,且不会改变用户 L1limit已选出的候选集合

内置表达式

decay

由一个数值输入列计算数值衰减分数。参数包括:

  • functiongaussexplinear
  • origin:原点
  • scale:尺度
  • offset:偏移
  • decay:衰减系数

源码层面,decay_expr.go 定义了GaussFunction/LinearFunction/ExpFunction常量与decayReScorer计算函数,注释明确其输出为[0, 1]的纯衰减因子,$score的组合由链中后续的num_combine节点负责,列映射由map算子完成。

num_combine

组合两个及以上数值输入,支持以下模式:

  • multiply
  • sum
  • max
  • min
  • avg
  • weighted(要求每个输入对应一个数值权重)

round_decimal

将单个 Float32 分数列四舍五入到[0, 6]位小数。

rerank_model

对文本列调用外部重排模型 provider,首版仅在 L2 重排阶段可运行。必填参数:

  • queries:每个搜索 query 块对应一条 query;
  • provider 参数,如providermodel_namemax_client_batch_size及 provider 专属选项。

Provider 凭据与 endpoint 默认值在 Milvus 服务端通过既有 function provider 配置解析。仓库中 rerank_model_expr.go 与其测试对应此表达式;模型 client 集中在internal/util/function/models/下(如 voyageai、cohere、openai、tei、siliconflow 等)。

输入、写入与投影语义

Function Chain 将链执行命名最终结果投影分离:

expr-based op read names = column references in FunctionChainExpr.args non-expr op read names = FunctionChainOp.inputs op write names = FunctionChainOp.outputs final result projection = Search-owned output projection

以文档示例为准:

FunctionChain(FunctionChainStage.L2_RERANK) \ .map("freshness", fn.decay(col("published_at"), ...)) \ .map("$score", fn.num_combine(col("$score"), col("freshness"), mode="sum")) \ .sort(col("$score"), desc=True)

依赖分析会得出:

  • 先前写入之前必需的输入:published_at$score
  • 写入的名字:freshness$score
  • freshness不会从集合 schema 取数(因为它被前序算子写入);
  • published_at即使不在用户output_fields中,也会为重排而内部取数
  • 最终响应只返回$id、最终$score与用户请求的输出字段。

中间变量(如freshness)不会返回给用户。

端到端执行流与集成设计

高层 Search 流程

SearchRequest.function_chains -> SDK serialization -> Proxy request validation and stage split -> L0/L1 serialized into PlanNode.querynode_function_chains -> L2 retained as Proxy rerank metadata -> worker QueryNode exports per-segment Arrow DataFrames -> L0 executes on each segment DataFrame -> worker QueryNode cross-segment reduce -> L1 materializes required fields and executes on the merged DataFrame -> shard-leader and Proxy reductions -> L2 rerankOperator builds and executes a DataFrame chain -> Search-owned final projection

值得注意的实现事实:

  • L1不需要新增 protobuf 传输:它复用PlanNode.querynode_function_chains字段与 L0 一起下发,靠FunctionChain.stage区分。
  • L2 被当作一种 Proxy rerank source,复用既有rerankOperator,而非新增独立的functionChainOperator;L1 属于 worker QueryNode 的 Go 归约流程,也不新增 Proxy 管线算子。
  • 文档给出的 L2 集成路径为:SearchResultData -> chain.FromSearchResultData(..., neededFields) -> build FuncChain from rerank metadata -> ExecuteWithContext -> chain.ToSearchResultDataWithOptions(...),链构建器按 rerank metadata 类型分发:legacy function score、legacy rank params、公开 function chain(FuncChainFromRepr/FuncChainFromReprWithContext)。

Proxy L2 输入规划

普通 Search L2 重排时,Proxy 对每个必需输入分类:

  1. $score$id为运行时系统输入;
  2. 其他名字必须解析为受支持的集合 schema 字段;
  3. 未知的非系统名被拒绝;
  4. 不受支持的$xxx系统输入被拒绝;
  5. 前序算子写出的临时变量不从 schema 取数。

首版受支持的 schema 输入字段类型:

  • Bool
  • Int8 / Int16 / Int32 / Int64 / Timestamptz
  • Float / Double
  • String / VarChar / Text

不受支持的输入字段类型包括:向量字段、JSON、Array、Geometry 以及 dynamic field 子键。validateFunctionChainInputField通过chain.ToArrowType校验字段类型可映射为 Arrow 类型,否则拒绝。

QueryNode L0/L1 准备

Proxy 将 L0 与 L1 链一起序列化进物理计划。worker QueryNode 解析planpb.PlanNode一次,把公开链转为ChainRepr,按各自 stage 校验每条链,并分别规划 L0 与 L1 的 schema 输入。Legacyfunction_score也在该准备阶段被规范化为包含其 scorer 与解析后的 score-combine 模式的 L0 prepared 配置。

  • 只有公开 L0 输入字段随每个 segment 搜索结果在归约前导出;prepared legacy boost score 只需既有 segment 偏移,不向导出追加集合字段。
  • L1 输入字段在跨 segment 归约后、为幸存的 worker 候选物化。
  • L0 保持 segment 本地行为,只接受map;L1 接受mapsortlimit。两个阶段均可写普通临时列与集合列,系统列中只有$score可写,$id与其他$xxx只读。

L1 输入物化与延迟投影(late materialization)

worker 跨 segment reducer 产出合并 DataFrame 及并行的 source map

type segmentSource struct { InputIdx int SegOffset int64 OriginalIdx int } type mergeResult struct { DF *chain.DataFrame Sources [][]segmentSource }

仅 L1 需要的普通标量字段不会随所有 segment ANN 候选导出,也不在 heap merge 中复制。L1 执行前,QueryNode 展平合并结果的 source map,仅从源 segment 读取幸存候选:

Sources[query chunk][row].{InputIdx, SegOffset} -> ordered segment field read for L1 required field IDs -> Arrow RecordBatch in merged-row order -> L1 input DataFrame with mergedDF chunk sizes

物化必须保持 Arrow 类型、field ID/类型/nullability 元数据、null 值、chunk 数与行数;缺失字段、非法源索引/偏移、畸形 Arrow 元数据、DataFrame/source 形状不匹配都属于内部结果契约失败而非非法请求。

L1 的sort/limit会重排或裁剪mergeResult.DF,直接变换会破坏行到 segment 的映射。因此 L1 执行前 QueryNode 会为每个 chunk 附加隐藏的 per-chunk source-index 列l1_function_chain.go中常量l1SourceIndexColumn = "$l1_source_index"),token 是该行在当前 chunkSources切片中的下标。由于所有 Function Chain 行算子都会变换每一列,token 会随用户map/sort/limit及内部规范化排序跟随每一行;执行后用变换后的 token 重建Sources,再移除隐藏列。

PK 不能作为 provenance token:element-level search 可能包含多条同 PK 的行,按 PK 重建 source 会产生歧义。隐藏 token 仅限内部:公开链不可读,也绝不能出现在SearchResultData.FieldsData或最终输出投影中。延迟物化前必须满足五项不变量(chunk 数一致、每行恰有一个 source 条目、token 非空且类型/范围合法、重建顺序与最终 DataFrame 一致、token 与 L1 临时输入列在序列化前移除)。

Requery 与字段可用性

Function Chain 输入字段走既有 rerank metadata:

rerankMeta.GetInputFieldNames() rerankMeta.GetInputFieldIDs()

需要 requery 时,requery 算子会包含 rerank 必需字段名,以便在重排执行前构建 DataFrame;最终投影仍只使用用户 output 字段,不暴露内部取来的重排输入。Proxy 的functionChainRerankMeta正是通过这两个 getter 暴露输入字段(function_chain_validator.go)。

请求级规则与校验清单

function_score冲突

function_scorefunction_chains互斥,错误信息为function_score and function_chains cannot be used together——两者都定义重排打分行为,同时使用会使排序语义歧义。SDKranker映射到 legacy rerank/function-score 行为,因此 SDK 在 RPC 前就拒绝ranker+function_chains

阶段唯一性

同一请求中每个FunctionChainStage至多出现一次。一条 L0、一条 L1、一条 L2 可以共存,其执行顺序由阶段固定而非请求列表顺序决定。单阶段需要多步时,应把多个有序算子放进该阶段的同一条链,而不是发送重复链。

L1 兼容性限制

  • L1 不支持 hybrid/advanced search;
  • L1 不支持 Search Iterator(legacy 或 v2);
  • L1 不支持order_by(两者都定义排序行为);
  • L1 不支持 search aggregation;
  • Search 级 group-by 可与 L1map/sort/limit组合(与 L2 兼容性一致),Function Chain 算子可以重排或裁剪分组行,无需重建原始 group count 或group_size契约。

这些校验在执行前完成,保证不受支持的组合不会悄悄产生不完整的分布式结果。

首版校验规则汇总

  1. Function chain proto 不得为 nil;
  2. stage 必须受请求类型支持;
  3. 拒绝重复 stage;
  4. 算子名非空;
  5. 表达式存在时表达式名非空;
  6. 列引用与输入/输出名非空;
  7. expr 参数只能是列引用或受支持字面量;
  8. 参数值必须类型化且可转换为运行时值;
  9. L0/L1/L2 公开输入系统名限定为$id$score
  10. 公开系统输出限定为$score
  11. 非系统必需输入必须是受支持的集合字段;
  12. L0 只接受map;L1 只接受mapsortlimit
  13. 未知算子与函数被拒绝;
  14. 函数必须在其所在 stage 可运行;
  15. 函数专属参数须通过校验;
  16. 外部重排模型 query 数量必须与 query chunk 数量一致;
  17. L1 与 search aggregation 组合被拒绝;
  18. Function rerank 与 Search Iterator(legacy/v2)及order_by组合被拒绝。

文档还指出:“至多一个 sort”“sort 必须在最后”等更严格的排序约束可作为未来选项;首版按用户下发顺序执行用户计划(除非算子自身拒绝),随后仅应用上述内部 L0/L1 reducer 规范化。

可观测性

QueryNode 复用milvus_querynode_function_chain_latency指标统计 L0 与 L1 执行;L1 记录chain_level="l1"及既有 success/failure 状态标签。计时的 L1 阶段包含必需字段物化、链构建与执行、provenance 重建与内部规范化。指标标签必须保持有界:字段名、表达式、集合名、链名不得作为指标标签。

运行时错误走既有类型化错误路径:非法用户计划与不受支持的组合是输入错误;缺失内部字段、畸形 DataFrame、非法源索引、provenance 损坏、结果形状违规是系统/内部失败。为既有类型化错误追加上下文时应使用merr.Wrap/merr.Wrapf,保证原始错误码存活。

文档建议的后续指标方向:按算子/函数类型的链执行延迟、重排内部取数字段数、外部 provider 延迟与按 provider 的错误计数、按校验类别的被拒链请求数。

测试计划要点

仓库中的测试覆盖与文档测试计划一一对应,可直接定位验证:

  • SDK 测试:builder 序列化、类型化参数序列化、col(...)校验、decay/num_combine/round_decimal/rerank_modelhelper 校验、L0/L1/L2 链编码、function_chains+ranker拒绝等。
  • Proxy/chain 规划测试:function_chain_validator_test.go 覆盖function_score冲突、各 stage 重复链拒绝、L0/L1/L2 路由与共存、hybrid/advanced 拒绝、Iterator v2 与order_by冲突、L1 + aggregation 拒绝等。
  • QueryNode L1 测试:l1_function_chain_test.go、search_task_go_reduce_test.go 覆盖map/sort/limit接受与filter/select/group_by拒绝、标量输入(含 null)物化、Int64/string PK 保真、同 PK 元素级结果、用户排序决定 limit 选择、内部规范化排序、Ragged TopKs、类型化内部错误、隐藏 provenance 列与 L1-only 输入不出现在结果字段、延迟指标恰记录一次等。
  • 归约与延迟物化测试:L1 在延迟物化前执行、sort/limit 同步重排mergeResult.Sources、输出字段在重排/裁剪后仍与正确 hit 关联、混合 NQ/topK 合并后按请求切片、group-by 与元素元数据对齐、shard-leader 正确合并多 worker 的归一化 L1 输出、worker 本地 limit 语义显式化。
  • L2 与回归测试buildChainFromMeta构建FuncChain、既有rerankOperator执行 proto 派生链、$scoremap/sort 改变结果顺序与分数、limit更新每 query TopK、requery 后必需字段可用但不出现在响应字段、L0+L1+L2 按阶段顺序执行、既有function_score与 legacy rank 行为不变、rerank_model可选外部 provider 测试(以服务端凭据为门槛)。
  • 端到端测试:Python 与 REST 测试覆盖 L1 分数映射、隐藏标量输入、sort+limit、L0/L1/L2 组合、不兼容性校验、输出字段对齐、worker 本地候选预算语义。测试数据必须能区分 segment 本地 L0、worker 级 L1 与 Proxy 全局 L2,以证明命中的是所选执行边界而非仅最终算术。

文档给出的回归命令示例:

go test -tags dynamic,test -gcflags="all=-N -l" -count=1 ./internal/util/function/chain/... go test -tags dynamic,test -gcflags="all=-N -l" -count=1 ./internal/proxy/... -run 'FunctionChain|Rerank|L1' go test -tags dynamic,test -gcflags="all=-N -l" -count=1 ./internal/querynodev2/tasks/... -run 'FunctionChain|L1|GoReduce'

由于 L1 改变了分布式排序、provenance 与延迟物化,验证还包括完整 Go 测试套件与端到端失败模式追踪;仅一条成功的成功路径搜索不足以证明 source 对齐或 worker 本地 limit 语义正确。SDK 测试从 PyMilvus 仓库或本地 checkout 运行。

安全考虑

外部模型重排会从 Milvus 服务端进程调用第三方服务,安全要求如下:

  1. API 凭据通过既有 provider 配置或凭据库在服务端解析;
  2. SDKfunction_chains请求不应包含原始 API Key;
  3. provider 凭据必须由既有凭据处理路径在日志中脱敏;
  4. provider endpoint 应使用 HTTPS,除非为可信本地测试显式配置;
  5. 发给外部 provider 的请求可能包含用户文本字段,部署方必须将其视为数据出口(data egress)并相应配置 provider;
  6. 超时与批量上限必须防止无界的外部调用。

兼容性与迁移

  • 不含function_chains的既有搜索请求行为不变;
  • 既有function_score与 legacy rank 行为继续受支持;
  • 公开 function chain 为 opt-in;
  • 既有结果 schema 保持不变,最终分数仍通过当前 score/distance 字段暴露。

本 MEP 不引入任何弃用(deprecation)。当用户需要显式有序组合时,可从function_score或 ranker API 迁移到function_chains;首版不提供自动转换

被否决的备选方案

  1. KeyValuePair中以 JSON 字符串编码参数:被否决。链参数可能包含嵌套对象、数组、布尔、整数、浮点与字节,JSON-in-string 会导致迟解析失败、弱类型信息、SDK 行为不一致、更弱的校验错误以及数值类型歧义。FunctionParamValue保证公开计划类型化。
  2. function_score承载全部链行为:被否决。function_score不是有序算子流水线,无法自然表达带显式依赖的多步 map/sort/limit/model。
  3. 为搜索管线新增独立functionChainOperator:首版被否决。Function Chain 本质是另一种 rerank 实现,复用rerankOperator可让 fetch/requery/最终投影与 legacy rerank 保持一致。
  4. 在通用 chain 包中分类必需输入:被否决。只有调用方知道某个名字是 schema 字段、请求负载字段、运行时系统值还是非法值;chain 包只报告结构依赖。

开放问题

  1. Hybrid search 的最佳公开 API 形态:顶层 post-merge 链、每个 sub-search 各自的链,还是两者兼有?
  2. 未来 L0/L1 阶段应允许哪些新增函数与算子?
  3. L1 未来是否应支持 shard-leader 执行模式(worker post-reduce 之外的第二种模式)?
  4. 是否应强制严格算子排序规则,例如至多一个sort且只能作为最后一个排序算子?
  5. 未来 API 是否允许用户显式返回中间变量?
  6. 是否应在 embedding 与 rerank 模型 provider 之间标准化 provider 专属指标?

进一步阅读路径

  • 设计文档原文:docs/design-docs/design_docs/20260624-function-chain-api.md
  • 运行时核心包:internal/util/function/chain(含 repr.go、optimization_plan.go、pruning.go 与各算子/表达式实现)
  • Proxy 校验与规划:internal/proxy/function_chain_validator.go、internal/proxy/search_pipeline.go、internal/proxy/task_search.go
  • QueryNode L0/L1 执行:internal/querynodev2/tasks/l0_function_chain.go、internal/querynodev2/tasks/l1_function_chain.go、internal/querynodev2/tasks/search_task_go_reduce.go、internal/querynodev2/tasks/querynode_function_chain.go
  • 内置表达式:internal/util/function/chain/expr(decay、num_combine、round_decimal、rerank_model 及其测试)
  • 外部模型 provider:internal/util/function/models

【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询