2025-09-04 16:52:12 +02:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
2025-12-28 10:40:28 +01:00
|
|
|
|
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
|
|
|
|
|
2025-12-28 10:40:28 +01: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
|
|
|
|
|
2025-12-28 10:40:28 +01: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
|
|
|
|
|
2025-12-28 10:40:28 +01: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
|
|
|
|
|
2025-12-28 10:40:28 +01: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:
|
2025-12-28 10:40:28 +01:00
|
|
|
|
-------------------
|
|
|
|
|
|
- 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
|
|
|
|
|
2025-12-28 10:40:28 +01:00
|
|
|
|
Umgebungsvariablen:
|
|
|
|
|
|
-------------------
|
|
|
|
|
|
QDRANT_URL, QDRANT_API_KEY, COLLECTION_PREFIX, VECTOR_DIM (Default: 768)
|
2025-09-04 16:52:12 +02:00
|
|
|
|
|
2025-12-28 10:40:28 +01: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
|
|
|
|
|
2025-12-11 13:57:19 +01: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():
|
2025-12-11 13:57:19 +01:00
|
|
|
|
# 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:
|
2025-12-11 13:57:19 +01:00
|
|
|
|
# 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]
|
|
|
|
|
|
|
2025-12-11 13:57:19 +01:00
|
|
|
|
# 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__":
|
2025-12-11 13:57:19 +01:00
|
|
|
|
main()
|