|
20 | 20 | from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto |
21 | 21 | from feast.protos.feast.types.Value_pb2 import Value as ValueProto |
22 | 22 | from feast.repo_config import RepoConfig |
23 | | -from feast.types import Float32, Int64, String |
| 23 | +from feast.types import Array, Float32, Int64, String |
24 | 24 | from feast.value_type import ValueType |
25 | 25 |
|
26 | 26 | MILVUS_MODULE = "feast.infra.online_stores.milvus_online_store.milvus" |
@@ -375,3 +375,124 @@ def test_lite_path_used_without_uri(mock_client_cls: MagicMock) -> None: |
375 | 375 | MilvusOnlineStore()._connect(config) |
376 | 376 |
|
377 | 377 | mock_client_cls.assert_called_once_with("/tmp/online_store.db") |
| 378 | + |
| 379 | + |
| 380 | +def _vector_feature_view(name: str = "driver_embeddings") -> FeatureView: |
| 381 | + return FeatureView( |
| 382 | + name=name, |
| 383 | + entities=[_driver_entity()], |
| 384 | + ttl=timedelta(days=1), |
| 385 | + schema=[ |
| 386 | + Field(name="driver_id", dtype=Int64), |
| 387 | + Field( |
| 388 | + name="embedding", |
| 389 | + dtype=Array(Float32), |
| 390 | + vector_index=True, |
| 391 | + vector_search_metric="COSINE", |
| 392 | + ), |
| 393 | + Field(name="city", dtype=String), |
| 394 | + ], |
| 395 | + ) |
| 396 | + |
| 397 | + |
| 398 | +def _vector_rows() -> Dict[int, Dict[str, ValueProto]]: |
| 399 | + def embedding(x: float, y: float) -> ValueProto: |
| 400 | + value = ValueProto() |
| 401 | + value.float_list_val.val.extend([x, y]) |
| 402 | + return value |
| 403 | + |
| 404 | + return { |
| 405 | + 1: {"embedding": embedding(1.0, 0.0), "city": ValueProto(string_val="Paris")}, |
| 406 | + 2: {"embedding": embedding(0.0, 1.0), "city": ValueProto(string_val="Rome")}, |
| 407 | + } |
| 408 | + |
| 409 | + |
| 410 | +def _search_with_mock(**online_store: Any) -> Dict[str, Any]: |
| 411 | + """Create a collection and search it with a mocked client. |
| 412 | +
|
| 413 | + Returns the index definition of the embedding field and the search kwargs. |
| 414 | + """ |
| 415 | + with patch(f"{MILVUS_MODULE}.MilvusClient") as mock_client_cls: |
| 416 | + mock_client = _mock_client(mock_client_cls, has_collection=False) |
| 417 | + mock_client.describe_collection.return_value = { |
| 418 | + "collection_name": "test_milvus_driver_embeddings", |
| 419 | + "fields": [ |
| 420 | + {"name": "driver_id_pk", "type": DataType.VARCHAR, "params": {}}, |
| 421 | + {"name": "event_ts", "type": DataType.INT64, "params": {}}, |
| 422 | + {"name": "created_ts", "type": DataType.INT64, "params": {}}, |
| 423 | + { |
| 424 | + "name": "embedding", |
| 425 | + "type": DataType.FLOAT_VECTOR, |
| 426 | + "params": {"dim": 2}, |
| 427 | + }, |
| 428 | + {"name": "city", "type": DataType.VARCHAR, "params": {}}, |
| 429 | + ], |
| 430 | + } |
| 431 | + mock_client.search.return_value = [[]] |
| 432 | + |
| 433 | + store = MilvusOnlineStore() |
| 434 | + store.retrieve_online_documents_v2( |
| 435 | + _mock_config(embedding_dim=2, **online_store), |
| 436 | + _vector_feature_view(), |
| 437 | + ["embedding", "city"], |
| 438 | + embedding=[1.0, 0.0], |
| 439 | + top_k=1, |
| 440 | + ) |
| 441 | + return { |
| 442 | + "index": _created_indexes(mock_client)["embedding"], |
| 443 | + "search": mock_client.search.call_args.kwargs, |
| 444 | + } |
| 445 | + |
| 446 | + |
| 447 | +def test_default_index_and_search_params_unchanged() -> None: |
| 448 | + result = _search_with_mock(index_type="IVF_FLAT") |
| 449 | + |
| 450 | + assert result["index"]["index_type"] == "IVF_FLAT" |
| 451 | + assert result["index"]["nlist"] == 128 |
| 452 | + assert result["search"]["search_params"]["params"] == {"nprobe": 10} |
| 453 | + |
| 454 | + |
| 455 | +def test_autoindex_gets_no_index_or_search_params_by_default() -> None: |
| 456 | + result = _search_with_mock(index_type="AUTOINDEX") |
| 457 | + |
| 458 | + # Milvus servers reject AUTOINDEX with any build param besides the metric. |
| 459 | + assert result["index"] == { |
| 460 | + "field_name": "embedding", |
| 461 | + "index_type": "AUTOINDEX", |
| 462 | + "index_name": "vector_index_embedding", |
| 463 | + "metric_type": "COSINE", |
| 464 | + } |
| 465 | + assert result["search"]["search_params"]["params"] == {} |
| 466 | + |
| 467 | + |
| 468 | +def test_index_and_search_params_pass_through() -> None: |
| 469 | + result = _search_with_mock( |
| 470 | + index_type="HNSW", |
| 471 | + index_params={"M": 16, "efConstruction": 200}, |
| 472 | + search_params={"ef": 64}, |
| 473 | + ) |
| 474 | + |
| 475 | + assert result["index"]["M"] == 16 |
| 476 | + assert result["index"]["efConstruction"] == 200 |
| 477 | + assert "nlist" not in result["index"] |
| 478 | + assert result["search"]["search_params"]["params"] == {"ef": 64} |
| 479 | + |
| 480 | + |
| 481 | +def test_autoindex_search_with_level(tmp_path: Path) -> None: |
| 482 | + config = _lite_config(tmp_path, index_type="AUTOINDEX", search_params={"level": 2}) |
| 483 | + fv = _vector_feature_view() |
| 484 | + store = MilvusOnlineStore() |
| 485 | + store.update(config, [], [fv], [], [], partial=False) |
| 486 | + _write_rows(store, config, fv, _vector_rows()) |
| 487 | + |
| 488 | + results = store.retrieve_online_documents_v2( |
| 489 | + config, |
| 490 | + fv, |
| 491 | + ["embedding", "city"], |
| 492 | + embedding=[1.0, 0.0], |
| 493 | + top_k=1, |
| 494 | + distance_metric="COSINE", |
| 495 | + ) |
| 496 | + |
| 497 | + assert len(results) == 1 |
| 498 | + assert results[0][2] is not None and results[0][2]["city"].string_val == "Paris" |
0 commit comments