mindnet/scripts/reset_qdrant.py
Lars e9532e8878
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 4s
script_Überprüfung und Kommentarheader
2025-12-28 10:40:28 +01:00

228 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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)
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).
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)
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
Verwendung:
-----------
- Vor größeren Migrationen oder Schema-Änderungen
- Bei Inkonsistenzen in der Datenbank
- Für Entwicklung/Testing (schneller Reset)
- CI/CD-Pipelines (mit --yes)
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)
Umgebungsvariablen:
-------------------
QDRANT_URL, QDRANT_API_KEY, COLLECTION_PREFIX, VECTOR_DIM (Default: 768)
Ä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
"""
from __future__ import annotations
import argparse
import os
import sys
from typing import List
# FIX: Dotenv laden
from dotenv import load_dotenv
from qdrant_client import QdrantClient
from qdrant_client.http import models as rest
from app.core.qdrant import (
QdrantConfig,
get_client,
ensure_collections,
ensure_payload_indexes,
collection_names,
)
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
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)")
if nonexisting:
print("Nicht vorhanden (werden bei wipe ggf. neu angelegt):")
for c in nonexisting:
print(f" - {c}")
if assume_yes:
print("\n--yes gesetzt → führe ohne Rückfrage aus.")
return True
try:
ans = input("\nFortfahren? (yes/no): ").strip().lower()
except EOFError:
return False
return ans in ("y", "yes")
def delete_all_points(client: QdrantClient, collections: List[str]) -> None:
"""Löscht *alle* Points in den angegebenen Collections.
API-Kompatibilität:
- neuere qdrant-client: client.delete_points(...)
- ältere qdrant-client: client.delete(...)
"""
match_all = rest.Filter(must=[])
for col in collections:
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
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
def main():
# FIX: Umgebungsvariablen aus .env laden
load_dotenv()
ap = argparse.ArgumentParser(description="Wipe oder truncate mindnet-Collections in Qdrant (mit Bestätigung & Index-Setup).")
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")
ap.add_argument("--no-indexes", action="store_true", help="Überspringt ensure_payload_indexes()")
args = ap.parse_args()
# Qdrant-Konfiguration
try:
# Hier wird jetzt VECTOR_DIM=768 korrekt berücksichtigt
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}")
# 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)
if not args.no_indexes:
ensure_payload_indexes(client, cfg.prefix)
print(f"Wiped & recreated (Prefix={cfg.prefix}): {all_col_names}")
if not args.no_indexes:
print("Payload-Indizes & Schema-Ergänzungen geprüft/ergänzt (ensure_payload_indexes).")
else:
if not existing:
print("Keine existierenden Collections zum Truncaten gefunden. Beende ohne Aktion.")
sys.exit(0)
delete_all_points(client, existing)
if not args.no_indexes:
# Auch nach truncate sicherstellen, dass neue/fehlende Indizes ergänzt werden
ensure_payload_indexes(client, cfg.prefix)
print(f"Truncated (deleted all points in): {existing}")
if not args.no_indexes:
print("Payload-Indizes & Schema-Ergänzungen geprüft/ergänzt (ensure_payload_indexes).")
if __name__ == "__main__":
main()