mirror of
https://github.com/milvus-io/milvus.git
synced 2026-07-21 02:05:41 +00:00
fix: keep round_decimal from changing search order (#50440)
## Summary Keep raw search distances through segment search, iterator output, index query, and chunk merging so ranking/reduce order is not affected by `round_decimal`. Apply `round_decimal` only when producing the final search response, after reduce/rerank/order-by has already determined result order. Returned scores keep the requested precision without changing TopK selection. issue: #50347 --------- Signed-off-by: xianliang.li <xianliang.li@zilliz.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
43264fc57a
commit
ad68690249
@@ -755,15 +755,8 @@ VectorDiskAnnIndex<T>::Query(const DatasetPtr dataset,
|
||||
float* distances = const_cast<float*>(final->GetDistance());
|
||||
final->SetIsOwner(true);
|
||||
|
||||
auto round_decimal = search_info.round_decimal_;
|
||||
auto total_num = num_queries * topk;
|
||||
|
||||
if (round_decimal != -1) {
|
||||
const float multiplier = pow(10.0, round_decimal);
|
||||
for (int i = 0; i < total_num; i++) {
|
||||
distances[i] = std::round(distances[i] * multiplier) / multiplier;
|
||||
}
|
||||
}
|
||||
search_result.seg_offsets_.resize(total_num);
|
||||
search_result.distances_.resize(total_num);
|
||||
search_result.total_nq_ = num_queries;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
@@ -795,15 +794,8 @@ VectorMemIndex<T>::Query(const DatasetPtr dataset,
|
||||
auto num_queries = final->GetRows();
|
||||
float* distances = const_cast<float*>(final->GetDistance());
|
||||
final->SetIsOwner(true);
|
||||
auto round_decimal = search_info.round_decimal_;
|
||||
auto total_num = num_queries * topk;
|
||||
|
||||
if (round_decimal != -1) {
|
||||
const float multiplier = pow(10.0, round_decimal);
|
||||
for (int i = 0; i < total_num; i++) {
|
||||
distances[i] = std::round(distances[i] * multiplier) / multiplier;
|
||||
}
|
||||
}
|
||||
search_result.seg_offsets_.resize(total_num);
|
||||
search_result.distances_.resize(total_num);
|
||||
search_result.total_nq_ = num_queries;
|
||||
|
||||
@@ -251,8 +251,7 @@ CachedSearchIterator::NextBatch(const SearchInfo& search_info,
|
||||
|
||||
for (size_t query_idx = 0; query_idx < nq_; ++query_idx) {
|
||||
auto rst = GetBatchedNextResults(query_idx, search_info);
|
||||
WriteSingleQuerySearchResult(
|
||||
search_result, query_idx, rst, search_info.round_decimal_);
|
||||
WriteSingleQuerySearchResult(search_result, query_idx, rst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,20 +367,11 @@ void
|
||||
CachedSearchIterator::WriteSingleQuerySearchResult(
|
||||
SearchResult& search_result,
|
||||
const size_t idx,
|
||||
std::vector<DisIdPair>& rst,
|
||||
const int64_t round_decimal) {
|
||||
const float multiplier = pow(10.0, round_decimal);
|
||||
|
||||
std::vector<DisIdPair>& rst) {
|
||||
std::transform(rst.begin(),
|
||||
rst.end(),
|
||||
search_result.distances_.begin() + idx * batch_size_,
|
||||
[multiplier, round_decimal](DisIdPair& x) {
|
||||
if (round_decimal != -1) {
|
||||
x.first =
|
||||
std::round(x.first * multiplier) / multiplier;
|
||||
}
|
||||
return x.first;
|
||||
});
|
||||
[](const DisIdPair& x) { return x.first; });
|
||||
|
||||
std::transform(rst.begin(),
|
||||
rst.end(),
|
||||
|
||||
@@ -179,8 +179,7 @@ class CachedSearchIterator {
|
||||
void
|
||||
WriteSingleQuerySearchResult(SearchResult& search_result,
|
||||
const size_t idx,
|
||||
std::vector<DisIdPair>& rst,
|
||||
const int64_t round_decimal);
|
||||
std::vector<DisIdPair>& rst);
|
||||
|
||||
void
|
||||
Init(const SearchInfo& search_info);
|
||||
|
||||
@@ -299,7 +299,6 @@ BruteForceSearch(const dataset::SearchDataset& query_ds,
|
||||
KnowhereStatusString(stat));
|
||||
}
|
||||
}
|
||||
sub_result.round_values();
|
||||
return sub_result;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <folly/ExceptionWrapper.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -60,7 +59,6 @@ SearchOnSealedIndex(const Schema& schema,
|
||||
milvus::OpContext* op_context,
|
||||
SearchResult& search_result) {
|
||||
auto topK = search_info.topk_;
|
||||
auto round_decimal = search_info.round_decimal_;
|
||||
|
||||
auto field_id = search_info.field_id_;
|
||||
auto& field = schema[field_id];
|
||||
@@ -138,15 +136,6 @@ SearchOnSealedIndex(const Schema& schema,
|
||||
if (!use_iterator) {
|
||||
vec_index->Query(
|
||||
dataset, search_info, search_bitset, op_context, search_result);
|
||||
float* distances = search_result.distances_.data();
|
||||
auto total_num = num_queries * topK;
|
||||
if (round_decimal != -1) {
|
||||
const float multiplier = pow(10.0, round_decimal);
|
||||
for (int i = 0; i < total_num; i++) {
|
||||
distances[i] =
|
||||
std::round(distances[i] * multiplier) / multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
FinalizeVectorSearchOffsets(
|
||||
search_result,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/Consts.h"
|
||||
#include "filemanager/InputStream.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "knowhere/comp/index_param.h"
|
||||
@@ -141,3 +142,29 @@ TEST(Reduce, SubSearchResult) {
|
||||
TestSubSearchResultMerge<queue_type_ip>(knowhere::metric::IP, 4, 16, 1);
|
||||
TestSubSearchResultMerge<queue_type_ip>(knowhere::metric::IP, 4, 16, 10);
|
||||
}
|
||||
|
||||
TEST(Reduce, SubSearchResultMergesBeforeRounding) {
|
||||
SubSearchResult final_result(1, 2, knowhere::metric::L2, 0);
|
||||
|
||||
SubSearchResult left(1, 2, knowhere::metric::L2, 0);
|
||||
left.mutable_offsets() = {1, INVALID_SEG_OFFSET};
|
||||
left.mutable_distances() = {
|
||||
0.49f, SubSearchResult::init_value(knowhere::metric::L2)};
|
||||
|
||||
SubSearchResult right(1, 2, knowhere::metric::L2, 0);
|
||||
right.mutable_offsets() = {2, INVALID_SEG_OFFSET};
|
||||
right.mutable_distances() = {
|
||||
0.36f, SubSearchResult::init_value(knowhere::metric::L2)};
|
||||
|
||||
final_result.merge(left);
|
||||
final_result.merge(right);
|
||||
|
||||
EXPECT_EQ(final_result.get_offsets()[0], 2);
|
||||
EXPECT_EQ(final_result.get_offsets()[1], 1);
|
||||
EXPECT_FLOAT_EQ(final_result.get_distances()[0], 0.36f);
|
||||
EXPECT_FLOAT_EQ(final_result.get_distances()[1], 0.49f);
|
||||
|
||||
final_result.round_values();
|
||||
EXPECT_FLOAT_EQ(final_result.get_distances()[0], 0.0f);
|
||||
EXPECT_FLOAT_EQ(final_result.get_distances()[1], 0.0f);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ TEST(Indexing, BinaryBruteForce) {
|
||||
sr.seg_offsets_ = std::move(sub_result.mutable_offsets());
|
||||
sr.distances_ = std::move(sub_result.mutable_distances());
|
||||
|
||||
auto json = SearchResultToJson(sr);
|
||||
auto json = SearchResultToJson(sr, round_decimal);
|
||||
std::cout << json.dump(2);
|
||||
#ifdef __linux__
|
||||
auto ref = nlohmann::json::parse(R"(
|
||||
|
||||
@@ -115,7 +115,7 @@ TEST(Query, ExecWithPredicateLoader) {
|
||||
|
||||
auto sr = segment->Search(plan.get(), ph_group.get(), timestamp);
|
||||
|
||||
query::Json json = SearchResultToJson(*sr);
|
||||
query::Json json = SearchResultToJson(*sr, 3);
|
||||
#ifdef __linux__
|
||||
auto ref = json::parse(R"(
|
||||
[
|
||||
@@ -222,7 +222,7 @@ TEST(Query, ExecWithPredicate) {
|
||||
|
||||
auto sr = segment->Search(plan.get(), ph_group.get(), timestamp);
|
||||
|
||||
query::Json json = SearchResultToJson(*sr);
|
||||
query::Json json = SearchResultToJson(*sr, 3);
|
||||
#ifdef __linux__
|
||||
auto ref = json::parse(R"(
|
||||
[
|
||||
@@ -404,7 +404,7 @@ TEST(Query, ExecWithoutPredicate) {
|
||||
auto sr = segment->Search(plan.get(), ph_group.get(), timestamp);
|
||||
assert_order(*sr, "l2");
|
||||
std::vector<std::vector<std::string>> results;
|
||||
auto json = SearchResultToJson(*sr);
|
||||
auto json = SearchResultToJson(*sr, 3);
|
||||
#ifdef __linux__
|
||||
auto ref = json::parse(R"(
|
||||
[
|
||||
|
||||
@@ -1396,16 +1396,22 @@ SearchResultToVector(const SearchResult& sr) {
|
||||
}
|
||||
|
||||
inline nlohmann::json
|
||||
SearchResultToJson(const SearchResult& sr) {
|
||||
SearchResultToJson(const SearchResult& sr, int64_t round_decimal = -1) {
|
||||
int64_t num_queries = sr.total_nq_;
|
||||
int64_t topk = sr.unity_topK_;
|
||||
std::vector<std::vector<std::string>> results;
|
||||
const float multiplier =
|
||||
round_decimal >= 0 ? std::pow(10.0, round_decimal) : 1.0f;
|
||||
for (int q = 0; q < num_queries; ++q) {
|
||||
std::vector<std::string> result;
|
||||
for (int k = 0; k < topk; ++k) {
|
||||
int index = q * topk + k;
|
||||
auto distance = sr.distances_[index];
|
||||
if (round_decimal >= 0) {
|
||||
distance = std::round(distance * multiplier) / multiplier;
|
||||
}
|
||||
result.emplace_back(std::to_string(sr.seg_offsets_[index]) + "->" +
|
||||
std::to_string(sr.distances_[index]));
|
||||
std::to_string(distance));
|
||||
}
|
||||
results.emplace_back(std::move(result));
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -809,17 +810,26 @@ func copySearchResultsWithData(src *milvuspb.SearchResults, data *schemapb.Searc
|
||||
}
|
||||
|
||||
type aggregateOperator struct {
|
||||
aggCtx *search_agg.SearchAggregationContext
|
||||
collSchema *schemapb.CollectionSchema
|
||||
aggCtx *search_agg.SearchAggregationContext
|
||||
collSchema *schemapb.CollectionSchema
|
||||
roundDecimal int64
|
||||
}
|
||||
|
||||
func newAggregateOperator(t *searchTask, _ map[string]any) (operator, error) {
|
||||
if t.aggCtx == nil {
|
||||
return nil, merr.WrapErrServiceInternal("aggregate operator requires non-nil aggCtx")
|
||||
}
|
||||
// Aggregation searches bypass endOperator (newSearchPipeline returns early for
|
||||
// aggCtx), so round the aggregation hit scores here at the terminal step to keep
|
||||
// round_decimal behavior consistent with non-aggregation searches.
|
||||
roundDecimal := int64(-1)
|
||||
if !t.GetIsAdvanced() && len(t.queryInfos) > 0 && t.queryInfos[0] != nil {
|
||||
roundDecimal = t.queryInfos[0].GetRoundDecimal()
|
||||
}
|
||||
return &aggregateOperator{
|
||||
aggCtx: t.aggCtx,
|
||||
collSchema: t.schema.CollectionSchema,
|
||||
aggCtx: t.aggCtx,
|
||||
collSchema: t.schema.CollectionSchema,
|
||||
roundDecimal: roundDecimal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -857,6 +867,7 @@ func (op *aggregateOperator) run(ctx context.Context, span trace.Span, inputs ..
|
||||
aggBuckets := make([]*schemapb.AggBucket, 0)
|
||||
aggTopks := make([]int64, 0, len(nqAggResults))
|
||||
for _, buckets := range nqAggResults {
|
||||
roundAggHitScores(buckets, op.roundDecimal)
|
||||
aggTopks = append(aggTopks, int64(len(buckets)))
|
||||
aggBuckets = append(aggBuckets, serializeAggBuckets(buckets, fieldIDToName, op.aggCtx.Levels, 0)...)
|
||||
}
|
||||
@@ -1773,12 +1784,18 @@ func (op *lambdaOperator) run(ctx context.Context, span trace.Span, inputs ...an
|
||||
type endOperator struct {
|
||||
outputFieldNames []string
|
||||
fieldSchemas []*schemapb.FieldSchema
|
||||
roundDecimal int64
|
||||
}
|
||||
|
||||
func newEndOperator(t *searchTask, _ map[string]any) (operator, error) {
|
||||
roundDecimal := int64(-1)
|
||||
if !t.GetIsAdvanced() && len(t.queryInfos) > 0 && t.queryInfos[0] != nil {
|
||||
roundDecimal = t.queryInfos[0].GetRoundDecimal()
|
||||
}
|
||||
return &endOperator{
|
||||
outputFieldNames: t.translatedOutputFields,
|
||||
fieldSchemas: typeutil.GetAllFieldSchemas(t.schema.CollectionSchema),
|
||||
roundDecimal: roundDecimal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1798,9 +1815,47 @@ func (op *endOperator) run(ctx context.Context, span trace.Span, inputs ...any)
|
||||
})
|
||||
allSearchCount := aggregatedAllSearchCount(inputs[1].([]*milvuspb.SearchResults))
|
||||
result.GetResults().AllSearchCount = allSearchCount
|
||||
roundSearchScores(result.GetResults(), op.roundDecimal)
|
||||
return []any{result}, nil
|
||||
}
|
||||
|
||||
func roundScore(score float32, roundDecimal int64) float32 {
|
||||
if roundDecimal < 0 {
|
||||
return score
|
||||
}
|
||||
multiplier := math.Pow(10.0, float64(roundDecimal))
|
||||
return float32(math.Floor(float64(score)*multiplier+0.5) / multiplier)
|
||||
}
|
||||
|
||||
func roundSearchScores(result *schemapb.SearchResultData, roundDecimal int64) {
|
||||
if result == nil || roundDecimal < 0 {
|
||||
return
|
||||
}
|
||||
for i, score := range result.Scores {
|
||||
result.Scores[i] = roundScore(score, roundDecimal)
|
||||
}
|
||||
}
|
||||
|
||||
// roundAggHitScores rounds aggregation hit scores in place (recursing into
|
||||
// sub-aggregation buckets). Applied at the aggregate operator's terminal step
|
||||
// because aggregation searches bypass endOperator.
|
||||
func roundAggHitScores(buckets []*search_agg.AggBucketResult, roundDecimal int64) {
|
||||
if roundDecimal < 0 {
|
||||
return
|
||||
}
|
||||
for _, b := range buckets {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
for _, h := range b.Hits {
|
||||
if h != nil {
|
||||
h.Score = roundScore(h.Score, roundDecimal)
|
||||
}
|
||||
}
|
||||
roundAggHitScores(b.SubAggBuckets, roundDecimal)
|
||||
}
|
||||
}
|
||||
|
||||
func newHighlightOperator(t *searchTask, _ map[string]any) (operator, error) {
|
||||
return t.highlighter.AsSearchPipelineOperator(t)
|
||||
}
|
||||
|
||||
@@ -2483,6 +2483,110 @@ func (s *SearchPipelineSuite) TestFilterFieldOperatorWithStructArrayFields() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SearchPipelineSuite) TestEndOperatorRoundsScores() {
|
||||
task := &searchTask{
|
||||
queryInfos: []*planpb.QueryInfo{{RoundDecimal: 0}},
|
||||
schema: &schemaInfo{
|
||||
CollectionSchema: &schemapb.CollectionSchema{},
|
||||
},
|
||||
}
|
||||
|
||||
op, err := newEndOperator(task, nil)
|
||||
s.NoError(err)
|
||||
|
||||
searchResults := &milvuspb.SearchResults{
|
||||
Results: &schemapb.SearchResultData{
|
||||
Scores: []float32{0.49, 0.36},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := op.run(context.Background(), s.span, searchResults, []*milvuspb.SearchResults{{Results: &schemapb.SearchResultData{AllSearchCount: 0}}})
|
||||
s.NoError(err)
|
||||
|
||||
resultData := results[0].(*milvuspb.SearchResults).GetResults()
|
||||
s.Equal([]float32{0, 0}, resultData.GetScores())
|
||||
}
|
||||
|
||||
func (s *SearchPipelineSuite) TestRoundAggHitScores() {
|
||||
// Aggregation searches bypass endOperator, so hit scores are rounded at the
|
||||
// aggregate operator's terminal step. Covers nested sub-aggregation buckets.
|
||||
buckets := []*search_agg.AggBucketResult{
|
||||
{
|
||||
Hits: []*search_agg.HitResult{{Score: 0.49}, {Score: 0.36}},
|
||||
SubAggBuckets: []*search_agg.AggBucketResult{
|
||||
{Hits: []*search_agg.HitResult{{Score: 0.51}, nil}},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
}
|
||||
|
||||
roundAggHitScores(buckets, 0)
|
||||
|
||||
s.Equal(float32(0), buckets[0].Hits[0].Score)
|
||||
s.Equal(float32(0), buckets[0].Hits[1].Score)
|
||||
s.Equal(float32(1), buckets[0].SubAggBuckets[0].Hits[0].Score)
|
||||
}
|
||||
|
||||
func (s *SearchPipelineSuite) TestRoundAggHitScoresDisabled() {
|
||||
buckets := []*search_agg.AggBucketResult{
|
||||
{Hits: []*search_agg.HitResult{{Score: 0.49}, {Score: 0.36}}},
|
||||
}
|
||||
|
||||
roundAggHitScores(buckets, -1)
|
||||
|
||||
s.Equal(float32(0.49), buckets[0].Hits[0].Score)
|
||||
s.Equal(float32(0.36), buckets[0].Hits[1].Score)
|
||||
}
|
||||
|
||||
func (s *SearchPipelineSuite) TestEndOperatorKeepsScoresWhenRoundDecimalDisabled() {
|
||||
task := &searchTask{
|
||||
queryInfos: []*planpb.QueryInfo{{RoundDecimal: -1}},
|
||||
schema: &schemaInfo{
|
||||
CollectionSchema: &schemapb.CollectionSchema{},
|
||||
},
|
||||
}
|
||||
|
||||
op, err := newEndOperator(task, nil)
|
||||
s.NoError(err)
|
||||
|
||||
searchResults := &milvuspb.SearchResults{
|
||||
Results: &schemapb.SearchResultData{
|
||||
Scores: []float32{0.49, 0.36},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := op.run(context.Background(), s.span, searchResults, []*milvuspb.SearchResults{{Results: &schemapb.SearchResultData{AllSearchCount: 0}}})
|
||||
s.NoError(err)
|
||||
|
||||
resultData := results[0].(*milvuspb.SearchResults).GetResults()
|
||||
s.Equal([]float32{float32(0.49), float32(0.36)}, resultData.GetScores())
|
||||
}
|
||||
|
||||
func (s *SearchPipelineSuite) TestEndOperatorKeepsAdvancedRerankScores() {
|
||||
task := &searchTask{
|
||||
SearchRequest: &internalpb.SearchRequest{IsAdvanced: true},
|
||||
rankParams: &rankParams{roundDecimal: 0},
|
||||
schema: &schemaInfo{
|
||||
CollectionSchema: &schemapb.CollectionSchema{},
|
||||
},
|
||||
}
|
||||
|
||||
op, err := newEndOperator(task, nil)
|
||||
s.NoError(err)
|
||||
|
||||
searchResults := &milvuspb.SearchResults{
|
||||
Results: &schemapb.SearchResultData{
|
||||
Scores: []float32{0.99, 0.88},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := op.run(context.Background(), s.span, searchResults, []*milvuspb.SearchResults{{Results: &schemapb.SearchResultData{AllSearchCount: 0}}})
|
||||
s.NoError(err)
|
||||
|
||||
resultData := results[0].(*milvuspb.SearchResults).GetResults()
|
||||
s.Equal([]float32{float32(0.99), float32(0.88)}, resultData.GetScores())
|
||||
}
|
||||
|
||||
func (s *SearchPipelineSuite) TestHybridSearchWithRequeryAndRerankByDataPipe() {
|
||||
task := getHybridSearchTask("test_collection", [][]string{
|
||||
{"1", "2"},
|
||||
|
||||
@@ -1697,6 +1697,52 @@ func (s *RerankBuilderTestSuite) TestExecuteRerankChain_RoundDecimalNegativeOne_
|
||||
s.NotContains(chainStr, "round_decimal")
|
||||
}
|
||||
|
||||
func (s *RerankBuilderTestSuite) TestExecuteRerankChain_RoundDecimalAfterOrdering() {
|
||||
collSchema := s.createCollectionSchemaWithCategory()
|
||||
searchParams := NewSearchParams(1, 2, 0, 0)
|
||||
searchMetrics := []string{"L2"}
|
||||
|
||||
funcScoreSchema := &schemapb.FunctionScore{
|
||||
Functions: []*schemapb.FunctionSchema{
|
||||
{
|
||||
Type: schemapb.FunctionType_Rerank,
|
||||
Params: []*commonpb.KeyValuePair{
|
||||
{Key: "reranker", Value: "weighted"},
|
||||
{Key: "weights", Value: "[1.0]"},
|
||||
{Key: "norm_score", Value: "false"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fc, err := BuildRerankChain(collSchema, funcScoreSchema, searchMetrics, searchParams, s.pool)
|
||||
s.Require().NoError(err)
|
||||
|
||||
df := s.createTestDataFrameForRerank(
|
||||
[]int64{1, 2},
|
||||
[]float32{0.49, 0.36},
|
||||
[]string{"A", "B"},
|
||||
[]int64{2},
|
||||
)
|
||||
defer df.Release()
|
||||
|
||||
result, err := fc.ExecuteWithContext(context.Background(), df)
|
||||
s.Require().NoError(err)
|
||||
defer result.Release()
|
||||
|
||||
idCol := result.Column("$id")
|
||||
s.Require().NotNil(idCol)
|
||||
idChunk := idCol.Chunk(0).(*array.Int64)
|
||||
s.Equal(int64(2), idChunk.Value(0))
|
||||
s.Equal(int64(1), idChunk.Value(1))
|
||||
|
||||
scoreCol := result.Column("$score")
|
||||
s.Require().NotNil(scoreCol)
|
||||
scoreChunk := scoreCol.Chunk(0).(*array.Float32)
|
||||
s.Equal(float32(0), scoreChunk.Value(0))
|
||||
s.Equal(float32(0), scoreChunk.Value(1))
|
||||
}
|
||||
|
||||
func (s *RerankBuilderTestSuite) TestExecuteRerankChain_WithGroupingAndRoundDecimal() {
|
||||
collSchema := s.createCollectionSchemaWithCategory()
|
||||
searchParams := NewSearchParamsWithGroupingAndScorer(1, 3, 0, 2, "category", 2, GroupScorerMax)
|
||||
|
||||
Reference in New Issue
Block a user