mindnet/scripts/reset_qdrant.py

228 lines
8.1 KiB
Python
Raw Permalink Normal View History

2025-09-04 16:52:12 +02:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FILE: scripts/reset_qdrant.py
VERSION: 2.1.0 (2025-12-15)
STATUS: Active (Core)
COMPATIBILITY: v2.9.1 (Post-WP14/WP-15b)
2025-09-04 16:52:12 +02:00
Zweck:
-------
Sicheres Zurücksetzen der Qdrant-Collections für ein Projektpräfix.
Ermöglicht vollständigen Neustart (wipe) oder Löschen nur der Inhalte (truncate).
2025-09-04 16:52:12 +02:00
Funktionsweise:
---------------
1. Ermittelt betroffene Collections (notes, chunks, edges für Präfix)
2. Zeigt Preview der existierenden Collections
3. Interaktive Bestätigung (außer mit --yes)
4. Führt Aktion aus:
- wipe: Löscht Collections komplett, legt neu an, richtet Indizes ein
- truncate: Löscht nur Points, behält Collection-Settings, prüft Indizes
5. Richtet Payload-Indizes idempotent ein (außer mit --no-indexes)
2025-09-04 16:52:12 +02:00
Ergebnis-Interpretation:
------------------------
- Preview: Zeigt betroffene Collections vor Ausführung
- Exit-Code 0: Erfolgreich
- Exit-Code 1: Abgebrochen oder keine Aktion
- Exit-Code 2: Verbindungs- oder Konfigurationsfehler
2025-09-04 16:52:12 +02:00
Verwendung:
-----------
- Vor größeren Migrationen oder Schema-Änderungen
- Bei Inkonsistenzen in der Datenbank
- Für Entwicklung/Testing (schneller Reset)
- CI/CD-Pipelines (mit --yes)
2025-09-04 16:52:12 +02:00
2025-09-05 07:12:47 +02:00
Sicherheitsmerkmale:
-------------------
- Betrachtet nur Collections des angegebenen Präfixes
- Listet betroffene Collections vor Ausführung auf
- Interaktive Bestätigung (außer mit --yes)
- Dry-Run Modus verfügbar
Aufruf:
-------
python3 -m scripts.reset_qdrant --mode wipe --prefix mindnet
python3 -m scripts.reset_qdrant --mode truncate --prefix mindnet --yes
python3 -m scripts.reset_qdrant --mode wipe --dry-run
Parameter:
----------
--mode MODE wipe | truncate (Pflicht)
- wipe: Collections löschen & neu anlegen
- truncate: Nur Points löschen, Settings behalten
--prefix TEXT Collection-Präfix (Default: ENV COLLECTION_PREFIX oder mindnet)
--yes Keine Rückfrage, direkt ausführen (CI/CD)
--dry-run Nur anzeigen, was passieren würde
--no-indexes Überspringt ensure_payload_indexes() (Standard: Indizes werden geprüft/ergänzt)
2025-09-04 16:52:12 +02:00
Umgebungsvariablen:
-------------------
QDRANT_URL, QDRANT_API_KEY, COLLECTION_PREFIX, VECTOR_DIM (Default: 768)
2025-09-04 16:52:12 +02:00
Änderungen:
-----------
v2.1.0 (2025-12-15): Kompatibilität mit WP-14 Modularisierung
- Aktualisiert: Import-Pfade für neue Struktur
v1.2.1 (2025-12-11): Fix load_dotenv() für VECTOR_DIM
v1.2.0: ensure_payload_indexes() standardmäßig nach wipe/truncate
v1.1.0: Interaktive Bestätigung, --yes/--dry-run
v1.0.0: Initial Release
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
# FIX: Dotenv laden
from dotenv import load_dotenv
2025-09-04 16:52:12 +02:00
from qdrant_client import QdrantClient
from qdrant_client.http import models as rest
2025-12-28 11:04:40 +01:00
from app.core.database.qdrant import (
2025-11-11 17:40:34 +01:00
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():
# FIX: Umgebungsvariablen aus .env laden
load_dotenv()
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:
# Hier wird jetzt VECTOR_DIM=768 korrekt berücksichtigt
2025-09-05 07:12:47 +02:00
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]
# Debug-Info zur Dimension
print(f"Info: Nutze Vektor-Dimension: {cfg.dim}")
2025-09-05 07:12:47 +02:00
# 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__":
main()