KevinStephenson
Adding in weaviate code
b110593
raw
history blame
84.1 kB
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//
package traverser
import (
"context"
"testing"
"time"
"github.com/go-openapi/strfmt"
"github.com/pkg/errors"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/weaviate/weaviate/entities/additional"
"github.com/weaviate/weaviate/entities/dto"
"github.com/weaviate/weaviate/entities/filters"
"github.com/weaviate/weaviate/entities/models"
"github.com/weaviate/weaviate/entities/modulecapabilities"
"github.com/weaviate/weaviate/entities/schema"
"github.com/weaviate/weaviate/entities/search"
"github.com/weaviate/weaviate/entities/searchparams"
"github.com/weaviate/weaviate/entities/vectorindex/hnsw"
"github.com/weaviate/weaviate/usecases/config"
)
var defaultConfig = config.Config{
QueryDefaults: config.QueryDefaults{
Limit: 100,
},
QueryMaximumResults: 100,
}
func Test_Explorer_GetClass(t *testing.T) {
t.Run("when an explore param is set for nearVector", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
metrics := &fakeMetrics{}
log, _ := test.NewNullLogger()
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7}
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
t.Run("usage must be tracked", func(t *testing.T) {
metrics.AssertExpectations(t)
})
})
t.Run("when an explore param is set for nearObject without id and beacon", func(t *testing.T) {
t.Run("with distance", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearObject: &searchparams.NearObject{
Distance: 0.1,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.NotNil(t, err)
assert.Nil(t, res)
assert.Contains(t, err.Error(), "explorer: get class: vectorize params: nearObject params: empty id and beacon")
})
})
t.Run("with certainty", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearObject: &searchparams.NearObject{
Certainty: 0.9,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.NotNil(t, err)
assert.Nil(t, res)
assert.Contains(t, err.Error(), "explorer: get class: vectorize params: nearObject params: empty id and beacon")
})
})
})
t.Run("when an explore param is set for nearObject with beacon", func(t *testing.T) {
t.Run("with distance", func(t *testing.T) {
t.Run("with certainty", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearObject: &searchparams.NearObject{
Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Distance: 0.1,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchRes := search.Result{
ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Schema: map[string]interface{}{
"name": "Foo",
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
search.
On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")).
Return(&searchRes, nil)
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain object", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
})
})
t.Run("with certainty", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearObject: &searchparams.NearObject{
Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Certainty: 0.9,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchRes := search.Result{
ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Schema: map[string]interface{}{
"name": "Foo",
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
search.
On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")).
Return(&searchRes, nil)
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain object", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
})
})
t.Run("when an explore param is set for nearObject with id", func(t *testing.T) {
t.Run("with distance", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearObject: &searchparams.NearObject{
ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Distance: 0.1,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchRes := search.Result{
ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Schema: map[string]interface{}{
"name": "Foo",
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
search.
On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")).
Return(&searchRes, nil)
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain object", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
})
t.Run("with certainty", func(t *testing.T) {
// TODO: this is a module specific test case, which relies on the
// text2vec-contextionary module
params := dto.GetParams{
ClassName: "BestClass",
NearObject: &searchparams.NearObject{
ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Certainty: 0.9,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchRes := search.Result{
ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041",
Schema: map[string]interface{}{
"name": "Foo",
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
metrics := &fakeMetrics{}
log, _ := test.NewNullLogger()
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
search.
On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")).
Return(&searchRes, nil)
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain object", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
})
})
t.Run("when an explore param is set for nearVector and the required distance not met",
func(t *testing.T) {
t.Run("with distance", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
Distance: 0.4,
WithDistance: true,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Dist: 2 * 0.69,
Dims: 128,
},
{
ID: "id2",
Dist: 2 * 0.69,
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7}
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("no concept met the required certainty", func(t *testing.T) {
assert.Len(t, res, 0)
})
})
t.Run("with certainty", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
Certainty: 0.8,
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Dist: 2 * 0.69,
Dims: 128,
},
{
ID: "id2",
Dist: 2 * 0.69,
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7}
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("no concept met the required certainty", func(t *testing.T) {
assert.Len(t, res, 0)
})
})
})
t.Run("when two conflicting (nearVector, nearObject) near searchers are set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
},
NearObject: &searchparams.NearObject{
Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041",
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
_, err := explorer.GetClass(context.Background(), params)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "parameters which are conflicting")
})
t.Run("when no explore param is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
})
t.Run("near vector with group", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
},
Group: &dto.GroupParams{
Strategy: "closest",
Force: 1.0,
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7}
expectedParamsToSearch.AdditionalProperties.Vector = true
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 1)
})
})
t.Run("when the semanticPath prop is set but cannot be", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
ModuleParams: map[string]interface{}{
"semanticPath": getDefaultParam("semanticPath"),
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("error can't be nil", func(t *testing.T) {
assert.NotNil(t, err)
assert.Nil(t, res)
assert.Contains(t, err.Error(), "unknown capability: semanticPath")
})
})
t.Run("when the classification prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
Classification: true,
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"classification": nil,
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "1234",
},
},
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
"_additional": map[string]interface{}{
"classification": &additional.Classification{
ID: "1234",
},
},
}, res[1])
})
})
t.Run("when the interpretation prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
ModuleParams: map[string]interface{}{
"interpretation": true,
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"interpretation": nil,
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
AdditionalProperties: models.AdditionalProperties{
"interpretation": &Interpretation{
Source: []*InterpretationSource{
{
Concept: "foo",
Weight: 0.123,
Occurrence: 123,
},
},
},
},
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
"_additional": map[string]interface{}{
"interpretation": &Interpretation{
Source: []*InterpretationSource{
{
Concept: "foo",
Weight: 0.123,
Occurrence: 123,
},
},
},
},
}, res[1])
})
})
t.Run("when the vector _additional prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
Vector: true,
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Vector: []float32{0.1, -0.3},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "_additional.vector", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain vector", func(t *testing.T) {
require.Len(t, res, 1)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"vector": []float32{0.1, -0.3},
},
}, res[0])
})
})
t.Run("when the creationTimeUnix _additional prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
CreationTimeUnix: true,
},
}
now := time.Now().UnixNano() / int64(time.Millisecond)
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Created: now,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain creationTimeUnix", func(t *testing.T) {
require.Len(t, res, 1)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"creationTimeUnix": now,
},
}, res[0])
})
})
t.Run("when the lastUpdateTimeUnix _additional prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
LastUpdateTimeUnix: true,
},
}
now := time.Now().UnixNano() / int64(time.Millisecond)
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Updated: now,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
search.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain lastUpdateTimeUnix", func(t *testing.T) {
require.Len(t, res, 1)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"lastUpdateTimeUnix": now,
},
}, res[0])
})
})
t.Run("when the nearestNeighbors prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
ModuleParams: map[string]interface{}{
"nearestNeighbors": true,
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
},
}
searcher := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
extender := &fakeExtender{
returnArgs: []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "foo",
Distance: 0.1,
},
},
},
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
AdditionalProperties: models.AdditionalProperties{
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "bar",
Distance: 0.1,
},
},
},
},
},
},
}
explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(extender, nil, nil), nil, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
searcher.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
searcher.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "foo",
Distance: 0.1,
},
},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"name": "Bar",
"_additional": map[string]interface{}{
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "bar",
Distance: 0.1,
},
},
},
},
}, res[1])
})
})
t.Run("when the featureProjection prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
ModuleParams: map[string]interface{}{
"featureProjection": getDefaultParam("featureProjection"),
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
},
}
searcher := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
projector := &fakeProjector{
returnArgs: []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"featureProjection": &FeatureProjection{
Vector: []float32{0, 1},
},
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
AdditionalProperties: models.AdditionalProperties{
"featureProjection": &FeatureProjection{
Vector: []float32{1, 0},
},
},
},
},
}
explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(nil, projector, nil), nil, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
searcher.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
searcher.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"featureProjection": &FeatureProjection{
Vector: []float32{0, 1},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"name": "Bar",
"_additional": map[string]interface{}{
"featureProjection": &FeatureProjection{
Vector: []float32{1, 0},
},
},
}, res[1])
})
})
t.Run("when the _additional on ref prop is set", func(t *testing.T) {
now := time.Now().UnixMilli()
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
Properties: []search.SelectProperty{
{
Name: "ofBestRefClass",
Refs: []search.SelectClass{
{
ClassName: "BestRefClass",
AdditionalProperties: additional.Properties{
ID: true,
Vector: true,
CreationTimeUnix: true,
LastUpdateTimeUnix: true,
},
},
},
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"vector": []float32{1, 0},
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
},
},
},
},
AdditionalProperties: models.AdditionalProperties{
"classification": nil,
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
},
},
},
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "1234",
},
},
},
}
fakeSearch := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(fakeSearch, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
fakeSearch.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
fakeSearch.AssertExpectations(t)
})
t.Run("response must contain _additional id and vector params for ref prop", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"vector": []float32{1, 0},
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"vector": []float32{1, 0},
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
"vector": nil,
"creationTimeUnix": nil,
"lastUpdateTimeUnix": nil,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
},
},
},
"_additional": map[string]interface{}{
"classification": &additional.Classification{
ID: "1234",
},
},
}, res[1])
})
})
t.Run("when the _additional on all refs prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
Properties: []search.SelectProperty{
{
Name: "ofBestRefClass",
Refs: []search.SelectClass{
{
ClassName: "BestRefClass",
AdditionalProperties: additional.Properties{
ID: true,
},
RefProperties: search.SelectProperties{
search.SelectProperty{
Name: "ofBestRefInnerClass",
Refs: []search.SelectClass{
{
ClassName: "BestRefInnerClass",
AdditionalProperties: additional.Properties{
ID: true,
},
},
},
},
},
},
},
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa",
},
},
},
},
},
},
},
AdditionalProperties: models.AdditionalProperties{
"classification": nil,
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb",
},
},
},
},
},
},
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "1234",
},
},
},
}
fakeSearch := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(fakeSearch, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
fakeSearch.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
fakeSearch.AssertExpectations(t)
})
t.Run("response must contain _additional id param for ref prop", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa",
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa",
},
},
},
},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb",
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb",
},
},
},
},
},
},
"_additional": map[string]interface{}{
"classification": &additional.Classification{
ID: "1234",
},
},
}, res[1])
})
})
t.Run("when the _additional on lots of refs prop is set", func(t *testing.T) {
now := time.Now().UnixMilli()
vec := []float32{1, 2, 3}
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
Properties: []search.SelectProperty{
{
Name: "ofBestRefClass",
Refs: []search.SelectClass{
{
ClassName: "BestRefClass",
AdditionalProperties: additional.Properties{
ID: true,
Vector: true,
CreationTimeUnix: true,
LastUpdateTimeUnix: true,
},
RefProperties: search.SelectProperties{
search.SelectProperty{
Name: "ofBestRefInnerClass",
Refs: []search.SelectClass{
{
ClassName: "BestRefInnerClass",
AdditionalProperties: additional.Properties{
ID: true,
Vector: true,
CreationTimeUnix: true,
LastUpdateTimeUnix: true,
},
RefProperties: search.SelectProperties{
search.SelectProperty{
Name: "ofBestRefInnerInnerClass",
Refs: []search.SelectClass{
{
ClassName: "BestRefInnerInnerClass",
AdditionalProperties: additional.Properties{
ID: true,
Vector: true,
CreationTimeUnix: true,
LastUpdateTimeUnix: true,
},
},
},
},
},
},
},
},
},
},
},
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerInnerClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4aaa",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
},
},
},
},
},
},
},
},
},
AdditionalProperties: models.AdditionalProperties{
"classification": nil,
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerInnerClass",
Fields: map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4bbb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
},
},
},
},
},
},
},
},
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "1234",
},
},
},
}
fakeSearch := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(fakeSearch, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
fakeSearch.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
fakeSearch.AssertExpectations(t)
})
t.Run("response must contain _additional id param for ref prop", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerInnerClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4aaa",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4aaa",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
},
},
},
},
},
},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
"ofBestRefClass": []interface{}{
search.LocalRef{
Class: "BestRefClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
"ofBestRefInnerInnerClass": []interface{}{
search.LocalRef{
Class: "BestRefInnerInnerClass",
Fields: map[string]interface{}{
"_additional": map[string]interface{}{
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4bbb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
"id": "2d68456c-73b4-4cfc-a6dc-718efc5b4bbb",
"creationTimeUnix": now,
"lastUpdateTimeUnix": now,
"vector": vec,
},
},
},
},
},
},
},
},
},
"_additional": map[string]interface{}{
"classification": &additional.Classification{
ID: "1234",
},
},
}, res[1])
})
})
t.Run("when the almost all _additional props set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
ID: true,
Classification: true,
ModuleParams: map[string]interface{}{
"interpretation": true,
"nearestNeighbors": true,
},
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "1234",
},
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "foo",
Distance: 0.1,
},
},
},
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "5678",
},
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "bar",
Distance: 0.1,
},
},
},
},
},
}
searcher := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
extender := &fakeExtender{
returnArgs: []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "1234",
},
"interpretation": &Interpretation{
Source: []*InterpretationSource{
{
Concept: "foo",
Weight: 0.123,
Occurrence: 123,
},
},
},
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "foo",
Distance: 0.1,
},
},
},
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
AdditionalProperties: models.AdditionalProperties{
"classification": &additional.Classification{
ID: "5678",
},
"interpretation": &Interpretation{
Source: []*InterpretationSource{
{
Concept: "bar",
Weight: 0.456,
Occurrence: 456,
},
},
},
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "bar",
Distance: 0.1,
},
},
},
},
},
},
}
explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(extender, nil, nil), nil, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = nil
searcher.
On("Search", expectedParamsToSearch).
Return(searchResults, nil)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
searcher.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"id": strfmt.UUID("id1"),
"classification": &additional.Classification{
ID: "1234",
},
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "foo",
Distance: 0.1,
},
},
},
"interpretation": &Interpretation{
Source: []*InterpretationSource{
{
Concept: "foo",
Weight: 0.123,
Occurrence: 123,
},
},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"name": "Bar",
"_additional": map[string]interface{}{
"id": strfmt.UUID("id2"),
"classification": &additional.Classification{
ID: "5678",
},
"nearestNeighbors": &NearestNeighbors{
Neighbors: []*NearestNeighbor{
{
Concept: "bar",
Distance: 0.1,
},
},
},
"interpretation": &Interpretation{
Source: []*InterpretationSource{
{
Concept: "bar",
Weight: 0.456,
Occurrence: 456,
},
},
},
},
}, res[1])
})
})
}
func Test_Explorer_GetClass_With_Modules(t *testing.T) {
t.Run("when an explore param is set for nearCustomText", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
}),
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
Dims: 128,
},
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{1, 2, 3}
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
}, res[0])
assert.Equal(t,
map[string]interface{}{
"age": 200,
}, res[1])
})
})
t.Run("when an explore param is set for nearCustomText and the required distance not met",
func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
"distance": float64(0.2),
}),
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Dist: 2 * 0.69,
Dims: 128,
},
{
ID: "id2",
Dist: 2 * 0.69,
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{1, 2, 3}
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("no object met the required distance", func(t *testing.T) {
assert.Len(t, res, 0)
})
})
t.Run("when an explore param is set for nearCustomText and the required certainty not met",
func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
"certainty": float64(0.8),
}),
},
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
}
searchResults := []search.Result{
{
ID: "id1",
Dist: 2 * 0.69,
Dims: 128,
},
{
ID: "id2",
Dist: 2 * 0.69,
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{1, 2, 3}
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("vector search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("no object met the required certainty", func(t *testing.T) {
assert.Len(t, res, 0)
})
})
t.Run("when two conflicting (nearVector, nearCustomText) near searchers are set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
},
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
}),
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
_, err := explorer.GetClass(context.Background(), params)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "parameters which are conflicting")
})
t.Run("when two conflicting (nearCustomText, nearObject) near searchers are set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
NearObject: &searchparams.NearObject{
Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041",
},
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
}),
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
_, err := explorer.GetClass(context.Background(), params)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "parameters which are conflicting")
})
t.Run("when three conflicting (nearCustomText, nearVector, nearObject) near searchers are set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
NearVector: &searchparams.NearVector{
Vector: []float32{0.8, 0.2, 0.7},
},
NearObject: &searchparams.NearObject{
Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041",
},
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
}),
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
_, err := explorer.GetClass(context.Background(), params)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "parameters which are conflicting")
})
t.Run("when nearCustomText.moveTo has no concepts and objects defined", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
"moveTo": map[string]interface{}{
"force": float64(0.1),
},
}),
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
_, err := explorer.GetClass(context.Background(), params)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "needs to have defined either 'concepts' or 'objects' fields")
})
t.Run("when nearCustomText.moveAwayFrom has no concepts and objects defined", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foo"},
"moveAwayFrom": map[string]interface{}{
"force": float64(0.1),
},
}),
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
_, err := explorer.GetClass(context.Background(), params)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "needs to have defined either 'concepts' or 'objects' fields")
})
t.Run("when the distance prop is set", func(t *testing.T) {
params := dto.GetParams{
Filters: nil,
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
SearchVector: []float32{1.0, 2.0, 3.0},
AdditionalProperties: additional.Properties{
Distance: true,
},
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foobar"},
"limit": 100,
"distance": float64(1.38),
}),
},
}
searchResults := []search.Result{
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Vector: []float32{0.5, 1.5, 0.0},
Dist: 2 * 0.69,
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{1.0, 2.0, 3.0}
// expectedParamsToSearch.SearchVector = nil
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 1)
resMap := res[0].(map[string]interface{})
assert.Equal(t, 2, len(resMap))
assert.Contains(t, resMap, "age")
assert.Equal(t, 200, resMap["age"])
additionalMap := resMap["_additional"]
assert.Contains(t, additionalMap, "distance")
assert.InEpsilon(t, 1.38, additionalMap.(map[string]interface{})["distance"].(float32), 0.000001)
})
})
t.Run("when the certainty prop is set", func(t *testing.T) {
params := dto.GetParams{
Filters: nil,
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
SearchVector: []float32{1.0, 2.0, 3.0},
AdditionalProperties: additional.Properties{
Certainty: true,
},
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foobar"},
"limit": 100,
"certainty": float64(0.1),
}),
},
}
searchResults := []search.Result{
{
ID: "id2",
Schema: map[string]interface{}{
"age": 200,
},
Vector: []float32{0.5, 1.5, 0.0},
Dist: 2 * 0.69,
Dims: 128,
},
}
search := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
metrics := &fakeMetrics{}
explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
schemaGetter := newFakeSchemaGetter("BestClass")
schemaGetter.SetVectorIndexConfig(hnsw.UserConfig{Distance: "cosine"})
explorer.schemaGetter = schemaGetter
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{1.0, 2.0, 3.0}
// expectedParamsToSearch.SearchVector = nil
search.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
search.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 1)
resMap := res[0].(map[string]interface{})
assert.Equal(t, 2, len(resMap))
assert.Contains(t, resMap, "age")
assert.Equal(t, 200, resMap["age"])
additionalMap := resMap["_additional"]
assert.Contains(t, additionalMap, "certainty")
// Certainty is fixed to 0.69 in this mock
assert.InEpsilon(t, 0.31, additionalMap.(map[string]interface{})["certainty"], 0.000001)
})
})
t.Run("when the semanticPath prop is set", func(t *testing.T) {
params := dto.GetParams{
ClassName: "BestClass",
Pagination: &filters.Pagination{Limit: 100},
Filters: nil,
AdditionalProperties: additional.Properties{
ModuleParams: map[string]interface{}{
"semanticPath": getDefaultParam("semanticPath"),
},
},
ModuleParams: map[string]interface{}{
"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
"concepts": []interface{}{"foobar"},
}),
},
}
searchResults := []search.Result{
{
ID: "id1",
Schema: map[string]interface{}{
"name": "Foo",
},
},
{
ID: "id2",
Schema: map[string]interface{}{
"name": "Bar",
},
},
}
searcher := &fakeVectorSearcher{}
log, _ := test.NewNullLogger()
pathBuilder := &fakePathBuilder{
returnArgs: []search.Result{
{
ID: "id1",
Dims: 128,
Schema: map[string]interface{}{
"name": "Foo",
},
AdditionalProperties: models.AdditionalProperties{
"semanticPath": &SemanticPath{
Path: []*SemanticPathElement{
{
Concept: "pathelem1",
DistanceToQuery: 0,
DistanceToResult: 2.1,
DistanceToPrevious: nil,
DistanceToNext: ptFloat32(0.5),
},
{
Concept: "pathelem2",
DistanceToQuery: 2.1,
DistanceToResult: 0,
DistanceToPrevious: ptFloat32(0.5),
DistanceToNext: nil,
},
},
},
},
},
{
ID: "id2",
Dims: 128,
Schema: map[string]interface{}{
"name": "Bar",
},
AdditionalProperties: models.AdditionalProperties{
"semanticPath": &SemanticPath{
Path: []*SemanticPathElement{
{
Concept: "pathelem1",
DistanceToQuery: 0,
DistanceToResult: 2.1,
DistanceToPrevious: nil,
DistanceToNext: ptFloat32(0.5),
},
{
Concept: "pathelem2",
DistanceToQuery: 2.1,
DistanceToResult: 0,
DistanceToPrevious: ptFloat32(0.5),
DistanceToNext: nil,
},
},
},
},
},
},
}
metrics := &fakeMetrics{}
explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(nil, nil, pathBuilder), metrics, defaultConfig)
explorer.SetSchemaGetter(&fakeSchemaGetter{
schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{
{Class: "BestClass"},
}}},
})
expectedParamsToSearch := params
expectedParamsToSearch.SearchVector = []float32{1, 2, 3}
expectedParamsToSearch.AdditionalProperties.Vector = true // any custom additional params will trigger vector
searcher.
On("VectorSearch", expectedParamsToSearch).
Return(searchResults, nil)
metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128)
res, err := explorer.GetClass(context.Background(), params)
t.Run("class search must be called with right params", func(t *testing.T) {
assert.Nil(t, err)
searcher.AssertExpectations(t)
})
t.Run("response must contain concepts", func(t *testing.T) {
require.Len(t, res, 2)
assert.Equal(t,
map[string]interface{}{
"name": "Foo",
"_additional": map[string]interface{}{
"vector": []float32(nil),
"semanticPath": &SemanticPath{
Path: []*SemanticPathElement{
{
Concept: "pathelem1",
DistanceToQuery: 0,
DistanceToResult: 2.1,
DistanceToPrevious: nil,
DistanceToNext: ptFloat32(0.5),
},
{
Concept: "pathelem2",
DistanceToQuery: 2.1,
DistanceToResult: 0,
DistanceToPrevious: ptFloat32(0.5),
DistanceToNext: nil,
},
},
},
},
}, res[0])
assert.Equal(t,
map[string]interface{}{
"name": "Bar",
"_additional": map[string]interface{}{
"vector": []float32(nil),
"semanticPath": &SemanticPath{
Path: []*SemanticPathElement{
{
Concept: "pathelem1",
DistanceToQuery: 0,
DistanceToResult: 2.1,
DistanceToPrevious: nil,
DistanceToNext: ptFloat32(0.5),
},
{
Concept: "pathelem2",
DistanceToQuery: 2.1,
DistanceToResult: 0,
DistanceToPrevious: ptFloat32(0.5),
DistanceToNext: nil,
},
},
},
},
}, res[1])
})
})
}
func ptFloat32(in float32) *float32 {
return &in
}
type fakeModulesProvider struct {
customC11yModule *fakeText2vecContextionaryModule
}
func (p *fakeModulesProvider) VectorFromInput(ctx context.Context, className string, input string) ([]float32, error) {
panic("not implemented")
}
func (p *fakeModulesProvider) VectorFromSearchParam(ctx context.Context, className,
param string, params interface{},
findVectorFn modulecapabilities.FindVectorFn, tenant string,
) ([]float32, error) {
txt2vec := p.getFakeT2Vec()
vectorForParams := txt2vec.VectorSearches()["nearCustomText"]
return vectorForParams(ctx, params, "", findVectorFn, nil)
}
func (p *fakeModulesProvider) CrossClassVectorFromSearchParam(ctx context.Context,
param string, params interface{},
findVectorFn modulecapabilities.FindVectorFn,
) ([]float32, error) {
txt2vec := p.getFakeT2Vec()
vectorForParams := txt2vec.VectorSearches()["nearCustomText"]
return vectorForParams(ctx, params, "", findVectorFn, nil)
}
func (p *fakeModulesProvider) CrossClassValidateSearchParam(name string, value interface{}) error {
return p.ValidateSearchParam(name, value, "")
}
func (p *fakeModulesProvider) ValidateSearchParam(name string, value interface{}, className string) error {
txt2vec := p.getFakeT2Vec()
arg := txt2vec.Arguments()["nearCustomText"]
return arg.ValidateFunction(value)
}
func (p *fakeModulesProvider) GetExploreAdditionalExtend(ctx context.Context, in []search.Result,
moduleParams map[string]interface{}, searchVector []float32,
argumentModuleParams map[string]interface{},
) ([]search.Result, error) {
return p.additionalExtend(ctx, in, moduleParams, searchVector, "ExploreGet")
}
func (p *fakeModulesProvider) ListExploreAdditionalExtend(ctx context.Context, in []search.Result,
moduleParams map[string]interface{},
argumentModuleParams map[string]interface{},
) ([]search.Result, error) {
return p.additionalExtend(ctx, in, moduleParams, nil, "ExploreList")
}
func (p *fakeModulesProvider) additionalExtend(ctx context.Context,
in search.Results, moduleParams map[string]interface{},
searchVector []float32, capability string,
) (search.Results, error) {
txt2vec := p.getFakeT2Vec()
if additionalProperties := txt2vec.AdditionalProperties(); len(additionalProperties) > 0 {
for name, value := range moduleParams {
additionalPropertyFn := p.getAdditionalPropertyFn(additionalProperties[name], capability)
if additionalPropertyFn != nil && value != nil {
searchValue := value
if searchVectorValue, ok := value.(modulecapabilities.AdditionalPropertyWithSearchVector); ok {
searchVectorValue.SetSearchVector(searchVector)
searchValue = searchVectorValue
}
resArray, err := additionalPropertyFn(ctx, in, searchValue, nil, nil, nil)
if err != nil {
return nil, err
}
in = resArray
} else {
return nil, errors.Errorf("unknown capability: %s", name)
}
}
}
return in, nil
}
func (p *fakeModulesProvider) getAdditionalPropertyFn(additionalProperty modulecapabilities.AdditionalProperty,
capability string,
) modulecapabilities.AdditionalPropertyFn {
switch capability {
case "ObjectGet":
return additionalProperty.SearchFunctions.ObjectGet
case "ObjectList":
return additionalProperty.SearchFunctions.ObjectList
case "ExploreGet":
return additionalProperty.SearchFunctions.ExploreGet
case "ExploreList":
return additionalProperty.SearchFunctions.ExploreList
default:
return nil
}
}
func (p *fakeModulesProvider) getFakeT2Vec() *fakeText2vecContextionaryModule {
if p.customC11yModule != nil {
return p.customC11yModule
}
return &fakeText2vecContextionaryModule{}
}
func extractNearCustomTextParam(param map[string]interface{}) interface{} {
txt2vec := &fakeText2vecContextionaryModule{}
argument := txt2vec.Arguments()["nearCustomText"]
return argument.ExtractFunction(param)
}
func getDefaultParam(name string) interface{} {
switch name {
case "featureProjection":
return &fakeProjectorParams{}
case "semanticPath":
return &pathBuilderParams{}
case "nearestNeighbors":
return true
default:
return nil
}
}
func getFakeModulesProviderWithCustomExtenders(
customExtender *fakeExtender,
customProjector *fakeProjector,
customPathBuilder *fakePathBuilder,
) ModulesProvider {
return &fakeModulesProvider{
newFakeText2vecContextionaryModuleWithCustomExtender(customExtender, customProjector, customPathBuilder),
}
}
func getFakeModulesProvider() ModulesProvider {
return &fakeModulesProvider{}
}