Skip to content

Commit eb38389

Browse files
Classic298claude
andauthored
fix: delete Qdrant points by ID so memory deletions don't orphan vectors (open-webui#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>
1 parent eebbc48 commit eb38389

2 files changed

Lines changed: 20 additions & 23 deletions

File tree

backend/open_webui/retrieval/vector/dbs/qdrant.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -216,28 +216,23 @@ def delete(
216216
ids: Optional[list[str]] = None,
217217
filter: Optional[dict] = None,
218218
):
219-
# Delete the items from the collection based on the ids.
220-
field_conditions = []
221-
219+
# Delete by point ID: the point ID is the item's id (see _create_points).
220+
# Filtering on metadata.id silently misses points whose payload omits an
221+
# id (e.g. memories), leaving orphaned vectors behind.
222222
if ids:
223-
for id_value in ids:
224-
(
225-
field_conditions.append(
226-
models.FieldCondition(
227-
key='metadata.id',
228-
match=models.MatchValue(value=id_value),
229-
),
230-
),
231-
)
232-
elif filter:
223+
return self.client.delete(
224+
collection_name=f'{self.collection_prefix}_{collection_name}',
225+
points_selector=models.PointIdsList(points=ids),
226+
)
227+
228+
field_conditions = []
229+
if filter:
233230
for key, value in filter.items():
234-
(
235-
field_conditions.append(
236-
models.FieldCondition(
237-
key=f'metadata.{key}',
238-
match=models.MatchValue(value=value),
239-
),
240-
),
231+
field_conditions.append(
232+
models.FieldCondition(
233+
key=f'metadata.{key}',
234+
match=models.MatchValue(value=value),
235+
)
241236
)
242237

243238
return self.client.delete(

backend/open_webui/retrieval/vector/dbs/qdrant_multitenancy.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,17 @@ def delete(
228228
return None
229229

230230
must_conditions = [_tenant_filter(tenant_id)]
231-
should_conditions = []
232231
if ids:
233-
should_conditions = [_metadata_filter('id', id_value) for id_value in ids]
232+
# Delete by point ID within the tenant. The point ID is the item's id
233+
# (see _create_points); filtering on metadata.id silently misses points
234+
# whose payload omits an id (e.g. memories), leaving orphaned vectors.
235+
must_conditions.append(models.HasIdCondition(has_id=ids))
234236
elif filter:
235237
must_conditions += [_metadata_filter(k, v) for k, v in filter.items()]
236238

237239
return self.client.delete(
238240
collection_name=mt_collection,
239-
points_selector=models.FilterSelector(filter=models.Filter(must=must_conditions, should=should_conditions)),
241+
points_selector=models.FilterSelector(filter=models.Filter(must=must_conditions)),
240242
)
241243

242244
def search(

0 commit comments

Comments
 (0)