Skip to content

feat: introduce Arrow-based function chain pipeline to replace legacy rerank implementation - #47919

Merged
sre-ci-robot merged 1 commit into
milvus-io:masterfrom
junjiejiangjjj:func-chain
Apr 10, 2026
Merged

feat: introduce Arrow-based function chain pipeline to replace legacy rerank implementation#47919
sre-ci-robot merged 1 commit into
milvus-io:masterfrom
junjiejiangjjj:func-chain

Conversation

@junjiejiangjjj

@junjiejiangjjj junjiejiangjjj commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

#46565
design doc: https://github.com/milvus-io/milvus-design-docs/blob/main/design_docs/20260310-function-chain.md

Adds a new chain package (internal/util/function/chain/) that implements a composable, Apache Arrow-based pipeline for search result post-processing (reranking, scoring, merging, filtering, grouping, etc.). This replaces the legacy rerank utility functions with a DataFrame-oriented approach that provides better performance and extensibility.

Key changes:

  • New chain package with DataFrame abstraction, operator registry, expression engine (decay, score combine, round decimal, rerank model), and operators (merge, sort, filter, limit, select, map, group-by)
  • Refactored search_pipeline.go to use chain-based reranking instead of the legacy rerank package, adding a hybrid_assemble operator for hybrid search
  • Removed legacy rerank utilities (decay_function, rrf_function, weighted_function, util.go, rerank_base) and simplified remaining rerank providers to thin wrappers
  • Added rerank_meta.go in proxy for structured rerank configuration parsing
  • Comprehensive unit tests and benchmarks for the chain package
  • Extended Python and Go integration tests for reranker functions

@sre-ci-robot sre-ci-robot added the do-not-merge/work-in-progress Don't merge even CI passed. label Feb 27, 2026
@sre-ci-robot sre-ci-robot added the size/XXL Denotes a PR that changes 1000+ lines. label Feb 27, 2026
@mergify

mergify Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

@junjiejiangjjj This is a feature PR (feat:). Please provide a design document.

How to resolve:
Link a design doc in the PR description:

design doc: https://github.com/milvus-io/milvus-design-docs/blob/main/design_docs/your_design.md

Design documents location: https://github.com/milvus-io/milvus-design-docs/tree/main/design_docs

@mergify mergify Bot added dco-passed DCO check passed. do-not-merge/missing-design-doc kind/feature Issues related to feature request from users labels Feb 27, 2026
@mergify

mergify Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

@junjiejiangjjj Please associate the related issue to the body of your Pull Request. (eg. "issue: #")

@sre-ci-robot

Copy link
Copy Markdown
Contributor

[ci-v2-notice]
Notice: New ci-v2 system is enabled for this PR.

To rerun ci-v2 checks, comment with:

  • /ci-rerun-code-check // for ci-v2/code-check
  • /ci-rerun-build // for ci-v2/build
  • /ci-rerun-build-all // for ci-v2/build-all (multi-arch builds)
  • /ci-rerun-ut-integration // for ci-v2/ut-integration, will rerun ci-v2/build
  • /ci-rerun-ut-go // for ci-v2/ut-go, will rerun ci-v2/build
  • /ci-rerun-ut-cpp // for ci-v2/ut-cpp
  • /ci-rerun-ut // for all ci-v2/ut-integration, ci-v2/ut-go, ci-v2/ut-cpp, will rerun ci-v2/build
  • /ci-rerun-e2e-arm // for ci-v2/e2e-arm
  • /ci-rerun-e2e-default // for ci-v2/e2e-default

If you have any questions or requests, please contact @zhikunyao.

@codecov

codecov Bot commented Mar 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.02219% with 441 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.91%. Comparing base (e982662) to head (625078d).
⚠️ Report is 7 commits behind head on master.

Files with missing lines Patch % Lines
internal/proxy/search_pipeline.go 62.40% 82 Missing and 18 partials ⚠️
internal/util/function/chain/operator_group_by.go 80.76% 44 Missing and 16 partials ⚠️
internal/util/function/chain/operator_merge.go 92.23% 33 Missing and 14 partials ⚠️
internal/util/function/chain/converter.go 91.95% 30 Missing and 16 partials ⚠️
internal/util/function/chain/chain.go 79.44% 25 Missing and 12 partials ⚠️
internal/util/function/chain/dataframe.go 87.92% 25 Missing and 7 partials ⚠️
...nal/util/function/chain/expr/score_combine_expr.go 80.00% 21 Missing and 7 partials ⚠️
internal/util/function/chain/operator_filter.go 78.18% 17 Missing and 7 partials ⚠️
internal/util/function/chain/expr/decay_expr.go 85.47% 12 Missing and 5 partials ⚠️
internal/util/function/chain/operator_limit.go 82.35% 12 Missing and 3 partials ⚠️
... and 5 more
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           master   #47919       +/-   ##
===========================================
+ Coverage   75.15%   77.91%    +2.75%     
===========================================
  Files        1506     2165      +659     
  Lines      252029   356057   +104028     
===========================================
+ Hits       189408   277408    +88000     
- Misses      54135    70052    +15917     
- Partials     8486     8597      +111     
Components Coverage Δ
Client 79.25% <ø> (ø)
Core 84.44% <ø> (∅)
Go 76.17% <86.02%> (+0.16%) ⬆️
Files with missing lines Coverage Δ
internal/proxy/rerank_meta.go 100.00% <100.00%> (ø)
internal/proxy/task_search.go 71.75% <100.00%> (+1.76%) ⬆️
internal/util/function/chain/expr/base_expr.go 100.00% <100.00%> (ø)
...rnal/util/function/chain/expr/rerank_model_expr.go 100.00% <100.00%> (ø)
internal/util/function/chain/operator_registry.go 100.00% <ø> (ø)
internal/util/function/chain/operator_select.go 88.13% <ø> (ø)
internal/util/function/chain/operator_sort.go 76.70% <ø> (ø)
internal/util/function/chain/repr.go 75.86% <ø> (ø)
internal/util/function/chain/rerank_builder.go 91.13% <ø> (ø)
internal/util/function/chain/types/registry.go 100.00% <ø> (ø)
... and 25 more

... and 671 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mergify

mergify Bot commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

@junjiejiangjjj go-sdk check failed, comment rerun go-sdk can trigger the job again.

@sre-ci-robot sre-ci-robot added the low-code-coverage add test-label from zhikun, diff coverage > 80% label Mar 2, 2026
@junjiejiangjjj
junjiejiangjjj marked this pull request as draft March 3, 2026 02:49
@sre-ci-robot sre-ci-robot added the do-not-merge/work-in-progress Don't merge even CI passed. label Mar 3, 2026
@junjiejiangjjj
junjiejiangjjj force-pushed the func-chain branch 3 times, most recently from 7b56659 to 6197c97 Compare March 4, 2026 12:25
@junjiejiangjjj
junjiejiangjjj marked this pull request as ready for review March 4, 2026 12:26
@sre-ci-robot sre-ci-robot added the low-code-coverage add test-label from zhikun, diff coverage > 80% label Mar 31, 2026

@liliu-z liliu-z left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review: 7 issues identified (2 critical regressions, 5 important improvements)

// Grouping parameters
GroupByField string // Field to group by (empty means no grouping)
GroupSize int64 // Maximum rows per group
GroupScorer GroupScorer // How to compute group score ("max", "sum", "avg")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 strictGroupSize missing from SearchParams — API regression

strictGroupSize is parsed in proxy (search_util.go:33) and stored in rankParams, but rerankOperator never reads it, SearchParams doesn't define it, and GroupByOp doesn't enforce it. Users setting strictGroupSize=true will silently get non-strict results after this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strictGroupSize is a QueryNode-level parameter that controls segment-level search behavior — whether each group must be fully filled to groupSize rows before the search considers it "enough". The proxy only parses it and passes it through to QueryNode via the QueryInfo proto. The old rerank code never used it on the proxy side either. The chain code operates at the proxy rerank/merge layer and does not need to handle this parameter.

}
chunks = importChunkedBatch(data, offsets, getValidSlice, array.NewStringBuilder, alloc)

default:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Timestamptz type not handled — regression

The old rerank/util.go handled DataType_Timestamptz as int64. This type switch covers Bool through String/VarChar/Text but has no Timestamptz case. Collections using Timestamptz fields for decay rerank will hit this default error branch. Fix: add case schemapb.DataType_Timestamptz: handled like Int64.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamptz is a newly added data type. The scope of this PR is to cover all existing capabilities of the old rerank implementation. Support for new types like Timestamptz will be added in a follow-up.

}

if hasNull {
builder.AppendNull()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 NaN silently propagated to user output

Null inputs are correctly handled here (AppendNull), but NaN floats from upstream pass through all combine modes unchecked. Built-in decay expressions have parameter validation that prevents NaN production, but there's no defensive layer if a future expression produces NaN. Consider adding math.IsNaN(result) check after s.combine(values) and returning an error or substituting 0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decay parameter validation in NewDecayExpr already prevents NaN production: decay is constrained to (0.001, 0.999) so log(decay) is always a finite negative, scale > 0 prevents division by zero, and offset >= 0. All three decay functions are mathematically bounded to (0, 1] under these constraints. The only theoretical path to NaN would be a user storing NaN in a float field — the old rerank code had no guard for that either. Acknowledged as a defensive improvement, but not a real risk in practice.


// buildGroups builds groups from the chunk.
func (o *GroupByOp) buildGroups(groupChunk arrow.Array, scoreChunk *array.Float32, idChunk arrow.Array, chunkLen int) []*group {
groupMap := make(map[any]*group)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 No group cardinality bound

This map grows to match the number of distinct group keys with no upper limit. While TopK (≤16384) caps it in practice, grouping by a near-unique field silently produces degenerate results (one row per group). A log.Warn when len(groupMap) > limit * 10 would help users catch misconfigured group-by fields early.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input size is already bounded by upstream TopK (≤16384), so the map cannot grow unbounded. The group-by field is user-specified — if they pick a near-unique field, the degenerate result is expected behavior, same as the old code. A warning log could be useful for observability but is not a correctness issue. Will consider adding it as a follow-up improvement.

}

// Add grouping or sort+limit
if searchParams.HasGrouping() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 IsSupportGroup check removed — intentional?

The old code rejected group-by for incompatible rerankers via IsSupportGroup() in task_search.go. The new architecture decouples GroupByOp from rerankers, implicitly making all rerankers group-compatible. If intentional, a comment here explaining the design change would help — otherwise this is a missing validation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is intentional. The new architecture decouples group-by into a standalone GroupByOp that works uniformly across all rerankers, so individual rerankers no longer need to declare group-by compatibility via IsSupportGroup(). This was one of the design goals of the refactor — eliminate per-reranker group-by logic in favor of a single composable operator.


// collectRRFScores collects RRF scores for a single chunk.
func (op *MergeOp) collectRRFScores(inputs []*DataFrame, chunkIdx int) (map[any]float32, map[any]idLocation, error) {
idScores := make(map[any]float32)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 map[any] loses compile-time type safety

map[any]float32 relies on runtime type consistency of ID keys. If different chunks are ever parsed into mismatched types (e.g., int64 vs string), lookups silently fail. A type-switched dispatch to map[int64] / map[string] would catch this at compile time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Milvus, the ID (primary key) type is defined at the collection level — either Int64 or VarChar. All inputs to MergeOp come from sub-searches on the same collection, so the ID type is guaranteed to be consistent across all inputs. The type mismatch scenario (int64 vs string keys in the same map) cannot happen in practice.

return compareTyped(a, i, j)
case *array.String:
return compareTyped(a, i, j)
default:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 default: return 0 hides future bugs

If a new type is added to isComparableType() but not to compareArrayValues(), rows with that type silently compare as equal, producing wrong sort order with no error. Since reaching this branch indicates an internal logic inconsistency, this should be a panic or error rather than a silent fallback.

// Decay Builder
// =============================================================================

func buildDecayChain(fc *FuncChain, collSchema *schemapb.CollectionSchema, funcSchema *schemapb.FunctionSchema, searchMetrics []string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildDecayChain uses fc.Merge which loses sortDescending propagation. For L2 + norm_score=false, results are incorrectly sorted DESC instead of ASC.

@junjiejiangjjj junjiejiangjjj Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed. Root cause is slightly different from "lost sortDescending propagation": decay multiplies $score by a [0, 1] factor and assumes "higher = better", so the chain must always produce DESC scores. Legacy rerank/decay_function.go enforced this by passing toGreater=true to getNormalizeFunc regardless of norm_score; the chain refactor lost that.

Fix: added WithForceDescending option on MergeOp. When set, resolveMergeBehavior applies getDirectionConvertFunc (atan-based) for distance metrics regardless of normalize. buildDecayChain always passes WithForceDescending(true). Other rerankers unchanged.

Coverage: TestExecuteDecay_L2_NoNormScore_RanksByCombinedScore (asserts each score matches (1 − 2·atan(d)/π) × gauss_decay within 1e-5) + Python e2e test_milvus_client_search_decay_rerank_l2_metric_no_norm_score.

if scorer == "" {
scorer = GroupScorerMax
}
fc.GroupByWithScorer(searchParams.GroupByField, searchParams.GroupSize, searchParams.Limit, searchParams.Offset, scorer)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GroupByWithScorer path does not consume sortDescending and hard-codes DESC, breaking weighted + group_by + L2 + normalize=false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. This is also a long-standing latent bug in legacy rerank/util.go::newGroupingIDScores (no direction parameter, hard-coded DESC) — the chain refactor inherited it. Fix:

  1. Added sortDescending bool field on GroupByOp (defaults true, preserving legacy behavior for all existing callers and NewGroupByOpFromRepr)
  2. Added builder-style SetSortDescending(bool) setter
  3. Direction-aware sortAndLimitGroup (within-group trim) and processChunk step 4 (cross-group sort)
  4. buildRerankChainInternal constructs the op directly and propagates sortDescending from buildWeightedChain

Max scorer code is unchanged: scores[0] is now correctly the best representative under either direction since sortAndLimitGroup orders the slice first. Public chain API (GroupBy/GroupByWithScorer) unchanged — no breaking change.

While validating this end-to-end I also discovered a related pre-existing proxy bug in reduceAdvanceGroupBy single-shard early return path (search_reduce_util.go:86) — without that fix the chain layer fix alone wasn't observable from Python e2e because the proxy was passing -L2 to the chain. Also fixed in this PR with its own table-driven test (TDD-validated by reverting the fix and watching the new test go red, then re-applying).

Coverage: TestExecuteWeightedGroupBy_L2_NoNormScore_PreservesAscOrder, TestExecuteWeightedGroupBy_L2_NormScore_PreservesDescOrder, TestGroupByOp_AscDirection, TestGroupByOp_DefaultSortDescending + Python e2e test_milvus_client_hybrid_search_weighted_groupby_l2_no_norm_score + new TestReduceAdvanceGroupBy_SingleShardScoreNegation and TestReduceAdvanceGroupBy_SingleShardMatchesMultiShard.

}
}

// Assemble only the rows referenced by the reranked IDs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping nil computers without removing corresponding IDs/FieldsData entries causes row/column misalignment in assembled results.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that this is an invariant violation. Tracing the proxy code, the precondition (one sub-result with FieldsData while another has none) shouldn't be reachable today — task_search.go sets identical plan.OutputFieldIds across all sub-requests and the needRequery=false path used by hybridSearchPipe always includes the PK. So the defensive continue is dead code that would only fire on an upstream invariant violation, where silently dropping the row is the worst possible outcome.

Fix: continuereturn merr.WrapErrServiceInternal(...) with the offending sub-result index, the PK that triggered it, and the collection ID. Surfacing the bug at its source is much better than corrupting Ids ↔ FieldsData row alignment.

Coverage: TestHybridAssembleOp_MixedFieldsDataLayoutErrors (synthesizes the offending state and asserts the operator returns an error mentioning FieldsData).

return rerank.GetRerankName(funcScore.Functions[0])
}

func validateInputField(collSchema *schemapb.CollectionSchema, fieldName string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateInputField accepts Timestamptz but GetNumericValue only handles Int/Float, causing runtime failures after passing schema validation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated: legacy decay listed Timestamptz in its type-dispatch switch (added in c0d62268a) but the support never reached end-to-end. No production path or test ever exercised it — getField had a branch but neither converter nor GetNumericValue handled it, and there was no decay+Timestamptz test. The chain refactor reproduces the same incomplete state.

Since this PR's scope is to preserve legacy functionality rather than extend it, I removed Timestamptz from validateInputField so the chain rejects it explicitly. The user-visible error in proxy e2e actually comes from chain.FromSearchResultData (Arrow converter default branch) which runs before BuildRerankChain — both layers reject Timestamptz, just at different stages. Adding genuine Timestamptz support (converter + GetNumericValue + tests) belongs in a separate feature PR.

Coverage: TestBuildDecayChainTimestamptzInputField (chain unit) + Python e2e test_milvus_client_search_decay_rerank_timestamptz_field_rejected (asserts unsupported field type: Timestamptz).

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 559e4f8

Stage Result Duration Tests
✅ Build SUCCESS 18.5min -

Total: 23min | Pipeline | Artifacts

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 559e4f8

Stage Result Duration Tests
✅ Build SUCCESS 8.0min -

Total: 13min | Pipeline | Artifacts

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/search_pipeline.go:1 strictGroupSize is silently dropped in the Arrow chain path, causing a functional regression between single-path and multi-path search. Must be restored before merge.

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/rerank/converter.go:280 When ids==nil && totalRows>0, a malformed DataFrame is silently constructed instead of returning an error.

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/rerank/converter.go:293 Same hole as ids branch: when totalRows>0 && len(scores)==0, the $score column is silently dropped. Return an explicit error.

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/rerank/converter.go:1 Legacy rerank/ directory is nearly emptied but not fully removed, and the modelProvider→ModelProvider rename affects other callers. PR cannot be cleanly reverted; finish cleanup or provide a rollback plan.

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/rerank/operator_filter.go:1 FilterOp.Execute assumes the FunctionExpr chunked output shares chunk boundaries with the input DataFrame. Arrow does not guarantee this. Add an assertion that input.chunkSizes[chunkIdx]==boolChunk.Len() to fail fast.

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/rerank/converter.go:1 Missing unit tests: (1) empty path + offset/limit on chain pipeline, (2) end-to-end hybrid + chain-path strictGroupSize.

@liliu-z

liliu-z commented Apr 9, 2026

Copy link
Copy Markdown
Member

internal/proxy/search_pipeline.go:1 Single-shard rerank score negation inverts sort order vs prior versions. Must be loudly flagged in release notes as a breaking behavior change.

@junjiejiangjjj

Copy link
Copy Markdown
Contributor Author

Thanks for the additional pass. Quick rundown:

1. strictGroupSize dropped — Not a regression. Legacy rerank.SearchParams carries the field but no reranker ever reads it. Actual enforcement lives in segcore (SearchGroupByOperator::IsGroupResEnough); proxy/rerank just consumes what segcore returns. Both old and new paths are consistent.

2. ids==nil && totalRows>0
3. scores==nil && totalRows>0 — Both unreachable. All reduce paths initialize Ids via setupIdListForSearchResult and append Scores/Topks 1:1 with len(scores) == sum(topks) == totalRows as an invariant. The totalRows == 0 branches handle the only legitimate empty case. Happy to add fail-fast assertions if you want defense in depth, but no real path hits these.

4. Legacy rerank/ cleanup — The remaining 12 files are the rerank shared infrastructure intentionally retained: function_score.go (GetRerankName/BoostName registry), model_function.go (ModelProvider interface + factory), and 8 concrete provider implementations — all actively used by chain/rerank_builder.go and chain/expr/rerank_model_expr.go. The modelProviderModelProvider rename doesn't appear in the diff; only the capitalized form exists on master and this branch.

5. FilterOp chunk-boundary assertion — Real defensive gap, but all current FunctionExpr implementations produce one bool chunk per input chunk so the assumption holds. Same shape as the hybridAssemble nil-computer branch we converted to fail-loud — happy to add the assertion if you want.

6. Missing tests — Will add (1) empty input + offset/limit on the chain pipeline. (2) follows from #1, so n/a.

7. Single-shard score-negation behavior change — Agreed. Will add a "Behavior change" entry to the PR description / release notes covering hybrid + group_by + distance metric (L2/HAMMING/JACCARD) + single-shard collections.

Let me know if any of #1/#2/#3/#5 are blocking — happy to add the assertions as pure defense in depth if preferred.

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 44efa62

Stage Result Duration Tests
✅ Build SUCCESS 8.2min -

Total: 12min | Pipeline | Artifacts

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 44efa62

Stage Result Duration Tests
✅ Build SUCCESS 7.8min -

Total: 12min | Pipeline | Artifacts

… rerank implementation

  Adds a new chain package (internal/util/function/chain/) that implements a
  composable, Apache Arrow-based pipeline for search result post-processing
  (reranking, scoring, merging, filtering, grouping, etc.). This replaces the
  legacy rerank utility functions with a DataFrame-oriented approach that
  provides better performance and extensibility.

  Key changes:
  - New chain package with DataFrame abstraction, operator registry, expression
  engine (decay, score combine, round decimal, rerank model), and operators
  (merge, sort, filter, limit, select, map, group-by)
  - Refactored search_pipeline.go to use chain-based reranking instead of the
  legacy rerank package, adding a hybrid_assemble operator for hybrid search
  - Removed legacy rerank utilities (decay_function, rrf_function,
  weighted_function, util.go, rerank_base) and simplified remaining rerank
  providers to thin wrappers
  - Added rerank_meta.go in proxy for structured rerank configuration parsing
  - Comprehensive unit tests and benchmarks for the chain package
  - Extended Python and Go integration tests for reranker functions

Signed-off-by: junjie.jiang <junjie.jiang@zilliz.com>
@sre-ci-robot

Copy link
Copy Markdown
Contributor

❌ CI Loop Results 625078d

Stage Result Duration Tests

Total: 3min | Pipeline | Artifacts

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 625078d

Stage Result Duration Tests
✅ Build SUCCESS 7.9min -

Total: 12min | Pipeline | Artifacts

@junjiejiangjjj

Copy link
Copy Markdown
Contributor Author

/ci-rerun-e2e-default

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 625078d

Stage Result Duration Tests
✅ Build SUCCESS 7.9min -

Total: 12min | Pipeline | Artifacts

@liliu-z

liliu-z commented Apr 10, 2026

Copy link
Copy Markdown
Member

/lgtm
/approve

@sre-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: junjiejiangjjj, liliu-z

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved area/test ci-passed dco-passed DCO check passed. kind/feature Issues related to feature request from users lgtm sig/testing size/XXL Denotes a PR that changes 1000+ lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants