mirror of
https://github.com/milvus-io/milvus.git
synced 2026-07-21 10:15:43 +00:00
test: add vanilla FAISS e2e coverage (#50126)
## Summary Add E2E coverage for vanilla FAISS index support across Milvus clients: - Add Python client index tests for vanilla FAISS factory strings, metrics, vector types, search params, scalar filters, release/load/search, range search, and expected unsupported behavior - Add Go client coverage through the generic index API - Add REST client create/list/describe metadata coverage Dependency note: this PR is temporarily based on Milvus #49912 and currently depends on the Knowhere `faiss-passthrough` branch. The dependency will be replaced after the final Knowhere reference is available. issue: #50124 ## Tests - python -m py_compile tests/python_client/testcases/indexes/idx_faiss.py tests/python_client/testcases/indexes/test_faiss.py tests/restful_client_v2/testcases/test_index_operation.py - pytest -c /dev/null tests/python_client/testcases/indexes/test_faiss.py -q -o cache_dir=/tmp/pytest-cache-vanilla-faiss --tb=short - pytest tests/restful_client_v2/testcases/test_index_operation.py -q -k faiss --tb=short -o cache_dir=/tmp/pytest-cache-vanilla-faiss-rest - cd tests/go_client && go test ./testcases -run TestCreateIndexVanillaFaissGeneric -count=1 -v --------- Signed-off-by: xianliang.li <xianliang.li@zilliz.com>
This commit is contained in:
@@ -972,6 +972,47 @@ func TestCreateIndexGeneric(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIndexVanillaFaissGeneric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := hp.CreateContext(t, time.Second*common.DefaultTimeout)
|
||||
mc := hp.CreateDefaultMilvusClient(ctx, t)
|
||||
|
||||
cp := hp.NewCreateCollectionParams(hp.Int64Vec)
|
||||
prepare, schema := hp.CollPrepare.CreateCollection(ctx, t, mc, cp, hp.TNewFieldsOption(), hp.TNewSchemaOption())
|
||||
|
||||
// insert
|
||||
ip := hp.NewInsertParams(schema)
|
||||
prepare.InsertData(ctx, t, mc, ip, hp.TNewDataOption())
|
||||
prepare.FlushData(ctx, t, mc, schema.CollectionName)
|
||||
|
||||
idx := index.NewGenericIndex(common.DefaultFloatVecFieldName, map[string]string{
|
||||
index.IndexTypeKey: "FAISS",
|
||||
index.MetricTypeKey: "L2",
|
||||
"faiss_index_name": "IVF64,Flat",
|
||||
})
|
||||
idxTask, err := mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idx))
|
||||
common.CheckErr(t, err, true)
|
||||
err = idxTask.Await(ctx)
|
||||
common.CheckErr(t, err, true)
|
||||
|
||||
descIdx, err := mc.DescribeIndex(ctx, client.NewDescribeIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName))
|
||||
common.CheckErr(t, err, true)
|
||||
common.CheckIndex(t, descIdx, index.NewGenericIndex(common.DefaultFloatVecFieldName, idx.Params()), common.TNewCheckIndexOpt(common.DefaultNb))
|
||||
require.Equal(t, "FAISS", descIdx.Index.Params()[index.IndexTypeKey])
|
||||
require.Equal(t, "IVF64,Flat", descIdx.Index.Params()["faiss_index_name"])
|
||||
|
||||
prepare.Load(ctx, t, mc, hp.NewLoadParams(schema.CollectionName))
|
||||
|
||||
queryVec := hp.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
|
||||
searchRes, err := mc.Search(ctx, client.NewSearchOption(schema.CollectionName, common.DefaultLimit, queryVec).
|
||||
WithANNSField(common.DefaultFloatVecFieldName).
|
||||
WithSearchParam("nprobe", "8").
|
||||
WithConsistencyLevel(entity.ClStrong))
|
||||
common.CheckErr(t, err, true)
|
||||
common.CheckSearchResult(t, searchRes, common.DefaultNq, common.DefaultLimit)
|
||||
}
|
||||
|
||||
// test create index with not exist index name and not exist field name
|
||||
func TestIndexNotExistName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
from pymilvus import DataType
|
||||
|
||||
success = "success"
|
||||
|
||||
|
||||
class FAISS:
|
||||
supported_vector_types = [
|
||||
DataType.FLOAT_VECTOR,
|
||||
DataType.BINARY_VECTOR,
|
||||
]
|
||||
|
||||
supported_metrics = ["L2", "IP", "COSINE"]
|
||||
|
||||
build_params = [
|
||||
{
|
||||
"description": "Flat float index",
|
||||
"params": {"faiss_index_name": "Flat"},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "IVF Flat float index",
|
||||
"params": {"faiss_index_name": "IVF64,Flat"},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "HNSW Flat float index",
|
||||
"params": {"faiss_index_name": "HNSW16,Flat"},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "OPQ IVF PQ float index",
|
||||
"params": {"faiss_index_name": "OPQ16,IVF64,PQ16x4"},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "IVF PQ RFlat float index",
|
||||
"params": {"faiss_index_name": "IVF64,PQ8x4,RFlat"},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "PQ float index",
|
||||
"params": {"faiss_index_name": "PQ8x4"},
|
||||
"searchable": False,
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "Binary flat index",
|
||||
"params": {"faiss_index_name": "BFlat"},
|
||||
"vector_data_type": DataType.BINARY_VECTOR,
|
||||
"metric_type": "HAMMING",
|
||||
"expected": success,
|
||||
},
|
||||
]
|
||||
|
||||
search_params = [
|
||||
{
|
||||
"description": "IVF Flat nprobe",
|
||||
"build_params": {"faiss_index_name": "IVF64,Flat"},
|
||||
"search_params": {"nprobe": 8},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "IVF Flat stringified nprobe",
|
||||
"build_params": {"faiss_index_name": "IVF64,Flat"},
|
||||
"search_params": {"nprobe": "8"},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "IVF Flat invalid nprobe string",
|
||||
"build_params": {"faiss_index_name": "IVF64,Flat"},
|
||||
"search_params": {"nprobe": "invalid"},
|
||||
"expected": {"err_code": 999, "err_msg": "expects a number"},
|
||||
},
|
||||
{
|
||||
"description": "HNSW Flat efSearch",
|
||||
"build_params": {"faiss_index_name": "HNSW16,Flat"},
|
||||
"search_params": {"efSearch": 64},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "HNSW Flat invalid efSearch string",
|
||||
"build_params": {"faiss_index_name": "HNSW16,Flat"},
|
||||
"search_params": {"efSearch": "invalid"},
|
||||
"expected": {"err_code": 999, "err_msg": "expects a number"},
|
||||
},
|
||||
{
|
||||
"description": "IVF PQ RFlat rerank",
|
||||
"build_params": {"faiss_index_name": "IVF64,PQ8x4,RFlat"},
|
||||
"search_params": {"nprobe": 8, "k_factor": 4},
|
||||
"expected": success,
|
||||
},
|
||||
{
|
||||
"description": "IVF PQ RFlat invalid k_factor string",
|
||||
"build_params": {"faiss_index_name": "IVF64,PQ8x4,RFlat"},
|
||||
"search_params": {"nprobe": 8, "k_factor": "invalid"},
|
||||
"expected": {"err_code": 999, "err_msg": "expects a number"},
|
||||
},
|
||||
]
|
||||
|
||||
metric_factories = [
|
||||
{"faiss_index_name": "IVF64,Flat"},
|
||||
{"faiss_index_name": "HNSW16,Flat"},
|
||||
]
|
||||
@@ -0,0 +1,402 @@
|
||||
import pytest
|
||||
from base.client_v2_base import TestMilvusClientV2Base
|
||||
from common import common_func as cf
|
||||
from common import common_type as ct
|
||||
from common.common_type import CaseLabel, CheckTasks
|
||||
from idx_faiss import FAISS
|
||||
from pymilvus import DataType
|
||||
|
||||
index_type = "FAISS"
|
||||
success = "success"
|
||||
pk_field_name = "id"
|
||||
vector_field_name = "vector"
|
||||
dim = ct.default_dim
|
||||
default_nb = ct.default_nb
|
||||
default_search_params = {"nprobe": 8}
|
||||
|
||||
|
||||
def _default_search_params_for_faiss_factory(faiss_index_name):
|
||||
if faiss_index_name.startswith("IVF"):
|
||||
return {"nprobe": 8}
|
||||
if faiss_index_name.startswith("HNSW"):
|
||||
return {"efSearch": 64}
|
||||
return {}
|
||||
|
||||
|
||||
class TestFaissBase(TestMilvusClientV2Base):
|
||||
def _create_collection(self, client, collection_name, vector_data_type=DataType.FLOAT_VECTOR):
|
||||
schema, _ = self.create_schema(client)
|
||||
schema.add_field(pk_field_name, datatype=DataType.INT64, is_primary=True, auto_id=False)
|
||||
if vector_data_type == DataType.SPARSE_FLOAT_VECTOR:
|
||||
schema.add_field(vector_field_name, datatype=vector_data_type)
|
||||
else:
|
||||
schema.add_field(vector_field_name, datatype=vector_data_type, dim=dim)
|
||||
self.create_collection(client, collection_name, schema=schema)
|
||||
return schema
|
||||
|
||||
def _insert_rows(self, client, collection_name, vector_data_type=DataType.FLOAT_VECTOR):
|
||||
vectors = cf.gen_vectors(default_nb, dim=dim, vector_data_type=vector_data_type)
|
||||
rows = [{pk_field_name: i, vector_field_name: vectors[i]} for i in range(default_nb)]
|
||||
self.insert(client, collection_name, rows)
|
||||
self.flush(client, collection_name)
|
||||
|
||||
def _create_faiss_index(
|
||||
self, client, collection_name, metric_type="L2", params=None, check_task=None, check_items=None
|
||||
):
|
||||
index_params = self.prepare_index_params(client)[0]
|
||||
index_params.add_index(
|
||||
field_name=vector_field_name, metric_type=metric_type, index_type=index_type, params=params
|
||||
)
|
||||
return self.create_index(client, collection_name, index_params, check_task=check_task, check_items=check_items)
|
||||
|
||||
def _search_and_check(self, client, collection_name, vector_data_type=DataType.FLOAT_VECTOR, search_params=None):
|
||||
nq = ct.default_nq
|
||||
search_vectors = cf.gen_vectors(nq, dim=dim, vector_data_type=vector_data_type)
|
||||
self.search(
|
||||
client,
|
||||
collection_name,
|
||||
search_vectors,
|
||||
search_params=search_params,
|
||||
limit=ct.default_limit,
|
||||
check_task=CheckTasks.check_search_results,
|
||||
check_items={
|
||||
"enable_milvus_client_api": True,
|
||||
"nq": nq,
|
||||
"limit": ct.default_limit,
|
||||
"pk_name": pk_field_name,
|
||||
},
|
||||
)
|
||||
|
||||
def _assert_index_params(self, client, collection_name, params, metric_type):
|
||||
idx_info = client.describe_index(collection_name, vector_field_name)
|
||||
assert idx_info["index_type"] == index_type
|
||||
assert idx_info["metric_type"] == metric_type
|
||||
for key, value in params.items():
|
||||
assert key in idx_info.keys()
|
||||
assert str(value) in [str(v) for v in idx_info.values()]
|
||||
|
||||
|
||||
class TestFaissBuildParams(TestFaissBase):
|
||||
@pytest.mark.tags(CaseLabel.L1)
|
||||
@pytest.mark.parametrize("params", FAISS.build_params)
|
||||
def test_faiss_build_params(self, params):
|
||||
"""
|
||||
Test vanilla Faiss factory build parameters.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
vector_data_type = params.get("vector_data_type", DataType.FLOAT_VECTOR)
|
||||
metric_type = params.get("metric_type", "L2")
|
||||
build_params = params.get("params", None)
|
||||
|
||||
self._create_collection(client, collection_name, vector_data_type)
|
||||
self._insert_rows(client, collection_name, vector_data_type)
|
||||
|
||||
if params.get("expected", None) != success:
|
||||
self._create_faiss_index(
|
||||
client,
|
||||
collection_name,
|
||||
metric_type=metric_type,
|
||||
params=build_params,
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items=params.get("expected"),
|
||||
)
|
||||
else:
|
||||
self._create_faiss_index(client, collection_name, metric_type=metric_type, params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
if vector_data_type == DataType.FLOAT_VECTOR and params.get("searchable", True):
|
||||
search_params = _default_search_params_for_faiss_factory(build_params["faiss_index_name"])
|
||||
self._search_and_check(client, collection_name, vector_data_type, search_params=search_params)
|
||||
elif vector_data_type != DataType.FLOAT_VECTOR:
|
||||
search_params = {}
|
||||
self._search_and_check(client, collection_name, vector_data_type, search_params=search_params)
|
||||
self._assert_index_params(client, collection_name, build_params, metric_type)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
@pytest.mark.parametrize("metric", FAISS.supported_metrics)
|
||||
@pytest.mark.parametrize("build_params", [{"faiss_index_name": "Flat"}] + FAISS.metric_factories)
|
||||
def test_faiss_on_all_float_metrics(self, metric, build_params):
|
||||
"""
|
||||
Test vanilla Faiss float index factories on all supported float metrics.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type=metric, params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
search_params = _default_search_params_for_faiss_factory(build_params["faiss_index_name"])
|
||||
self._search_and_check(client, collection_name, DataType.FLOAT_VECTOR, search_params=search_params)
|
||||
self._assert_index_params(client, collection_name, build_params, metric)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
@pytest.mark.parametrize("vector_data_type", ct.all_vector_types)
|
||||
def test_faiss_on_all_vector_types(self, vector_data_type):
|
||||
"""
|
||||
Test vanilla Faiss vector type support.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, vector_data_type)
|
||||
self._insert_rows(client, collection_name, vector_data_type)
|
||||
|
||||
if vector_data_type == DataType.BINARY_VECTOR:
|
||||
metric_type = "HAMMING"
|
||||
build_params = {"faiss_index_name": "BFlat"}
|
||||
else:
|
||||
metric_type = cf.get_default_metric_for_vector_type(vector_data_type)
|
||||
build_params = {"faiss_index_name": "Flat"}
|
||||
|
||||
if vector_data_type not in FAISS.supported_vector_types:
|
||||
self._create_faiss_index(
|
||||
client,
|
||||
collection_name,
|
||||
metric_type=metric_type,
|
||||
params=build_params,
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items={"err_code": 999, "err_msg": "invalid parameter"},
|
||||
)
|
||||
else:
|
||||
self._create_faiss_index(client, collection_name, metric_type=metric_type, params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
self._search_and_check(client, collection_name, vector_data_type, search_params={})
|
||||
self._assert_index_params(client, collection_name, build_params, metric_type)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
@pytest.mark.parametrize(
|
||||
"params", [p for p in FAISS.build_params if p.get("expected") == success and p.get("searchable", True)]
|
||||
)
|
||||
def test_faiss_build_release_load_search(self, params):
|
||||
"""
|
||||
Test vanilla Faiss index survives the full Milvus build -> release -> load -> search flow.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
vector_data_type = params.get("vector_data_type", DataType.FLOAT_VECTOR)
|
||||
metric_type = params.get("metric_type", "L2")
|
||||
build_params = params["params"]
|
||||
|
||||
self._create_collection(client, collection_name, vector_data_type)
|
||||
self._insert_rows(client, collection_name, vector_data_type)
|
||||
self._create_faiss_index(client, collection_name, metric_type=metric_type, params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
|
||||
self.release_collection(client, collection_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
search_params = {}
|
||||
if vector_data_type == DataType.FLOAT_VECTOR:
|
||||
search_params = _default_search_params_for_faiss_factory(build_params["faiss_index_name"])
|
||||
self._search_and_check(client, collection_name, vector_data_type, search_params=search_params)
|
||||
self._assert_index_params(client, collection_name, build_params, metric_type)
|
||||
|
||||
|
||||
class TestFaissSearchParams(TestFaissBase):
|
||||
@pytest.mark.tags(CaseLabel.L1)
|
||||
@pytest.mark.parametrize("params", FAISS.search_params)
|
||||
def test_faiss_search_params(self, params):
|
||||
"""
|
||||
Test vanilla Faiss search parameter forwarding.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
build_params = params["build_params"]
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="L2", params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
if params.get("expected", None) != success:
|
||||
nq = ct.default_nq
|
||||
search_vectors = cf.gen_vectors(nq, dim=dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
self.search(
|
||||
client,
|
||||
collection_name,
|
||||
search_vectors,
|
||||
search_params=params["search_params"],
|
||||
limit=ct.default_limit,
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items=params.get("expected"),
|
||||
)
|
||||
else:
|
||||
self._search_and_check(
|
||||
client, collection_name, DataType.FLOAT_VECTOR, search_params=params["search_params"]
|
||||
)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
def test_faiss_incompatible_search_params(self):
|
||||
"""
|
||||
Test vanilla Faiss rejects search parameters incompatible with the factory index.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
build_params = {"faiss_index_name": "Flat"}
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="L2", params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
nq = ct.default_nq
|
||||
search_vectors = cf.gen_vectors(nq, dim=dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
self.search(
|
||||
client,
|
||||
collection_name,
|
||||
search_vectors,
|
||||
search_params={"efSearch": 64},
|
||||
limit=ct.default_limit,
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items={"err_code": 999, "err_msg": "not supported"},
|
||||
)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
@pytest.mark.parametrize(
|
||||
"build_params",
|
||||
[
|
||||
{"faiss_index_name": "Flat"},
|
||||
{"faiss_index_name": "IVF64,Flat"},
|
||||
{"faiss_index_name": "HNSW16,Flat"},
|
||||
],
|
||||
)
|
||||
def test_faiss_search_with_scalar_filter(self, build_params):
|
||||
"""
|
||||
Test vanilla Faiss search honors Milvus scalar filter bitset.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="L2", params=build_params)
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
search_params = _default_search_params_for_faiss_factory(build_params["faiss_index_name"])
|
||||
search_vectors = cf.gen_vectors(1, dim=dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
results = client.search(
|
||||
collection_name,
|
||||
search_vectors,
|
||||
filter=f"{pk_field_name} >= 100",
|
||||
search_params=search_params,
|
||||
limit=ct.default_limit,
|
||||
)
|
||||
assert len(results) == 1
|
||||
assert len(results[0]) == ct.default_limit
|
||||
assert all(hit["id"] >= 100 for hit in results[0])
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
def test_faiss_flat_range_search(self):
|
||||
"""
|
||||
Test vanilla Faiss float Flat range search. The current adapter implements
|
||||
RangeSearch for float FAISS indexes.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="L2", params={"faiss_index_name": "Flat"})
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
search_vectors = cf.gen_vectors(1, dim=dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
range_params = {"radius": 100000.0, "range_filter": 0.0}
|
||||
self.search(
|
||||
client,
|
||||
collection_name,
|
||||
search_vectors,
|
||||
search_params=range_params,
|
||||
limit=ct.default_limit,
|
||||
check_task=CheckTasks.check_search_results,
|
||||
check_items={
|
||||
"enable_milvus_client_api": True,
|
||||
"nq": 1,
|
||||
"limit": ct.default_limit,
|
||||
"pk_name": pk_field_name,
|
||||
},
|
||||
)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
def test_faiss_binary_range_search_not_supported(self):
|
||||
"""
|
||||
Test vanilla Faiss binary range search is explicitly not implemented.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, DataType.BINARY_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.BINARY_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="HAMMING", params={"faiss_index_name": "BFlat"})
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
search_vectors = cf.gen_vectors(1, dim=dim, vector_data_type=DataType.BINARY_VECTOR)
|
||||
self.search(
|
||||
client,
|
||||
collection_name,
|
||||
search_vectors,
|
||||
search_params={"radius": 1000, "range_filter": 0},
|
||||
limit=ct.default_limit,
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items={"err_code": 999, "err_msg": "RangeSearch unsupported for binary faiss indexes"},
|
||||
)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
def test_faiss_pq_search_selector_not_supported(self):
|
||||
"""
|
||||
Test vanilla Faiss IndexPQ rejects Milvus search because Milvus passes
|
||||
an ID selector/bitset and native FAISS IndexPQ does not support it.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="L2", params={"faiss_index_name": "PQ8x4"})
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
search_vectors = cf.gen_vectors(1, dim=dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
self.search(
|
||||
client,
|
||||
collection_name,
|
||||
search_vectors,
|
||||
filter=f"{pk_field_name} >= 100",
|
||||
search_params={},
|
||||
limit=ct.default_limit,
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items={"err_code": 999, "err_msg": "selector not supported"},
|
||||
)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L2)
|
||||
def test_faiss_search_iterator_not_supported(self):
|
||||
"""
|
||||
Test vanilla Faiss search iterator is rejected because the adapter does
|
||||
not expose raw-vector retrieval.
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
|
||||
self._create_collection(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._insert_rows(client, collection_name, DataType.FLOAT_VECTOR)
|
||||
self._create_faiss_index(client, collection_name, metric_type="L2", params={"faiss_index_name": "Flat"})
|
||||
self.wait_for_index_ready(client, collection_name, index_name=vector_field_name)
|
||||
self.load_collection(client, collection_name)
|
||||
|
||||
search_vectors = cf.gen_vectors(1, dim=dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
self.search_iterator(
|
||||
client,
|
||||
collection_name,
|
||||
data=search_vectors,
|
||||
batch_size=100,
|
||||
search_params={},
|
||||
check_task=CheckTasks.err_res,
|
||||
check_items={"err_code": 65535, "err_msg": "Failed to create iterators from index"},
|
||||
)
|
||||
@@ -1,19 +1,21 @@
|
||||
import random
|
||||
from sklearn import preprocessing
|
||||
import numpy as np
|
||||
import time
|
||||
import concurrent.futures
|
||||
from typing import Dict, List
|
||||
from utils.utils import gen_collection_name, patch_faker_text, en_vocabularies_distribution, \
|
||||
zh_vocabularies_distribution
|
||||
from utils.util_log import test_log as logger
|
||||
import random
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from base.testbase import TestBase
|
||||
from utils.utils import gen_vector
|
||||
from pymilvus import (
|
||||
Collection
|
||||
)
|
||||
from faker import Faker
|
||||
from pymilvus import Collection
|
||||
from sklearn import preprocessing
|
||||
from utils.util_log import test_log as logger
|
||||
from utils.utils import (
|
||||
en_vocabularies_distribution,
|
||||
gen_collection_name,
|
||||
gen_vector,
|
||||
patch_faker_text,
|
||||
zh_vocabularies_distribution,
|
||||
)
|
||||
|
||||
Faker.seed(19530)
|
||||
fake_en = Faker("en_US")
|
||||
@@ -27,13 +29,12 @@ index_param_map = {
|
||||
"IVF_SQ8": {"nlist": 128},
|
||||
"HNSW": {"M": 16, "efConstruction": 200},
|
||||
"BM25_SPARSE_INVERTED_INDEX": {"bm25_k1": 0.5, "bm25_b": 0.5},
|
||||
"AUTOINDEX": {}
|
||||
"AUTOINDEX": {},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.L0
|
||||
class TestCreateIndex(TestBase):
|
||||
|
||||
@pytest.mark.parametrize("metric_type", ["L2", "COSINE", "IP"])
|
||||
@pytest.mark.parametrize("index_type", ["AUTOINDEX", "IVF_SQ8", "HNSW"])
|
||||
@pytest.mark.parametrize("dim", [128])
|
||||
@@ -52,9 +53,9 @@ class TestCreateIndex(TestBase):
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}},
|
||||
{"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}},
|
||||
{"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}}
|
||||
{"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
logger.info(f"create collection {name} with payload: {payload}")
|
||||
rsp = client.collection_create(payload)
|
||||
@@ -66,19 +67,21 @@ class TestCreateIndex(TestBase):
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexParams": [
|
||||
{"fieldName": "book_intro", "indexName": "book_intro_vector",
|
||||
"metricType": f"{metric_type}",
|
||||
"indexType": f"{index_type}",
|
||||
"params": index_param_map[index_type]
|
||||
}
|
||||
]
|
||||
{
|
||||
"fieldName": "book_intro",
|
||||
"indexName": "book_intro_vector",
|
||||
"metricType": f"{metric_type}",
|
||||
"indexType": f"{index_type}",
|
||||
"params": index_param_map[index_type],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
# Create multiple index creation tasks
|
||||
num_threads = 10 # Number of concurrent tasks
|
||||
payloads = [payload.copy() for _ in range(num_threads)]
|
||||
|
||||
def create_index(idx_payload: Dict) -> Dict:
|
||||
def create_index(idx_payload: dict) -> dict:
|
||||
return self.index_client.index_create(idx_payload)
|
||||
|
||||
# Execute index creation concurrently
|
||||
@@ -87,9 +90,9 @@ class TestCreateIndex(TestBase):
|
||||
for future in concurrent.futures.as_completed(future_to_payload):
|
||||
try:
|
||||
rsp = future.result()
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
except Exception as e:
|
||||
logger.info(f'Index creation failed with error: {str(e)}')
|
||||
logger.info(f"Index creation failed with error: {str(e)}")
|
||||
raise
|
||||
|
||||
time.sleep(10) # Wait for all indexes to be ready
|
||||
@@ -97,15 +100,15 @@ class TestCreateIndex(TestBase):
|
||||
rsp = self.index_client.index_list(collection_name=name)
|
||||
# describe index
|
||||
rsp = self.index_client.index_describe(collection_name=name, index_name="book_intro_vector")
|
||||
assert rsp['code'] == 0
|
||||
assert len(rsp['data']) == len(payload['indexParams'])
|
||||
expected_index = sorted(payload['indexParams'], key=lambda x: x['fieldName'])
|
||||
actual_index = sorted(rsp['data'], key=lambda x: x['fieldName'])
|
||||
assert rsp["code"] == 0
|
||||
assert len(rsp["data"]) == len(payload["indexParams"])
|
||||
expected_index = sorted(payload["indexParams"], key=lambda x: x["fieldName"])
|
||||
actual_index = sorted(rsp["data"], key=lambda x: x["fieldName"])
|
||||
for i in range(len(expected_index)):
|
||||
assert expected_index[i]['fieldName'] == actual_index[i]['fieldName']
|
||||
assert expected_index[i]['indexName'] == actual_index[i]['indexName']
|
||||
assert expected_index[i]['metricType'] == actual_index[i]['metricType']
|
||||
assert expected_index[i]["indexType"] == actual_index[i]['indexType']
|
||||
assert expected_index[i]["fieldName"] == actual_index[i]["fieldName"]
|
||||
assert expected_index[i]["indexName"] == actual_index[i]["indexName"]
|
||||
assert expected_index[i]["metricType"] == actual_index[i]["metricType"]
|
||||
assert expected_index[i]["indexType"] == actual_index[i]["indexType"]
|
||||
# check index by pymilvus
|
||||
index_info = [index.to_dict() for index in c.indexes]
|
||||
logger.info(f"index_info: {index_info}")
|
||||
@@ -120,15 +123,78 @@ class TestCreateIndex(TestBase):
|
||||
assert index_param.get("params", {}) == index_param_map[index_type]
|
||||
# drop index
|
||||
for i in range(len(actual_index)):
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexName": actual_index[i]['indexName']
|
||||
}
|
||||
payload = {"collectionName": name, "indexName": actual_index[i]["indexName"]}
|
||||
rsp = self.index_client.index_drop(payload)
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
# list index, expect empty
|
||||
rsp = self.index_client.index_list(collection_name=name)
|
||||
assert rsp['data'] == []
|
||||
assert rsp["data"] == []
|
||||
|
||||
@pytest.mark.parametrize("dim", [128])
|
||||
def test_create_vanilla_faiss_index(self, dim):
|
||||
"""
|
||||
target: test create vanilla Faiss index
|
||||
method: create a float vector collection and create FAISS index with faiss_index_name
|
||||
expected: create index success and metadata persisted
|
||||
"""
|
||||
name = gen_collection_name()
|
||||
client = self.collection_client
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"schema": {
|
||||
"fields": [
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}},
|
||||
{"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}},
|
||||
{"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}},
|
||||
]
|
||||
},
|
||||
}
|
||||
logger.info(f"create collection {name} with payload: {payload}")
|
||||
rsp = client.collection_create(payload)
|
||||
assert rsp["code"] == 0
|
||||
c = Collection(name)
|
||||
c.flush()
|
||||
|
||||
index_name = "book_intro_faiss"
|
||||
index_params = {"faiss_index_name": "IVF64,Flat"}
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexParams": [
|
||||
{
|
||||
"fieldName": "book_intro",
|
||||
"indexName": index_name,
|
||||
"metricType": "L2",
|
||||
"indexType": "FAISS",
|
||||
"params": index_params,
|
||||
}
|
||||
],
|
||||
}
|
||||
rsp = self.index_client.index_create(payload)
|
||||
assert rsp["code"] == 0
|
||||
|
||||
time.sleep(10)
|
||||
rsp = self.index_client.index_list(collection_name=name)
|
||||
assert rsp["code"] == 0
|
||||
assert index_name in rsp["data"]
|
||||
|
||||
rsp = self.index_client.index_describe(collection_name=name, index_name=index_name)
|
||||
assert rsp["code"] == 0
|
||||
assert len(rsp["data"]) == 1
|
||||
actual_index = rsp["data"][0]
|
||||
assert actual_index["fieldName"] == "book_intro"
|
||||
assert actual_index["indexName"] == index_name
|
||||
assert actual_index["metricType"] == "L2"
|
||||
assert actual_index["indexType"] == "FAISS"
|
||||
|
||||
index_info = [index.to_dict() for index in c.indexes]
|
||||
logger.info(f"index_info: {index_info}")
|
||||
assert len(index_info) == 1
|
||||
index_param = index_info[0]["index_param"]
|
||||
assert index_param["metric_type"] == "L2"
|
||||
assert index_param["index_type"] == "FAISS"
|
||||
persisted_params = index_param.get("params", {})
|
||||
assert persisted_params["faiss_index_name"] == index_params["faiss_index_name"]
|
||||
|
||||
@pytest.mark.parametrize("index_type", ["INVERTED"])
|
||||
@pytest.mark.parametrize("dim", [128])
|
||||
@@ -147,9 +213,9 @@ class TestCreateIndex(TestBase):
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}},
|
||||
{"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}},
|
||||
{"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}}
|
||||
{"fieldName": "book_intro", "dataType": "FloatVector", "elementTypeParams": {"dim": f"{dim}"}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
logger.info(f"create collection {name} with payload: {payload}")
|
||||
rsp = client.collection_create(payload)
|
||||
@@ -162,13 +228,11 @@ class TestCreateIndex(TestBase):
|
||||
"word_count": j,
|
||||
"book_describe": f"book_{j}",
|
||||
"book_intro": preprocessing.normalize([np.array([random.random() for _ in range(dim)])])[
|
||||
0].tolist(),
|
||||
0
|
||||
].tolist(),
|
||||
}
|
||||
data.append(tmp)
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"data": data
|
||||
}
|
||||
payload = {"collectionName": name, "data": data}
|
||||
rsp = self.vector_client.vector_insert(payload)
|
||||
c = Collection(name)
|
||||
c.flush()
|
||||
@@ -178,24 +242,30 @@ class TestCreateIndex(TestBase):
|
||||
# create index
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexParams": [{"fieldName": "word_count", "indexName": "word_count_vector", "indexType": "INVERTED",
|
||||
"params": {"index_type": "INVERTED"}}]
|
||||
"indexParams": [
|
||||
{
|
||||
"fieldName": "word_count",
|
||||
"indexName": "word_count_vector",
|
||||
"indexType": "INVERTED",
|
||||
"params": {"index_type": "INVERTED"},
|
||||
}
|
||||
],
|
||||
}
|
||||
rsp = self.index_client.index_create(payload)
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
time.sleep(10)
|
||||
# list index, expect not empty
|
||||
rsp = self.index_client.index_list(collection_name=name)
|
||||
# describe index
|
||||
rsp = self.index_client.index_describe(collection_name=name, index_name="word_count_vector")
|
||||
assert rsp['code'] == 0
|
||||
assert len(rsp['data']) == len(payload['indexParams'])
|
||||
expected_index = sorted(payload['indexParams'], key=lambda x: x['fieldName'])
|
||||
actual_index = sorted(rsp['data'], key=lambda x: x['fieldName'])
|
||||
assert rsp["code"] == 0
|
||||
assert len(rsp["data"]) == len(payload["indexParams"])
|
||||
expected_index = sorted(payload["indexParams"], key=lambda x: x["fieldName"])
|
||||
actual_index = sorted(rsp["data"], key=lambda x: x["fieldName"])
|
||||
for i in range(len(expected_index)):
|
||||
assert expected_index[i]['fieldName'] == actual_index[i]['fieldName']
|
||||
assert expected_index[i]['indexName'] == actual_index[i]['indexName']
|
||||
assert expected_index[i]['indexType'] == actual_index[i]['indexType']
|
||||
assert expected_index[i]["fieldName"] == actual_index[i]["fieldName"]
|
||||
assert expected_index[i]["indexName"] == actual_index[i]["indexName"]
|
||||
assert expected_index[i]["indexType"] == actual_index[i]["indexType"]
|
||||
|
||||
@pytest.mark.parametrize("index_type", ["BIN_FLAT", "BIN_IVF_FLAT"])
|
||||
@pytest.mark.parametrize("metric_type", ["JACCARD", "HAMMING"])
|
||||
@@ -215,9 +285,9 @@ class TestCreateIndex(TestBase):
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}},
|
||||
{"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}},
|
||||
{"fieldName": "binary_vector", "dataType": "BinaryVector", "elementTypeParams": {"dim": f"{dim}"}}
|
||||
{"fieldName": "binary_vector", "dataType": "BinaryVector", "elementTypeParams": {"dim": f"{dim}"}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
logger.info(f"create collection {name} with payload: {payload}")
|
||||
rsp = client.collection_create(payload)
|
||||
@@ -229,13 +299,10 @@ class TestCreateIndex(TestBase):
|
||||
"book_id": j,
|
||||
"word_count": j,
|
||||
"book_describe": f"book_{j}",
|
||||
"binary_vector": gen_vector(datatype="BinaryVector", dim=dim)
|
||||
"binary_vector": gen_vector(datatype="BinaryVector", dim=dim),
|
||||
}
|
||||
data.append(tmp)
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"data": data
|
||||
}
|
||||
payload = {"collectionName": name, "data": data}
|
||||
rsp = self.vector_client.vector_insert(payload)
|
||||
c = Collection(name)
|
||||
c.flush()
|
||||
@@ -246,26 +313,33 @@ class TestCreateIndex(TestBase):
|
||||
index_name = "binary_vector_index"
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexParams": [{"fieldName": "binary_vector", "indexName": index_name, "metricType": metric_type, "indexType": index_type,
|
||||
"params": {"index_type": index_type}}]
|
||||
"indexParams": [
|
||||
{
|
||||
"fieldName": "binary_vector",
|
||||
"indexName": index_name,
|
||||
"metricType": metric_type,
|
||||
"indexType": index_type,
|
||||
"params": {"index_type": index_type},
|
||||
}
|
||||
],
|
||||
}
|
||||
if index_type == "BIN_IVF_FLAT":
|
||||
payload["indexParams"][0]["params"]["nlist"] = "16384"
|
||||
rsp = self.index_client.index_create(payload)
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
time.sleep(10)
|
||||
# list index, expect not empty
|
||||
rsp = self.index_client.index_list(collection_name=name)
|
||||
# describe index
|
||||
rsp = self.index_client.index_describe(collection_name=name, index_name=index_name)
|
||||
assert rsp['code'] == 0
|
||||
assert len(rsp['data']) == len(payload['indexParams'])
|
||||
expected_index = sorted(payload['indexParams'], key=lambda x: x['fieldName'])
|
||||
actual_index = sorted(rsp['data'], key=lambda x: x['fieldName'])
|
||||
assert rsp["code"] == 0
|
||||
assert len(rsp["data"]) == len(payload["indexParams"])
|
||||
expected_index = sorted(payload["indexParams"], key=lambda x: x["fieldName"])
|
||||
actual_index = sorted(rsp["data"], key=lambda x: x["fieldName"])
|
||||
for i in range(len(expected_index)):
|
||||
assert expected_index[i]['fieldName'] == actual_index[i]['fieldName']
|
||||
assert expected_index[i]['indexName'] == actual_index[i]['indexName']
|
||||
assert expected_index[i]['indexType'] == actual_index[i]['indexType']
|
||||
assert expected_index[i]["fieldName"] == actual_index[i]["fieldName"]
|
||||
assert expected_index[i]["indexName"] == actual_index[i]["indexName"]
|
||||
assert expected_index[i]["indexType"] == actual_index[i]["indexType"]
|
||||
|
||||
@pytest.mark.parametrize("insert_round", [1])
|
||||
@pytest.mark.parametrize("auto_id", [True])
|
||||
@@ -273,12 +347,23 @@ class TestCreateIndex(TestBase):
|
||||
@pytest.mark.parametrize("enable_dynamic_schema", [True])
|
||||
@pytest.mark.parametrize("nb", [3000])
|
||||
@pytest.mark.parametrize("dim", [128])
|
||||
@pytest.mark.parametrize("tokenizer", ['standard', 'jieba'])
|
||||
@pytest.mark.parametrize("index_type", ['SPARSE_INVERTED_INDEX', 'SPARSE_WAND'])
|
||||
@pytest.mark.parametrize("tokenizer", ["standard", "jieba"])
|
||||
@pytest.mark.parametrize("index_type", ["SPARSE_INVERTED_INDEX", "SPARSE_WAND"])
|
||||
@pytest.mark.parametrize("bm25_k1", [1.2, 1.5])
|
||||
@pytest.mark.parametrize("bm25_b", [0.7, 0.5])
|
||||
def test_create_index_for_full_text_search(self, nb, dim, insert_round, auto_id, is_partition_key,
|
||||
enable_dynamic_schema, tokenizer, index_type, bm25_k1, bm25_b):
|
||||
def test_create_index_for_full_text_search(
|
||||
self,
|
||||
nb,
|
||||
dim,
|
||||
insert_round,
|
||||
auto_id,
|
||||
is_partition_key,
|
||||
enable_dynamic_schema,
|
||||
tokenizer,
|
||||
index_type,
|
||||
bm25_k1,
|
||||
bm25_b,
|
||||
):
|
||||
"""
|
||||
Insert a vector with a simple payload
|
||||
"""
|
||||
@@ -291,16 +376,26 @@ class TestCreateIndex(TestBase):
|
||||
"enableDynamicField": enable_dynamic_schema,
|
||||
"fields": [
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "user_id", "dataType": "Int64", "isPartitionKey": is_partition_key,
|
||||
"elementTypeParams": {}},
|
||||
{
|
||||
"fieldName": "user_id",
|
||||
"dataType": "Int64",
|
||||
"isPartitionKey": is_partition_key,
|
||||
"elementTypeParams": {},
|
||||
},
|
||||
{"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}},
|
||||
{"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}},
|
||||
{"fieldName": "document_content", "dataType": "VarChar",
|
||||
"elementTypeParams": {"max_length": "1000", "enable_analyzer": True,
|
||||
"analyzer_params": {
|
||||
"tokenizer": tokenizer,
|
||||
},
|
||||
"enable_match": True}},
|
||||
{
|
||||
"fieldName": "document_content",
|
||||
"dataType": "VarChar",
|
||||
"elementTypeParams": {
|
||||
"max_length": "1000",
|
||||
"enable_analyzer": True,
|
||||
"analyzer_params": {
|
||||
"tokenizer": tokenizer,
|
||||
},
|
||||
"enable_match": True,
|
||||
},
|
||||
},
|
||||
{"fieldName": "sparse_vector", "dataType": "SparseFloatVector"},
|
||||
],
|
||||
"functions": [
|
||||
@@ -309,19 +404,19 @@ class TestCreateIndex(TestBase):
|
||||
"type": "BM25",
|
||||
"inputFieldNames": ["document_content"],
|
||||
"outputFieldNames": ["sparse_vector"],
|
||||
"params": {}
|
||||
"params": {},
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
}
|
||||
rsp = self.collection_client.collection_create(payload)
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
rsp = self.collection_client.collection_describe(name)
|
||||
logger.info(f"rsp: {rsp}")
|
||||
assert rsp['code'] == 0
|
||||
if tokenizer == 'standard':
|
||||
assert rsp["code"] == 0
|
||||
if tokenizer == "standard":
|
||||
fake = fake_en
|
||||
elif tokenizer == 'jieba':
|
||||
elif tokenizer == "jieba":
|
||||
fake = fake_zh
|
||||
else:
|
||||
raise Exception("Invalid tokenizer")
|
||||
@@ -354,30 +449,32 @@ class TestCreateIndex(TestBase):
|
||||
"data": data,
|
||||
}
|
||||
rsp = self.vector_client.vector_insert(payload)
|
||||
assert rsp['code'] == 0
|
||||
assert rsp['data']['insertCount'] == nb
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
assert rsp["data"]["insertCount"] == nb
|
||||
assert rsp["code"] == 0
|
||||
|
||||
# create index
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexParams": [
|
||||
{"fieldName": "sparse_vector", "indexName": "sparse_vector",
|
||||
"metricType": "BM25",
|
||||
"indexType": index_type,
|
||||
"params": {"bm25_k1": bm25_k1, "bm25_b": bm25_b}
|
||||
}
|
||||
]
|
||||
{
|
||||
"fieldName": "sparse_vector",
|
||||
"indexName": "sparse_vector",
|
||||
"metricType": "BM25",
|
||||
"indexType": index_type,
|
||||
"params": {"bm25_k1": bm25_k1, "bm25_b": bm25_b},
|
||||
}
|
||||
],
|
||||
}
|
||||
rsp = self.index_client.index_create(payload)
|
||||
c = Collection(name)
|
||||
index_info = [index.to_dict() for index in c.indexes]
|
||||
logger.info(f"index_info: {index_info}")
|
||||
for info in index_info:
|
||||
assert info['index_param']['metric_type'] == 'BM25'
|
||||
assert info['index_param']["params"]['bm25_k1'] == bm25_k1
|
||||
assert info['index_param']["params"]['bm25_b'] == bm25_b
|
||||
assert info['index_param']['index_type'] == index_type
|
||||
assert info["index_param"]["metric_type"] == "BM25"
|
||||
assert info["index_param"]["params"]["bm25_k1"] == bm25_k1
|
||||
assert info["index_param"]["params"]["bm25_b"] == bm25_b
|
||||
assert info["index_param"]["index_type"] == index_type
|
||||
|
||||
|
||||
@pytest.mark.L0
|
||||
@@ -398,9 +495,9 @@ class TestIndexProperties(TestBase):
|
||||
"schema": {
|
||||
"fields": [
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "my_vector", "dataType": "FloatVector", "elementTypeParams": {"dim": 128}}
|
||||
{"fieldName": "my_vector", "dataType": "FloatVector", "elementTypeParams": {"dim": 128}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
collection_client.collection_create(payload)
|
||||
|
||||
@@ -414,15 +511,14 @@ class TestIndexProperties(TestBase):
|
||||
"indexName": "my_vector",
|
||||
"indexType": "IVF_SQ8",
|
||||
"metricType": "L2",
|
||||
"params": {"nlist": 128}
|
||||
"params": {"nlist": 128},
|
||||
}
|
||||
|
||||
],
|
||||
}
|
||||
index_client.index_create(index_payload)
|
||||
# list index
|
||||
rsp = index_client.index_list(name)
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
|
||||
# Alter index properties
|
||||
properties = {"mmap.enabled": True}
|
||||
@@ -431,7 +527,7 @@ class TestIndexProperties(TestBase):
|
||||
|
||||
# describe index
|
||||
rsp = index_client.index_describe(name, "my_vector")
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
|
||||
# Drop index properties
|
||||
delete_keys = ["mmap.enabled"]
|
||||
@@ -440,12 +536,9 @@ class TestIndexProperties(TestBase):
|
||||
|
||||
# describe index
|
||||
rsp = index_client.index_describe(name, "my_vector")
|
||||
assert rsp['code'] == 0
|
||||
assert rsp["code"] == 0
|
||||
|
||||
@pytest.mark.parametrize("invalid_property", [
|
||||
{"invalid_key": True},
|
||||
{"mmap.enabled": "invalid_value"}
|
||||
])
|
||||
@pytest.mark.parametrize("invalid_property", [{"invalid_key": True}, {"mmap.enabled": "invalid_value"}])
|
||||
def test_alter_index_properties_with_invalid_properties(self, invalid_property):
|
||||
"""
|
||||
target: test alter index properties with invalid properties
|
||||
@@ -460,9 +553,9 @@ class TestIndexProperties(TestBase):
|
||||
"schema": {
|
||||
"fields": [
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "my_vector", "dataType": "FloatVector", "elementTypeParams": {"dim": 128}}
|
||||
{"fieldName": "my_vector", "dataType": "FloatVector", "elementTypeParams": {"dim": 128}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
collection_client.collection_create(payload)
|
||||
|
||||
@@ -476,16 +569,15 @@ class TestIndexProperties(TestBase):
|
||||
"indexName": "my_vector",
|
||||
"indexType": "IVF_SQ8",
|
||||
"metricType": "L2",
|
||||
"params": {"nlist": 128}
|
||||
"params": {"nlist": 128},
|
||||
}
|
||||
|
||||
],
|
||||
}
|
||||
index_client.index_create(index_payload)
|
||||
|
||||
# Alter index properties with invalid property
|
||||
rsp = index_client.alter_index_properties(name, "my_vector", invalid_property)
|
||||
assert rsp['code'] == 1100
|
||||
assert rsp["code"] == 1100
|
||||
|
||||
def test_drop_index_properties_with_nonexistent_key(self):
|
||||
"""
|
||||
@@ -501,9 +593,9 @@ class TestIndexProperties(TestBase):
|
||||
"schema": {
|
||||
"fields": [
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "my_vector", "dataType": "FloatVector", "elementTypeParams": {"dim": 128}}
|
||||
{"fieldName": "my_vector", "dataType": "FloatVector", "elementTypeParams": {"dim": 128}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
collection_client.collection_create(payload)
|
||||
|
||||
@@ -517,9 +609,8 @@ class TestIndexProperties(TestBase):
|
||||
"indexName": "my_vector",
|
||||
"indexType": "IVF_SQ8",
|
||||
"metricType": "L2",
|
||||
"params": {"nlist": 128}
|
||||
"params": {"nlist": 128},
|
||||
}
|
||||
|
||||
],
|
||||
}
|
||||
index_client.index_create(index_payload)
|
||||
@@ -527,18 +618,16 @@ class TestIndexProperties(TestBase):
|
||||
# Drop index properties with nonexistent key
|
||||
delete_keys = ["nonexistent.key"]
|
||||
rsp = index_client.drop_index_properties(name, "my_vector", delete_keys)
|
||||
assert rsp['code'] == 1100
|
||||
assert rsp["code"] == 1100
|
||||
|
||||
|
||||
@pytest.mark.L1
|
||||
class TestCreateIndexNegative(TestBase):
|
||||
|
||||
@pytest.mark.parametrize("index_type", ["BIN_FLAT", "BIN_IVF_FLAT"])
|
||||
@pytest.mark.parametrize("metric_type", ["L2", "IP", "COSINE"])
|
||||
@pytest.mark.parametrize("dim", [128])
|
||||
def test_index_for_binary_vector_field_with_mismatch_metric_type(self, dim, metric_type, index_type):
|
||||
"""
|
||||
"""
|
||||
""" """
|
||||
name = gen_collection_name()
|
||||
client = self.collection_client
|
||||
payload = {
|
||||
@@ -548,9 +637,9 @@ class TestCreateIndexNegative(TestBase):
|
||||
{"fieldName": "book_id", "dataType": "Int64", "isPrimary": True, "elementTypeParams": {}},
|
||||
{"fieldName": "word_count", "dataType": "Int64", "elementTypeParams": {}},
|
||||
{"fieldName": "book_describe", "dataType": "VarChar", "elementTypeParams": {"max_length": "256"}},
|
||||
{"fieldName": "binary_vector", "dataType": "BinaryVector", "elementTypeParams": {"dim": f"{dim}"}}
|
||||
{"fieldName": "binary_vector", "dataType": "BinaryVector", "elementTypeParams": {"dim": f"{dim}"}},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
logger.info(f"create collection {name} with payload: {payload}")
|
||||
rsp = client.collection_create(payload)
|
||||
@@ -562,13 +651,10 @@ class TestCreateIndexNegative(TestBase):
|
||||
"book_id": j,
|
||||
"word_count": j,
|
||||
"book_describe": f"book_{j}",
|
||||
"binary_vector": gen_vector(datatype="BinaryVector", dim=dim)
|
||||
"binary_vector": gen_vector(datatype="BinaryVector", dim=dim),
|
||||
}
|
||||
data.append(tmp)
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"data": data
|
||||
}
|
||||
payload = {"collectionName": name, "data": data}
|
||||
rsp = self.vector_client.vector_insert(payload)
|
||||
c = Collection(name)
|
||||
c.flush()
|
||||
@@ -579,11 +665,17 @@ class TestCreateIndexNegative(TestBase):
|
||||
index_name = "binary_vector_index"
|
||||
payload = {
|
||||
"collectionName": name,
|
||||
"indexParams": [{"fieldName": "binary_vector", "indexName": index_name, "metricType": metric_type,
|
||||
"params": {"index_type": index_type}}]
|
||||
"indexParams": [
|
||||
{
|
||||
"fieldName": "binary_vector",
|
||||
"indexName": index_name,
|
||||
"metricType": metric_type,
|
||||
"params": {"index_type": index_type},
|
||||
}
|
||||
],
|
||||
}
|
||||
if index_type == "BIN_IVF_FLAT":
|
||||
payload["indexParams"][0]["params"]["nlist"] = "16384"
|
||||
rsp = self.index_client.index_create(payload)
|
||||
assert rsp['code'] == 1100
|
||||
assert "not supported" in rsp['message']
|
||||
assert rsp["code"] == 1100
|
||||
assert "not supported" in rsp["message"]
|
||||
|
||||
Reference in New Issue
Block a user