Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion python/semantic_kernel/connectors/faiss.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,16 @@ async def _inner_upsert(self, records: Sequence[Any], **kwargs: Any) -> Sequence
self.indexes[vector_field.name].add(vectors) # type: ignore
start = len(self.indexes_key_map[vector_field.name])
for i, record in enumerate(records):
key = record[self.definition.key_field.name]
# Records are serialized in whichever shape the data model and
# definition produce: logical names from a pydantic model_dump(),
# storage names from the dict path, or a custom to_dict(). Use the
# key name the serialization actually recorded, like InMemory does.
key_field_name = getattr(record, "key_field_name", self._key_field_storage_name)
if key_field_name not in record:
key_field_name = (
self._key_field_storage_name if self._key_field_storage_name in record else self._key_field_name
)
key = record[key_field_name]
self.indexes_key_map[vector_field.name][key] = start + i
return await super()._inner_upsert(records, **kwargs)

Expand Down
71 changes: 70 additions & 1 deletion python/tests/unit/connectors/memory/test_faiss.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.

from typing import Annotated

import faiss
from pydantic import BaseModel
from pytest import fixture, mark, raises

from semantic_kernel.connectors.faiss import FaissCollection, FaissStore
from semantic_kernel.data.vector import DistanceFunction, VectorStoreCollectionDefinition, VectorStoreField
from semantic_kernel.data.vector import (
DistanceFunction,
VectorStoreCollectionDefinition,
VectorStoreField,
vectorstoremodel,
)
from semantic_kernel.exceptions import VectorStoreInitializationException


Expand Down Expand Up @@ -144,6 +152,67 @@ async def test_custom_index_upsert_and_search(store, data_model_def, use_index_d
assert collection.inner_storage == {}


@fixture(scope="function")
def aliased_definition(data_model_def) -> VectorStoreCollectionDefinition:
"""Only the key field declares a storage_name.

This mirrors the InMemory test for the same behaviour and keeps the data and
vector fields on their logical names, so the record reaches _inner_upsert
under whichever serialization the record type produces.
"""
data_model_def.key_field.storage_name = "stored_id"
return data_model_def


@vectorstoremodel
class AliasedModel(BaseModel):
id: Annotated[str, VectorStoreField("key", storage_name="stored_id")]
content: Annotated[str, VectorStoreField("data")] = "test content"
vector: Annotated[
list[float] | None,
VectorStoreField(
"vector",
dimensions=5,
index_kind="flat",
distance_function="dot_prod",
type="float",
),
] = None


@mark.parametrize("model_kind", ["dict", "pydantic"], ids=["dict", "pydantic"])
async def test_upsert_with_key_storage_name(aliased_definition, model_kind):
"""A key field with a storage_name must upsert whichever shape it serializes to."""
model_type = {"dict": dict, "pydantic": AliasedModel}[model_kind]
collection = FaissCollection(record_type=model_type, definition=aliased_definition, collection_name="test-alias")
record = model_type(id="testid", content="test content", vector=[1.0, 0.0, 0.0, 0.0, 0.0])
await collection.ensure_collection_exists()

assert await collection.upsert(record) == "testid"
assert collection.indexes_key_map["vector"] == {"testid": 0}
assert set(collection.inner_storage) == {"testid"}

results = await collection.search(vector=[1.0, 0.0, 0.0, 0.0, 0.0], top=1, include_vectors=True)
assert [result.record async for result in results.results] == [record]
await collection.ensure_collection_deleted()


async def test_upsert_with_custom_to_dict_logical_names(aliased_definition):
"""A custom to_dict that emits the logical key name must still resolve."""
aliased_definition.to_dict = lambda record, **kwargs: {
"id": record["id"],
"content": record["content"],
"vector": record["vector"],
}
collection = FaissCollection(record_type=dict, definition=aliased_definition, collection_name="test-alias-logical")
record = {"id": "testid", "content": "test content", "vector": [1.0, 0.0, 0.0, 0.0, 0.0]}
await collection.ensure_collection_exists()

assert await collection.upsert(record) == "testid"
assert collection.indexes_key_map["vector"] == {"testid": 0}
await collection.ensure_collection_deleted()


async def test_upsert(faiss_collection):
await faiss_collection.ensure_collection_exists()
record = {"id": "testid", "content": "test content", "vector": [0.1, 0.2, 0.3, 0.4, 0.5]}
Expand Down
Loading