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:
kartik-mem0
2026-03-21 18:05:48 +05:30
parent b53507efd3
commit a930603c12
2 changed files with 29 additions and 4 deletions
+12 -4
View File
@@ -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
+17
View File
@@ -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