Skip to content

Commit b570677

Browse files
committed
Python: keep large floats on Range and 2**53 on MatchValue in Qdrant filters
The float64 cutoff also applied to floats, so a value such as 1e20 fell through to MatchValue, which rejects floats. And it included 2**53, which float64 can't tell apart from 2**53 + 1. Apply the cutoff to ints only, and keep ints from 2**53 up on MatchValue.
1 parent 279bdb6 commit b570677

2 files changed

Lines changed: 20 additions & 3 deletions

File tree

‎python/semantic_kernel/connectors/qdrant.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,17 @@
7979
"default": Datatype.FLOAT32,
8080
}
8181
IN_MEMORY_STRING: Final[str] = ":memory:"
82-
# The server compares ranges as float64, so larger ints are matched exactly with MatchValue instead.
82+
# The server compares ranges as float64, which can't tell 2**53 from 2**53 + 1, so ints from 2**53 up
83+
# are matched exactly with MatchValue instead.
8384
MAX_EXACT_FLOAT_INT: Final[int] = 2**53
8485

8586

8687
def _match_value(key: str, value: Any) -> FieldCondition:
8788
"""Match a single value, numerically for numbers so 5 also matches 5.0 and floats are accepted."""
8889
# MatchValue only accepts str, int and bool, and an int never matches a float payload
89-
if isinstance(value, (int, float)) and not isinstance(value, bool) and abs(value) <= MAX_EXACT_FLOAT_INT:
90+
if isinstance(value, float) or (
91+
isinstance(value, int) and not isinstance(value, bool) and abs(value) < MAX_EXACT_FLOAT_INT
92+
):
9093
return FieldCondition(key=key, range=Range(gte=value, lte=value))
9194
return FieldCondition(key=key, match=MatchValue(value=value))
9295

‎python/tests/unit/connectors/memory/test_qdrant_local.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from pytest import fixture, mark
77

8-
from semantic_kernel.connectors.qdrant import QdrantCollection
8+
from semantic_kernel.connectors.qdrant import QdrantCollection, _match_value
99
from semantic_kernel.data.vector import VectorStoreField, vectorstoremodel
1010

1111

@@ -92,8 +92,22 @@ async def product_collection():
9292
("lambda x: x.price not in [4.5]", [2]),
9393
("lambda x: x.in_stock in [True]", [1]),
9494
("lambda x: x.in_stock not in [True]", [2]),
95+
# Large floats too, which MatchValue would reject
96+
("lambda x: x.price == 1e20", []),
97+
("lambda x: x.price != 1e20", [1, 2]),
9598
],
9699
)
97100
async def test_local_filter_with_float_and_bool_values(product_collection, filter_str, expected):
98101
results = await product_collection.search(vector=[1.0, 1.0], filter=filter_str)
99102
assert sorted([r.record.id async for r in results.results]) == expected
103+
104+
105+
@mark.parametrize(
106+
("value", "uses_range"),
107+
[(2**53 - 1, True), (-(2**53) + 1, True), (2**53, False), (2**60, False), (1e20, True)],
108+
)
109+
def test_match_value_uses_range_only_where_float64_is_exact(value, uses_range):
110+
# The server compares ranges as float64, where 2**53 and 2**53 + 1 are the same value
111+
condition = _match_value("n", value)
112+
assert (condition.range is not None) is uses_range
113+
assert (condition.match is None) is uses_range

0 commit comments

Comments
 (0)