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:
Classic298
2026-06-01 14:13:46 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent eebbc48f80
commit eb38389636
2 changed files with 20 additions and 23 deletions
@@ -228,15 +228,17 @@ class QdrantClient(VectorDBBase):
return None
must_conditions = [_tenant_filter(tenant_id)]
should_conditions = []
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:
must_conditions += [_metadata_filter(k, v) for k, v in filter.items()]
return self.client.delete(
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(