fix: delete Qdrant points by ID so memory deletions don't orphan vectors (#25495)
The Qdrant backends implemented delete(ids=...) as a payload filter on
metadata.id, but points are stored with the item id as the Qdrant point id
(see _create_points), and not every point carries an id in its payload.
Memory points store only {created_at} in metadata (KB metadata embeddings
likewise), so deleting a single memory matched nothing and left an orphaned
vector that kept being injected into RAG context.
Delete by point id instead: PointIdsList for the standard backend, and a
tenant-scoped HasIdCondition for multitenancy (point ids are unique, so tenant
isolation is preserved). Filter-based deletion is unchanged.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
eebbc48f80
commit
eb38389636
2 files changed
+20
-23
No files matched your search
@@ -216,28 +216,23 @@ class QdrantClient(VectorDBBase):
|
|||||||
ids: Optional[list[str]] = None,
|
ids: Optional[list[str]] = None,
|
||||||
filter: Optional[dict] = None,
|
filter: Optional[dict] = None,
|
||||||
):
|
):
|
||||||
# Delete the items from the collection based on the ids.
|
# Delete by point ID: the point ID is the item's id (see _create_points).
|
||||||
field_conditions = []
|
# Filtering on metadata.id silently misses points whose payload omits an
|
||||||
|
# id (e.g. memories), leaving orphaned vectors behind.
|
||||||
if ids:
|
if ids:
|
||||||
for id_value in ids:
|
return self.client.delete(
|
||||||
(
|
collection_name=f'{self.collection_prefix}_{collection_name}',
|
||||||
field_conditions.append(
|
points_selector=models.PointIdsList(points=ids),
|
||||||
models.FieldCondition(
|
)
|
||||||
key='metadata.id',
|
|
||||||
match=models.MatchValue(value=id_value),
|
field_conditions = []
|
||||||
),
|
if filter:
|
||||||
),
|
|
||||||
)
|
|
||||||
elif filter:
|
|
||||||
for key, value in filter.items():
|
for key, value in filter.items():
|
||||||
(
|
field_conditions.append(
|
||||||
field_conditions.append(
|
models.FieldCondition(
|
||||||
models.FieldCondition(
|
key=f'metadata.{key}',
|
||||||
key=f'metadata.{key}',
|
match=models.MatchValue(value=value),
|
||||||
match=models.MatchValue(value=value),
|
)
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.client.delete(
|
return self.client.delete(
|
||||||
|
|||||||
@@ -228,15 +228,17 @@ class QdrantClient(VectorDBBase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
must_conditions = [_tenant_filter(tenant_id)]
|
must_conditions = [_tenant_filter(tenant_id)]
|
||||||
should_conditions = []
|
|
||||||
if ids:
|
if ids:
|
||||||
should_conditions = [_metadata_filter('id', id_value) for id_value in ids]
|
# Delete by point ID within the tenant. The point ID is the item's id
|
||||||
|
# (see _create_points); filtering on metadata.id silently misses points
|
||||||
|
# whose payload omits an id (e.g. memories), leaving orphaned vectors.
|
||||||
|
must_conditions.append(models.HasIdCondition(has_id=ids))
|
||||||
elif filter:
|
elif filter:
|
||||||
must_conditions += [_metadata_filter(k, v) for k, v in filter.items()]
|
must_conditions += [_metadata_filter(k, v) for k, v in filter.items()]
|
||||||
|
|
||||||
return self.client.delete(
|
return self.client.delete(
|
||||||
collection_name=mt_collection,
|
collection_name=mt_collection,
|
||||||
points_selector=models.FilterSelector(filter=models.Filter(must=must_conditions, should=should_conditions)),
|
points_selector=models.FilterSelector(filter=models.Filter(must=must_conditions)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
|
|||||||
Reference in new issue
Block a user