feat: Add highlight scores (#47017)

https://github.com/milvus-io/milvus/issues/46994

Signed-off-by: junjie.jiang <junjie.jiang@zilliz.com>
This commit is contained in:
junjiejiangjjj 2026-01-14 10:45:27 +08:00 committed by GitHub
parent 2b556dcac7
commit b9752362d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 386 additions and 125 deletions

2
go.mod
View File

@ -20,7 +20,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/klauspost/compress v1.18.0
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20251224033913-b2fbe2627f1c
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260113024922-c7feeb806088
github.com/minio/minio-go/v7 v7.0.73
github.com/panjf2000/ants/v2 v2.11.3 // indirect
github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81 // indirect

4
go.sum
View File

@ -799,8 +799,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6 h1:YHMFI6L
github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20251224033913-b2fbe2627f1c h1:W66Mf/hlR7SWHrSr7xpyt4ACE8v9/C7Y9dMJiZCvp3s=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20251224033913-b2fbe2627f1c/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260113024922-c7feeb806088 h1:qzlpV+1xygF/XK0bRVoLFg03uAQSCZ2AywZR3wt5ov0=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260113024922-c7feeb806088/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=

View File

@ -353,8 +353,7 @@ func (_c *MockMsgHandler_HandleTruncateCollection_Call) RunAndReturn(run func(me
func NewMockMsgHandler(t interface {
mock.TestingT
Cleanup(func())
},
) *MockMsgHandler {
}) *MockMsgHandler {
mock := &MockMsgHandler{}
mock.Mock.Test(t)

View File

@ -859,6 +859,65 @@ func (_c *MockProxy_BatchDescribeCollection_Call) RunAndReturn(run func(context.
return _c
}
// BatchUpdateManifest provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) BatchUpdateManifest(_a0 context.Context, _a1 *milvuspb.BatchUpdateManifestRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for BatchUpdateManifest")
}
var r0 *commonpb.Status
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.BatchUpdateManifestRequest) (*commonpb.Status, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.BatchUpdateManifestRequest) *commonpb.Status); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*commonpb.Status)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.BatchUpdateManifestRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockProxy_BatchUpdateManifest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchUpdateManifest'
type MockProxy_BatchUpdateManifest_Call struct {
*mock.Call
}
// BatchUpdateManifest is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.BatchUpdateManifestRequest
func (_e *MockProxy_Expecter) BatchUpdateManifest(_a0 interface{}, _a1 interface{}) *MockProxy_BatchUpdateManifest_Call {
return &MockProxy_BatchUpdateManifest_Call{Call: _e.mock.On("BatchUpdateManifest", _a0, _a1)}
}
func (_c *MockProxy_BatchUpdateManifest_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.BatchUpdateManifestRequest)) *MockProxy_BatchUpdateManifest_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.BatchUpdateManifestRequest))
})
return _c
}
func (_c *MockProxy_BatchUpdateManifest_Call) Return(_a0 *commonpb.Status, _a1 error) *MockProxy_BatchUpdateManifest_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockProxy_BatchUpdateManifest_Call) RunAndReturn(run func(context.Context, *milvuspb.BatchUpdateManifestRequest) (*commonpb.Status, error)) *MockProxy_BatchUpdateManifest_Call {
_c.Call.Return(run)
return _c
}
// CalcDistance provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) CalcDistance(_a0 context.Context, _a1 *milvuspb.CalcDistanceRequest) (*milvuspb.CalcDistanceResults, error) {
ret := _m.Called(_a0, _a1)

View File

@ -455,18 +455,23 @@ func (op *semanticHighlightOperator) run(ctx context.Context, span trace.Span, i
return nil, errors.Errorf("get highlight failed, text field not in output field %d", fieldID)
}
texts := fieldDatas.GetScalars().GetStringData().GetData()
highlights, err := op.highlight.Process(ctx, result.Results.GetTopks(), texts)
highlights, scores, err := op.highlight.Process(ctx, result.Results.GetTopks(), texts)
if err != nil {
return nil, err
}
singeFieldHighlights := &commonpb.HighlightResult{
if len(highlights) != len(scores) {
return nil, errors.Errorf("Highlights size must equal to scores size, but got highlights size [%d], scores size [%d]", len(highlights), len(scores))
}
singleFieldHighlights := &commonpb.HighlightResult{
FieldName: op.highlight.GetFieldName(fieldID),
Datas: make([]*commonpb.HighlightData, 0, len(highlights)),
Datas: make([]*commonpb.HighlightData, len(highlights)),
}
for _, highlight := range highlights {
singeFieldHighlights.Datas = append(singeFieldHighlights.Datas, &commonpb.HighlightData{Fragments: highlight})
for i := range highlights {
singleFieldHighlights.Datas[i] = &commonpb.HighlightData{Fragments: highlights[i], Scores: scores[i]}
}
highlightResults = append(highlightResults, singeFieldHighlights)
highlightResults = append(highlightResults, singleFieldHighlights)
}
result.Results.HighlightResults = highlightResults
return []any{result}, nil

View File

@ -368,12 +368,16 @@ func (s *SearchPipelineSuite) TestSemanticHighlightOp() {
// Mock SemanticHighlight methods
mockProcess := mockey.Mock((*highlight.SemanticHighlight).Process).To(
func(h *highlight.SemanticHighlight, ctx context.Context, topks []int64, texts []string) ([][]string, error) {
func(h *highlight.SemanticHighlight, ctx context.Context, topks []int64, texts []string) ([][]string, [][]float32, error) {
return [][]string{
{"<em>highlighted</em> text 1"},
{"<em>highlighted</em> text 2"},
{"<em>highlighted</em> text 3"},
}, nil
{"<em>highlighted</em> text 1"},
{"<em>highlighted</em> text 2"},
{"<em>highlighted</em> text 3"},
}, [][]float32{
{0.9},
{0.8},
{0.7},
}, nil
}).Build()
defer mockProcess.UnPatch()
@ -484,12 +488,15 @@ func (s *SearchPipelineSuite) TestSemanticHighlightOpMultipleFields() {
// Use a counter to return different results for different calls
callCount := 0
mockProcess := mockey.Mock((*highlight.SemanticHighlight).Process).To(
func(h *highlight.SemanticHighlight, ctx context.Context, topks []int64, texts []string) ([][]string, error) {
func(h *highlight.SemanticHighlight, ctx context.Context, topks []int64, texts []string) ([][]string, [][]float32, error) {
callCount++
return [][]string{
{fmt.Sprintf("<em>highlighted</em> text field%d-1", callCount)},
{fmt.Sprintf("<em>highlighted</em> text field%d-2", callCount)},
}, nil
{fmt.Sprintf("<em>highlighted</em> text field%d-1", callCount)},
{fmt.Sprintf("<em>highlighted</em> text field%d-2", callCount)},
}, [][]float32{
{0.9},
{0.8},
}, nil
}).Build()
defer mockProcess.UnPatch()
@ -571,8 +578,8 @@ func (s *SearchPipelineSuite) TestSemanticHighlightOpEmptyResults() {
// Mock Process to return empty results
mockProcess := mockey.Mock((*highlight.SemanticHighlight).Process).To(
func(h *highlight.SemanticHighlight, ctx context.Context, topks []int64, texts []string) ([][]string, error) {
return [][]string{}, nil
func(h *highlight.SemanticHighlight, ctx context.Context, topks []int64, texts []string) ([][]string, [][]float32, error) {
return [][]string{}, [][]float32{}, nil
}).Build()
defer mockProcess.UnPatch()

View File

@ -29,7 +29,7 @@ import (
)
type semanticHighlightProvider interface {
highlight(ctx context.Context, query string, texts []string) ([][]string, error)
highlight(ctx context.Context, query string, texts []string) ([][]string, [][]float32, error)
maxBatch() int
}
@ -114,40 +114,43 @@ func (highlight *SemanticHighlight) GetFieldName(id int64) string {
return highlight.fieldNames[id]
}
func (highlight *SemanticHighlight) processOneQuery(ctx context.Context, query string, data []string) ([][]string, error) {
if len(data) == 0 {
return [][]string{}, nil
func (highlight *SemanticHighlight) processOneQuery(ctx context.Context, query string, documents []string) ([][]string, [][]float32, error) {
if len(documents) == 0 {
return [][]string{}, [][]float32{}, nil
}
highlights, err := highlight.provider.highlight(ctx, query, data)
highlights, scores, err := highlight.provider.highlight(ctx, query, documents)
if err != nil {
return nil, err
return nil, nil, err
}
if len(highlights) != len(data) {
return nil, fmt.Errorf("Highlights size must equal to data size, but got highlights size [%d], data size [%d]", len(highlights), len(data))
if len(highlights) != len(documents) || len(scores) != len(documents) {
return nil, nil, fmt.Errorf("Highlights size must equal to documents size, but got highlights size [%d], scores size [%d], documents size [%d]", len(highlights), len(scores), len(documents))
}
return highlights, nil
return highlights, scores, nil
}
func (highlight *SemanticHighlight) Process(ctx context.Context, topks []int64, data []string) ([][]string, error) {
func (highlight *SemanticHighlight) Process(ctx context.Context, topks []int64, documents []string) ([][]string, [][]float32, error) {
nq := len(topks)
if len(highlight.queries) != nq {
return nil, fmt.Errorf("nq must equal to queries size, but got nq [%d], queries size [%d], queries: [%v]", nq, len(highlight.queries), highlight.queries)
return nil, nil, fmt.Errorf("nq must equal to queries size, but got nq [%d], queries size [%d], queries: [%v]", nq, len(highlight.queries), highlight.queries)
}
if len(data) == 0 {
return [][]string{}, nil
if len(documents) == 0 {
return [][]string{}, [][]float32{}, nil
}
highlights := make([][]string, 0, len(data))
highlights := make([][]string, 0, len(documents))
scores := make([][]float32, 0, len(documents))
start := int64(0)
for i, query := range highlight.queries {
size := topks[i]
singleHighlights, err := highlight.processOneQuery(ctx, query, data[start:start+size])
singleQueryHighlights, singleQueryScores, err := highlight.processOneQuery(ctx, query, documents[start:start+size])
if err != nil {
return nil, err
return nil, nil, err
}
highlights = append(highlights, singleHighlights...)
highlights = append(highlights, singleQueryHighlights...)
scores = append(scores, singleQueryScores...)
start += size
}
return highlights, nil
return highlights, scores, nil
}

View File

@ -261,14 +261,18 @@ func (s *SemanticHighlightSuite) TestProcessOneQuery_Success() {
{"machine learning"},
{"machine"},
}
expectedScores := [][]float32{
{0.95},
{0.80},
}
mock1 := mockey.Mock(zilliz.NewZilliClient).To(func(_ string, _ string, _ string, _ map[string]string) (*zilliz.ZillizClient, error) {
return &zilliz.ZillizClient{}, nil
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, error) {
return expectedHighlights, nil
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, [][]float32, error) {
return expectedHighlights, expectedScores, nil
}).Build()
defer mock2.UnPatch()
@ -292,10 +296,11 @@ func (s *SemanticHighlightSuite) TestProcessOneQuery_Success() {
ctx := context.Background()
data := []string{"Machine learning is a subset of AI", "Machine learning is powerful"}
highlights, err := highlight.processOneQuery(ctx, "machine learning", data)
highlights, scores, err := highlight.processOneQuery(ctx, "machine learning", data)
s.NoError(err)
s.Equal(expectedHighlights, highlights)
s.Equal(expectedScores, scores)
}
func (s *SemanticHighlightSuite) TestProcessOneQuery_Error() {
@ -312,8 +317,8 @@ func (s *SemanticHighlightSuite) TestProcessOneQuery_Error() {
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, error) {
return nil, expectedError
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, [][]float32, error) {
return nil, nil, expectedError
}).Build()
defer mock2.UnPatch()
@ -337,10 +342,11 @@ func (s *SemanticHighlightSuite) TestProcessOneQuery_Error() {
ctx := context.Background()
data := []string{"test document"}
highlights, err := highlight.processOneQuery(ctx, "test query", data)
highlights, scores, err := highlight.processOneQuery(ctx, "test query", data)
s.Error(err)
s.Nil(highlights)
s.Nil(scores)
s.Equal(expectedError, err)
}
@ -354,9 +360,15 @@ func (s *SemanticHighlightSuite) TestProcess_Success() {
expectedHighlights1 := [][]string{
{"machine learning", "deep learning"},
}
expectedScores1 := [][]float32{
{0.90},
}
expectedHighlights2 := [][]string{
{"deep learning", "machine learning"},
}
expectedScores2 := [][]float32{
{0.85},
}
callCount := 0
mock1 := mockey.Mock(zilliz.NewZilliClient).To(func(_ string, _ string, _ string, _ map[string]string) (*zilliz.ZillizClient, error) {
@ -364,12 +376,12 @@ func (s *SemanticHighlightSuite) TestProcess_Success() {
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, query string, _ []string, _ map[string]string) ([][]string, error) {
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, query string, _ []string, _ map[string]string) ([][]string, [][]float32, error) {
callCount++
if query == "machine learning" {
return expectedHighlights1, nil
return expectedHighlights1, expectedScores1, nil
}
return expectedHighlights2, nil
return expectedHighlights2, expectedScores2, nil
}).Build()
defer mock2.UnPatch()
@ -393,11 +405,15 @@ func (s *SemanticHighlightSuite) TestProcess_Success() {
ctx := context.Background()
data := []string{"Machine learning document", "Deep learning document"}
highlights, err := highlight.Process(ctx, []int64{1, 1}, data)
highlights, scores, err := highlight.Process(ctx, []int64{1, 1}, data)
s.NoError(err)
s.NotNil(highlights)
s.Equal(2, callCount, "Should call highlight twice for two queries")
s.NotNil(scores)
s.Equal(2, len(scores))
s.Equal(1, len(scores[0]))
s.Equal(1, len(scores[1]))
}
func (s *SemanticHighlightSuite) TestProcess_NqMismatch() {
@ -432,11 +448,12 @@ func (s *SemanticHighlightSuite) TestProcess_NqMismatch() {
ctx := context.Background()
data := []string{"test document"}
highlights, err := highlight.Process(ctx, []int64{1, 1, 1}, data) // nq=3 but queries has only 1
highlights, scores, err := highlight.Process(ctx, []int64{1, 1, 1}, data) // nq=3 but queries has only 1
s.Error(err)
s.Nil(highlights)
s.Contains(err.Error(), "nq must equal to queries size")
s.Nil(scores)
}
func (s *SemanticHighlightSuite) TestProcess_ProviderError() {
@ -453,8 +470,8 @@ func (s *SemanticHighlightSuite) TestProcess_ProviderError() {
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, error) {
return nil, expectedError
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, [][]float32, error) {
return nil, nil, expectedError
}).Build()
defer mock2.UnPatch()
@ -478,11 +495,12 @@ func (s *SemanticHighlightSuite) TestProcess_ProviderError() {
ctx := context.Background()
data := []string{"test document"}
highlights, err := highlight.Process(ctx, []int64{1}, data)
highlights, scores, err := highlight.Process(ctx, []int64{1}, data)
s.Error(err)
s.Nil(highlights)
s.Equal(expectedError, err)
s.Nil(scores)
}
func (s *SemanticHighlightSuite) TestProcess_EmptyData() {
@ -497,8 +515,12 @@ func (s *SemanticHighlightSuite) TestProcess_EmptyData() {
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, texts []string, _ map[string]string) ([][]string, error) {
return [][]string{texts}, nil
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, texts []string, _ map[string]string) ([][]string, [][]float32, error) {
scores := make([][]float32, len(texts))
for i := range texts {
scores[i] = []float32{0.75}
}
return [][]string{texts}, scores, nil
}).Build()
defer mock2.UnPatch()
@ -522,18 +544,25 @@ func (s *SemanticHighlightSuite) TestProcess_EmptyData() {
ctx := context.Background()
data := []string{}
highlights, err := highlight.Process(ctx, []int64{0, 0, 0}, data)
highlights, scores, err := highlight.Process(ctx, []int64{0, 0, 0}, data)
s.NoError(err)
s.NotNil(highlights)
s.Equal(0, len(highlights))
s.NotNil(scores)
s.Equal(0, len(scores))
data2 := []string{"test document"}
highlights2, err := highlight.Process(ctx, []int64{0, 1, 0}, data2)
highlights2, scores2, err := highlight.Process(ctx, []int64{0, 1, 0}, data2)
s.NoError(err)
s.Equal(1, len(highlights2))
s.Equal([][]string{{"test document"}}, highlights2)
s.NotNil(scores2)
s.Equal(1, len(scores2))
s.Equal(1, len(scores2[0]))
s.Equal(float32(0.75), scores2[0][0])
}
func (s *SemanticHighlightSuite) TestBaseSemanticHighlightProvider_MaxBatch() {

View File

@ -69,10 +69,10 @@ func newZillizHighlightProvider(params []*commonpb.KeyValuePair, conf map[string
return &provider, nil
}
func (h *zillizHighlightProvider) highlight(ctx context.Context, query string, texts []string) ([][]string, error) {
highlights, err := h.client.Highlight(ctx, query, texts, h.modelParams)
func (h *zillizHighlightProvider) highlight(ctx context.Context, query string, texts []string) ([][]string, [][]float32, error) {
highlights, scores, err := h.client.Highlight(ctx, query, texts, h.modelParams)
if err != nil {
return nil, err
return nil, nil, err
}
return highlights, nil
return highlights, scores, nil
}

View File

@ -177,14 +177,19 @@ func (s *ZillizHighlightProviderSuite) TestZillizHighlightProvider_Highlight_Suc
{"Deep learning", "machine learning"},
{"Natural language processing", "machine learning"},
}
expectedScores := [][]float32{
{0.9, 0.8},
{0.8, 0.7},
{0.7, 0.6},
}
mock1 := mockey.Mock(zilliz.NewZilliClient).To(func(_ string, _ string, _ string, _ map[string]string) (*zilliz.ZillizClient, error) {
return &zilliz.ZillizClient{}, nil
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, error) {
return expectedHighlights, nil
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, [][]float32, error) {
return expectedHighlights, expectedScores, nil
}).Build()
defer mock2.UnPatch()
@ -197,10 +202,11 @@ func (s *ZillizHighlightProviderSuite) TestZillizHighlightProvider_Highlight_Suc
s.NoError(err)
s.NotNil(provider)
highlights, err := provider.highlight(ctx, query, texts)
highlights, scores, err := provider.highlight(ctx, query, texts)
s.NoError(err)
s.Equal(expectedHighlights, highlights)
s.Equal(expectedScores, scores)
}
func (s *ZillizHighlightProviderSuite) TestZillizHighlightProvider_Highlight_Error() {
@ -215,8 +221,8 @@ func (s *ZillizHighlightProviderSuite) TestZillizHighlightProvider_Highlight_Err
}).Build()
defer mock1.UnPatch()
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, error) {
return nil, expectedError
mock2 := mockey.Mock((*zilliz.ZillizClient).Highlight).To(func(_ *zilliz.ZillizClient, _ context.Context, _ string, _ []string, _ map[string]string) ([][]string, [][]float32, error) {
return nil, nil, expectedError
}).Build()
defer mock2.UnPatch()
@ -230,9 +236,10 @@ func (s *ZillizHighlightProviderSuite) TestZillizHighlightProvider_Highlight_Err
s.NotNil(provider)
// Test the highlight method
highlights, err := provider.highlight(ctx, query, texts)
highlights, scores, err := provider.highlight(ctx, query, texts)
s.Error(err)
s.Nil(highlights)
s.Nil(scores)
s.Equal(expectedError, err)
}

View File

@ -253,7 +253,7 @@ func (c *ZillizClient) Rerank(ctx context.Context, query string, texts []string,
return res.Scores, nil
}
func (c *ZillizClient) Highlight(ctx context.Context, query string, texts []string, params map[string]string) ([][]string, error) {
func (c *ZillizClient) Highlight(ctx context.Context, query string, texts []string, params map[string]string) ([][]string, [][]float32, error) {
stub := modelservicepb.NewHighlightServiceClient(c.conn)
req := &modelservicepb.HighlightRequest{
Query: query,
@ -263,11 +263,27 @@ func (c *ZillizClient) Highlight(ctx context.Context, query string, texts []stri
ctx = c.setMeta(ctx)
res, err := stub.Highlight(ctx, req)
if err != nil {
return nil, err
return nil, nil, err
}
highlights := make([][]string, 0, len(res.GetResults()))
scores := make([][]float32, 0, len(res.GetResults()))
for _, ret := range res.GetResults() {
highlights = append(highlights, ret.GetSentences())
sentences := ret.GetSentences()
retScores := ret.GetScores()
// Handle nil cases
if sentences == nil {
sentences = []string{}
}
if retScores == nil {
retScores = []float32{}
}
if len(sentences) != len(retScores) {
return nil, nil, fmt.Errorf("sentences length %d does not match scores length %d", len(sentences), len(retScores))
}
highlights = append(highlights, sentences)
scores = append(scores, retScores)
}
return highlights, nil
return highlights, scores, nil
}

View File

@ -585,9 +585,11 @@ func TestZillizClient_Highlight(t *testing.T) {
Results: []*modelservicepb.HighlightResult{
{
Sentences: []string{"highlight1", "highlight2"},
Scores: []float32{0.9, 0.8},
},
{
Sentences: []string{"highlight3", "highlight4"},
Scores: []float32{0.8, 0.7},
},
},
},
@ -623,9 +625,10 @@ func TestZillizClient_Highlight(t *testing.T) {
query := "test query"
texts := []string{"doc1", "doc2", "doc3"}
params := map[string]string{"param1": "value1"}
highlights, err := client.Highlight(ctx, query, texts, params)
highlights, scores, err := client.Highlight(ctx, query, texts, params)
assert.NoError(t, err)
assert.Equal(t, [][]string{{"highlight1", "highlight2"}, {"highlight3", "highlight4"}}, highlights)
assert.Equal(t, [][]float32{{0.9, 0.8}, {0.8, 0.7}}, scores)
}
func TestZillizClient_Highlight_Error(t *testing.T) {
@ -668,7 +671,130 @@ func TestZillizClient_Highlight_Error(t *testing.T) {
query := "test query"
texts := []string{"doc1", "doc2", "doc3"}
params := map[string]string{"param1": "value1"}
highlights, err := client.Highlight(ctx, query, texts, params)
highlights, scores, err := client.Highlight(ctx, query, texts, params)
assert.Error(t, err)
assert.Nil(t, highlights)
assert.Nil(t, scores)
}
func TestZillizClient_Highlight_MismatchLength(t *testing.T) {
tests := []struct {
name string
response *modelservicepb.HighlightResponse
expectedErrMsg string
}{
{
name: "more sentences than scores",
response: &modelservicepb.HighlightResponse{
Status: &modelservicepb.Status{Code: 0, Msg: "success"},
Results: []*modelservicepb.HighlightResult{
{
Sentences: []string{"highlight1", "highlight2", "highlight3"},
Scores: []float32{0.9, 0.8},
},
},
},
expectedErrMsg: "sentences length 3 does not match scores length 2",
},
{
name: "more scores than sentences",
response: &modelservicepb.HighlightResponse{
Status: &modelservicepb.Status{Code: 0, Msg: "success"},
Results: []*modelservicepb.HighlightResult{
{
Sentences: []string{"highlight1"},
Scores: []float32{0.9, 0.8, 0.7},
},
},
},
expectedErrMsg: "sentences length 1 does not match scores length 3",
},
{
name: "mismatch in second result",
response: &modelservicepb.HighlightResponse{
Status: &modelservicepb.Status{Code: 0, Msg: "success"},
Results: []*modelservicepb.HighlightResult{
{
Sentences: []string{"highlight1", "highlight2"},
Scores: []float32{0.9, 0.8},
},
{
Sentences: []string{"highlight3"},
Scores: []float32{0.7, 0.6},
},
},
},
expectedErrMsg: "sentences length 1 does not match scores length 2",
},
{
name: "nil sentences",
response: &modelservicepb.HighlightResponse{
Status: &modelservicepb.Status{Code: 0, Msg: "success"},
Results: []*modelservicepb.HighlightResult{
{},
{
Sentences: []string{"highlight3"},
Scores: []float32{0.7, 0.6},
},
},
},
expectedErrMsg: "sentences length 1 does not match scores length 2",
},
{
name: "nil scores",
response: &modelservicepb.HighlightResponse{
Status: &modelservicepb.Status{Code: 0, Msg: "success"},
Results: []*modelservicepb.HighlightResult{
{
Sentences: []string{"highlight1", "highlight2"},
Scores: nil,
},
},
},
expectedErrMsg: "sentences length 2 does not match scores length 0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, lis, dialer := setupMockServer(t)
defer lis.Close()
defer s.Stop()
mockServer := &mockHighlightServer{
response: tt.response,
}
modelservicepb.RegisterHighlightServiceServer(s, mockServer)
go func() {
if err := s.Serve(lis); err != nil {
fmt.Printf("Server exited with error: %v\n", err)
}
}()
conn, err := grpc.DialContext(
context.Background(),
"bufnet",
grpc.WithContextDialer(dialer),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
)
require.NoError(t, err)
defer conn.Close()
client := &ZillizClient{
modelDeploymentID: "test-deployment",
clusterID: "test-cluster",
conn: conn,
}
ctx := context.Background()
highlights, scores, err := client.Highlight(ctx, "test query", []string{"doc1"}, map[string]string{})
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedErrMsg)
assert.Nil(t, highlights)
assert.Nil(t, scores)
})
}
}

View File

@ -22,7 +22,7 @@ require (
github.com/jolestar/go-commons-pool/v2 v2.1.2
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12
github.com/klauspost/compress v1.18.0
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20251224033913-b2fbe2627f1c
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260113024922-c7feeb806088
github.com/minio/minio-go/v7 v7.0.73
github.com/panjf2000/ants/v2 v2.11.3
github.com/prometheus/client_golang v1.20.5

View File

@ -482,8 +482,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6 h1:YHMFI6L
github.com/milvus-io/cgosymbolizer v0.0.0-20250318084424-114f4050c3a6/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20251224033913-b2fbe2627f1c h1:W66Mf/hlR7SWHrSr7xpyt4ACE8v9/C7Y9dMJiZCvp3s=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20251224033913-b2fbe2627f1c/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260113024922-c7feeb806088 h1:qzlpV+1xygF/XK0bRVoLFg03uAQSCZ2AywZR3wt5ov0=
github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260113024922-c7feeb806088/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.73 h1:qr2vi96Qm7kZ4v7LLebjte+MQh621fFWnv93p12htEo=

View File

@ -82,6 +82,7 @@ message HighlightRequest {
message HighlightResult {
repeated string sentences = 1;
repeated float scores = 2;
}
message HighlightResponse {

View File

@ -696,7 +696,8 @@ type HighlightResult struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Sentences []string `protobuf:"bytes,1,rep,name=sentences,proto3" json:"sentences,omitempty"`
Sentences []string `protobuf:"bytes,1,rep,name=sentences,proto3" json:"sentences,omitempty"`
Scores []float32 `protobuf:"fixed32,2,rep,packed,name=scores,proto3" json:"scores,omitempty"`
}
func (x *HighlightResult) Reset() {
@ -738,6 +739,13 @@ func (x *HighlightResult) GetSentences() []string {
return nil
}
func (x *HighlightResult) GetScores() []float32 {
if x != nil {
return x.Scores
}
return nil
}
type HighlightResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -925,57 +933,58 @@ var file_model_service_proto_rawDesc = []byte{
0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x3a, 0x02, 0x38, 0x01, 0x22, 0x2f, 0x0a, 0x0f, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68,
0x3a, 0x02, 0x38, 0x01, 0x22, 0x47, 0x0a, 0x0f, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x74, 0x65,
0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x74,
0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xae, 0x02, 0x0a, 0x11, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69,
0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x5a, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f,
0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x6d, 0x69, 0x6c,
0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e,
0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e,
0x66, 0x6f, 0x12, 0x44, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x02, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x22, 0xae, 0x02,
0x0a, 0x11, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e,
0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52,
0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x1a, 0x3c, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72,
0x61, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x86, 0x01, 0x0a, 0x14, 0x54, 0x65, 0x78, 0x74, 0x45,
0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x6e, 0x0a, 0x09, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x2f, 0x2e, 0x6d,
0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65,
0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x45, 0x6d, 0x62,
0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e,
0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64,
0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x45, 0x6d,
0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32,
0x76, 0x0a, 0x0d, 0x52, 0x65, 0x72, 0x61, 0x6e, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x65, 0x0a, 0x06, 0x52, 0x65, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x2c, 0x2e, 0x6d, 0x69, 0x6c,
0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x72, 0x61, 0x6e,
0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x72, 0x61, 0x6e, 0x6b, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x7a, 0x0a, 0x10, 0x48, 0x69, 0x67, 0x68, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x66, 0x0a, 0x09, 0x48,
0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x5a,
0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48,
0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x07, 0x72, 0x65,
0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6d, 0x69,
0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73,
0x1a, 0x3c, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x86,
0x01, 0x0a, 0x14, 0x54, 0x65, 0x78, 0x74, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6e, 0x0a, 0x09, 0x45, 0x6d, 0x62, 0x65, 0x64,
0x64, 0x69, 0x6e, 0x67, 0x12, 0x2f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x2e, 0x54, 0x65, 0x78, 0x74, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x2e, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2d, 0x69, 0x6f, 0x2f, 0x6d, 0x69, 0x6c, 0x76,
0x75, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x76, 0x0a, 0x0d, 0x52, 0x65, 0x72, 0x61, 0x6e,
0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x65, 0x0a, 0x06, 0x52, 0x65, 0x72, 0x61,
0x6e, 0x6b, 0x12, 0x2c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54,
0x65, 0x78, 0x74, 0x52, 0x65, 0x72, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2d, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x65, 0x78,
0x74, 0x52, 0x65, 0x72, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32,
0x7a, 0x0a, 0x10, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x12, 0x66, 0x0a, 0x09, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74,
0x12, 0x2b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x67,
0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e,
0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x6f, 0x64,
0x65, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x67, 0x68, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73,
0x2d, 0x69, 0x6f, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x76,
0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (