diff --git a/python/semantic_kernel/connectors/sql_server.py b/python/semantic_kernel/connectors/sql_server.py index d55cd59b022f..53b68799e38b 100644 --- a/python/semantic_kernel/connectors/sql_server.py +++ b/python/semantic_kernel/connectors/sql_server.py @@ -18,7 +18,6 @@ from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase from semantic_kernel.data.vector import ( - DISTANCE_FUNCTION_DIRECTION_HELPER, DistanceFunction, GetFilteredRecordOptions, IndexKind, @@ -1150,8 +1149,10 @@ def _build_search_query( f"Distance function '{vector_field.distance_function}' is not supported for field '{vector_field.name}'" ) distance_function = DISTANCE_FUNCTION_MAP[vector_field.distance_function] - asc: bool = True - asc = DISTANCE_FUNCTION_DIRECTION_HELPER[vector_field.distance_function](0, 1) + # VECTOR_DISTANCE returns a distance for every supported metric (the dot + # metric is the negative dot product), so the nearest rows are the + # smallest values. + asc = True command.query.append( f", VECTOR_DISTANCE('{distance_function}', {QueryBuilder.escape_identifier(vector_field.storage_name or vector_field.name)}, CAST(? AS VECTOR({vector_field.dimensions}))) as {SCORE_FIELD_NAME}\n", # noqa: E501 diff --git a/python/tests/unit/connectors/memory/test_sql_server.py b/python/tests/unit/connectors/memory/test_sql_server.py index 5e5cdf6954d1..66d0f9e6b0a5 100644 --- a/python/tests/unit/connectors/memory/test_sql_server.py +++ b/python/tests/unit/connectors/memory/test_sql_server.py @@ -286,7 +286,16 @@ def test_build_delete_query(self): assert cmd.parameters[0] == "test" assert str_cmd == "DELETE FROM [dbo].[Test] WHERE [id] IN (?) ;" - def test_build_search_query(self): + @mark.parametrize( + ("distance_function", "metric"), + [ + (DistanceFunction.COSINE_DISTANCE, "cosine"), + (DistanceFunction.EUCLIDEAN_DISTANCE, "euclidean"), + (DistanceFunction.DOT_PROD, "dot"), + (DistanceFunction.DEFAULT, "cosine"), + ], + ) + def test_build_search_query(self, distance_function, metric): schema = "dbo" table = "Test" key_field = VectorStoreField("key", name="id", type="str") @@ -300,7 +309,7 @@ def test_build_search_query(self): name="embedding", type="float", dimensions=5, - distance_function=DistanceFunction.COSINE_DISTANCE, + distance_function=distance_function, ), ] vector = [0.1, 0.2, 0.3, 0.4, 0.5] @@ -312,7 +321,7 @@ def test_build_search_query(self): assert cmd.parameters[0] == json.dumps(vector) str_cmd = str(cmd) assert ( - str_cmd == "SELECT [id], [name], [age], VECTOR_DISTANCE('cosine', [embedding], CAST(? AS VECTOR(5))) as " + str_cmd == f"SELECT [id], [name], [age], VECTOR_DISTANCE('{metric}', [embedding], CAST(? AS VECTOR(5))) as " "_vector_distance_value\n FROM [dbo].[Test] \nORDER BY " "_vector_distance_value ASC\nOFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY;" ) @@ -573,6 +582,49 @@ class MockRow: (json.dumps(vector), "test"), ) + async def test_search_with_default_distance_function( + self, + sql_server_unit_test_env, + mock_connection, + ): + @vectorstoremodel + @dataclass + class DefaultDistanceRecord: + id: Annotated[str, VectorStoreField("key")] + content: Annotated[str, VectorStoreField("data")] + vector: Annotated[list[float], VectorStoreField("vector", dimensions=5)] = None + + mock_cursor = MagicMock() + mock_connection.cursor.return_value.__enter__.return_value = mock_cursor + collection = SqlServerCollection( + collection_name="test", + record_type=DefaultDistanceRecord, + connection=mock_connection, + ) + assert collection.definition.vector_fields[0].distance_function == DistanceFunction.DEFAULT + vector = [0.1, 0.2, 0.3, 0.4, 0.5] + + @dataclass + class MockRow: + id: str + content: str + _vector_distance_value: float + + mock_cursor.description = [["id"], ["content"], ["_vector_distance_value"]] + mock_cursor.__iter__.return_value = [MockRow("1", "test", 0.1)] + search_result = await collection.search(vector=vector) + records = [record async for record in search_result.results] + assert records[0].record.id == "1" + assert records[0].score == 0.1 + mock_cursor.execute.assert_called_with( + ( + "SELECT [id], [content], VECTOR_DISTANCE('cosine', [vector], CAST(? AS VECTOR(5))) as " + "_vector_distance_value\n FROM [dbo].[test] \nORDER BY _vector_distance_value ASC\n" + "OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY;" + ), + (json.dumps(vector),), + ) + async def test_ensure_collection_exists( self, sql_server_unit_test_env,