mindnet/scripts/reset_qdrant.py

192 lines
7.7 KiB
Python
Raw Permalink Normal View History

2025-09-04 16:52:12 +02:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
2025-09-05 07:12:47 +02:00
Name: scripts/reset_qdrant.py
2025-11-11 17:40:34 +01:00
Version: v1.2.0 (2025-11-11)
2025-09-04 16:52:12 +02:00
Kurzbeschreibung:
2025-09-05 07:12:47 +02:00
Sicheres Zurücksetzen der Qdrant-Collections für EIN Projektpräfix. Das Skript
ermittelt zunächst die tatsächlich betroffenen Collections und zeigt eine
Übersicht an. Anschließend wird der User um Bestätigung gebeten (interaktive
2025-11-11 17:40:34 +01:00
Abfrage), bevor eine der Aktionen ausgeführt wird:
2025-09-04 16:52:12 +02:00
2025-11-11 17:40:34 +01:00
- wipe: Löscht die Collections komplett und legt sie gemäß Projekt-Defaults neu an.
**Neu**: Danach werden *immer* die Payload-Indizes & Schema-Ergänzungen
über ensure_payload_indexes() idempotent eingerichtet (falls nicht mit --no-indexes deaktiviert).
- truncate: Löscht nur alle Points (Inhalte), behält die Collection-Settings.
**Neu**: Optional (Default) werden im Anschluss die Payload-Indizes geprüft/ergänzt.
2025-09-04 16:52:12 +02:00
Aufruf (aus Projekt-Root, im venv):
2025-09-05 07:12:47 +02:00
python3 -m scripts.reset_qdrant --mode wipe [--prefix PREFIX] [--yes] [--dry-run]
python3 -m scripts.reset_qdrant --mode truncate [--prefix PREFIX] [--yes] [--dry-run]
2025-09-04 16:52:12 +02:00
Parameter:
2025-11-11 17:40:34 +01:00
--mode wipe | truncate (Pflicht)
--prefix Collection-Prefix (Default: env COLLECTION_PREFIX oder 'mindnet')
--yes Keine Rückfrage, direkt ausführen (CI/CD geeignet)
--dry-run Nur anzeigen, was passieren würde; nichts ändern
--no-indexes Überspringt den Schritt ensure_payload_indexes() (Standard: Indizes/Schema werden geprüft/ergänzt)
2025-09-04 16:52:12 +02:00
Umgebungsvariablen (optional):
2025-09-05 07:12:47 +02:00
QDRANT_URL, QDRANT_API_KEY, COLLECTION_PREFIX, VECTOR_DIM (Default 384)
2025-11-11 17:40:34 +01:00
(Zusätzliche ENV für Named Vectors etc. werden innerhalb ensure_collections/ensure_payload_indexes berücksichtigt.)
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
Sicherheitsmerkmale:
- Betrachtet ausschließlich Collections des angegebenen Präfixes.
- Listet vor Ausführung die tatsächlich existierenden Ziel-Collections auf.
- Fragt interaktiv nach Bestätigung (es sei denn --yes ist gesetzt).
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
Exitcodes:
0 = OK, 1 = abgebrochen/keine Aktion, 2 = Verbindungs-/Konfigurationsfehler
2025-09-04 16:52:12 +02:00
Changelog:
2025-11-11 17:40:34 +01:00
v1.2.0: ensure_payload_indexes() nach wipe/truncate standardmäßig ausführen (idempotent); --no-indexes Flag ergänzt.
v1.1.1: Stabilisierung & Preview (2025-09-05).
2025-09-05 07:12:47 +02:00
v1.1.0: Interaktive Bestätigung, --yes/--dry-run hinzugefügt, Preview der betroffenen Collections.
v1.0.0: Wipe/Truncate ohne Bestätigungsabfrage.
2025-09-04 16:52:12 +02:00
"""
from __future__ import annotations
import argparse
2025-09-05 07:12:47 +02:00
import os
import sys
from typing import List
2025-09-04 16:52:12 +02:00
from qdrant_client import QdrantClient
from qdrant_client.http import models as rest
2025-11-11 17:40:34 +01:00
from app.core.qdrant import (
QdrantConfig,
get_client,
ensure_collections,
ensure_payload_indexes,
collection_names,
)
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
def resolve_existing_collections(client: QdrantClient, prefix: str) -> List[str]:
"""Ermittelt die *existierenden* Collections für das übergebene Präfix.
Es werden NUR die projektdefinierten Collections betrachtet (notes/chunks/edges).
"""
notes, chunks, edges = collection_names(prefix)
candidates = [notes, chunks, edges]
existing = [c for c in candidates if client.collection_exists(c)]
return existing
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
def confirm_or_abort(action: str, collections: List[str], nonexisting: List[str], assume_yes: bool) -> bool:
print("\n=== Reset-Vorschau ===")
print(f"Aktion: {action}")
if collections:
print("Betroffen (existieren):")
for c in collections:
print(f" - {c}")
else:
print("Betroffen (existieren): — (keine)")
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
if nonexisting:
print("Nicht vorhanden (werden bei wipe ggf. neu angelegt):")
for c in nonexisting:
print(f" - {c}")
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
if assume_yes:
print("\n--yes gesetzt → führe ohne Rückfrage aus.")
return True
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
try:
ans = input("\nFortfahren? (yes/no): ").strip().lower()
except EOFError:
return False
return ans in ("y", "yes")
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
def delete_all_points(client: QdrantClient, collections: List[str]) -> None:
2025-09-05 07:15:20 +02:00
"""Löscht *alle* Points in den angegebenen Collections.
API-Kompatibilität:
- neuere qdrant-client: client.delete_points(...)
- ältere qdrant-client: client.delete(...)
"""
2025-09-05 07:12:47 +02:00
match_all = rest.Filter(must=[])
for col in collections:
2025-09-05 07:15:20 +02:00
try:
if hasattr(client, "delete_points"):
client.delete_points(collection_name=col, points_selector=match_all, wait=True)
else:
client.delete(collection_name=col, points_selector=match_all, wait=True)
except Exception as e:
print(f"Fehler beim Löschen der Points in {col}: {e}", file=sys.stderr)
raise
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
def wipe_collections(client: QdrantClient, all_col_names: List[str], existing: List[str]) -> None:
# Lösche nur Collections, die wirklich existieren; die anderen werden anschließend neu angelegt
for col in existing:
client.delete_collection(col)
# ensure_collections legt alle benötigten Collections (notes/chunks/edges) neu an
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
def main():
2025-11-11 17:40:34 +01:00
ap = argparse.ArgumentParser(description="Wipe oder truncate mindnet-Collections in Qdrant (mit Bestätigung & Index-Setup).")
2025-09-05 07:12:47 +02:00
ap.add_argument("--mode", choices=["wipe", "truncate"], required=True,
help="wipe = Collections löschen & neu anlegen; truncate = nur Inhalte löschen")
ap.add_argument("--prefix", help="Collection-Prefix (Default: env COLLECTION_PREFIX oder 'mindnet')")
ap.add_argument("--yes", action="store_true", help="Ohne Rückfrage ausführen (CI/CD)")
ap.add_argument("--dry-run", action="store_true", help="Nur anzeigen, was passieren würde; nichts ändern")
2025-11-11 17:40:34 +01:00
ap.add_argument("--no-indexes", action="store_true", help="Überspringt ensure_payload_indexes()")
2025-09-05 07:12:47 +02:00
args = ap.parse_args()
# Qdrant-Konfiguration
try:
cfg = QdrantConfig.from_env()
except Exception as e:
print(f"Konfigurationsfehler: {e}", file=sys.stderr)
sys.exit(2)
if args.prefix:
cfg.prefix = args.prefix
# Client
try:
client = get_client(cfg)
except Exception as e:
print(f"Verbindungsfehler zu Qdrant: {e}", file=sys.stderr)
sys.exit(2)
# Ziel-Collections: existierende & nicht existierende (per Namenskonvention)
notes, chunks, edges = collection_names(cfg.prefix)
all_col_names = [notes, chunks, edges]
existing = resolve_existing_collections(client, cfg.prefix)
nonexisting = [c for c in all_col_names if c not in existing]
# Preview & Bestätigung
if not confirm_or_abort(args.mode, existing, nonexisting, args.yes):
print("Abgebrochen – keine Änderungen vorgenommen.")
sys.exit(1)
if args.dry_run:
print("Dry-Run – keine Änderungen vorgenommen.")
sys.exit(0)
# Ausführen
if args.mode == "wipe":
wipe_collections(client, all_col_names, existing)
ensure_collections(client, cfg.prefix, cfg.dim)
2025-11-11 17:40:34 +01:00
if not args.no_indexes:
ensure_payload_indexes(client, cfg.prefix)
2025-09-05 07:12:47 +02:00
print(f"Wiped & recreated (Prefix={cfg.prefix}): {all_col_names}")
2025-11-11 17:40:34 +01:00
if not args.no_indexes:
print("Payload-Indizes & Schema-Ergänzungen geprüft/ergänzt (ensure_payload_indexes).")
2025-09-05 07:12:47 +02:00
else:
if not existing:
print("Keine existierenden Collections zum Truncaten gefunden. Beende ohne Aktion.")
sys.exit(0)
delete_all_points(client, existing)
2025-11-11 17:40:34 +01:00
if not args.no_indexes:
# Auch nach truncate sicherstellen, dass neue/fehlende Indizes ergänzt werden
ensure_payload_indexes(client, cfg.prefix)
2025-09-05 07:12:47 +02:00
print(f"Truncated (deleted all points in): {existing}")
2025-11-11 17:40:34 +01:00
if not args.no_indexes:
print("Payload-Indizes & Schema-Ergänzungen geprüft/ergänzt (ensure_payload_indexes).")
2025-09-04 16:52:12 +02:00
if __name__ == "__main__":
2025-09-05 07:12:47 +02:00
main()