fix(qdrant): add wildcard * support and update type hints
Wildcard '*' values now return None from _build_field_condition (match any / field exists semantics), and _create_filter skips None conditions so a filter containing only wildcards returns None. Updated return type annotations to Optional[FieldCondition] and Optional[Filter] accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from qdrant_client import QdrantClient
|
from qdrant_client import QdrantClient
|
||||||
from qdrant_client.models import (
|
from qdrant_client.models import (
|
||||||
@@ -141,7 +142,7 @@ class Qdrant(VectorStoreBase):
|
|||||||
]
|
]
|
||||||
self.client.upsert(collection_name=self.collection_name, points=points)
|
self.client.upsert(collection_name=self.collection_name, points=points)
|
||||||
|
|
||||||
def _build_field_condition(self, key: str, value) -> FieldCondition:
|
def _build_field_condition(self, key: str, value) -> Optional[FieldCondition]:
|
||||||
"""
|
"""
|
||||||
Build a single FieldCondition from a key-value filter pair.
|
Build a single FieldCondition from a key-value filter pair.
|
||||||
|
|
||||||
@@ -153,9 +154,14 @@ class Qdrant(VectorStoreBase):
|
|||||||
value: A scalar for simple equality, or a dict with one operator key.
|
value: A scalar for simple equality, or a dict with one operator key.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
FieldCondition: The Qdrant field condition.
|
Optional[FieldCondition]: The Qdrant field condition, or None if the
|
||||||
|
value is the wildcard '*' (match any / field exists — skip filter).
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
|
if value == "*":
|
||||||
|
# Wildcard: match any value. Qdrant has no direct "field exists"
|
||||||
|
# condition via FieldCondition, so we skip this filter (match all).
|
||||||
|
return None
|
||||||
# Simple equality: {"field": "value"}
|
# Simple equality: {"field": "value"}
|
||||||
return FieldCondition(key=key, match=MatchValue(value=value))
|
return FieldCondition(key=key, match=MatchValue(value=value))
|
||||||
|
|
||||||
@@ -192,7 +198,7 @@ class Qdrant(VectorStoreBase):
|
|||||||
f"Supported operators: {supported}"
|
f"Supported operators: {supported}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_filter(self, filters: dict) -> Filter:
|
def _create_filter(self, filters: dict) -> Optional[Filter]:
|
||||||
"""
|
"""
|
||||||
Create a Filter object from the provided filters.
|
Create a Filter object from the provided filters.
|
||||||
|
|
||||||
@@ -230,7 +236,9 @@ class Qdrant(VectorStoreBase):
|
|||||||
if built:
|
if built:
|
||||||
must_not.append(built)
|
must_not.append(built)
|
||||||
else:
|
else:
|
||||||
must.append(self._build_field_condition(key, value))
|
condition = self._build_field_condition(key, value)
|
||||||
|
if condition is not None:
|
||||||
|
must.append(condition)
|
||||||
|
|
||||||
if not any([must, should, must_not]):
|
if not any([must, should, must_not]):
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -551,5 +551,22 @@ class TestQdrantEnhancedFilters(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.qdrant._build_field_condition("score", {"gt": 0.5, "eq": 1.0})
|
self.qdrant._build_field_condition("score", {"gt": 0.5, "eq": 1.0})
|
||||||
|
|
||||||
|
def test_wildcard_returns_none(self):
|
||||||
|
"""Wildcard '*' should return None (skip filter — match any)."""
|
||||||
|
result = self.qdrant._build_field_condition("category", "*")
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_create_filter_with_wildcard_skips_it(self):
|
||||||
|
"""Wildcard fields should be skipped in the final filter."""
|
||||||
|
result = self.qdrant._create_filter({"category": "*", "user_id": "alice"})
|
||||||
|
self.assertIsInstance(result, Filter)
|
||||||
|
self.assertEqual(len(result.must), 1)
|
||||||
|
self.assertEqual(result.must[0].key, "user_id")
|
||||||
|
|
||||||
|
def test_create_filter_only_wildcard_returns_none(self):
|
||||||
|
"""Filter with only wildcard should return None."""
|
||||||
|
result = self.qdrant._create_filter({"category": "*"})
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
del self.qdrant
|
del self.qdrant
|
||||||
|
|||||||
Reference in New Issue
Block a user