# RAG Pipeline: Lessons Learned & Hardening ## Übersicht Dieses Dokument beschreibt die Erkenntnisse aus dem Aufbau der RAG-Pipeline und die daraus abgeleiteten Maßnahmen zur Härtung. Es dient als Referenz für zukünftige Ingestion-Runs und Pipeline-Erweiterungen. ## Architektur: Wann brauchen wir RAG vs. Direct PDF? ### RAG ist nötig für: | Use Case | Warum RAG? | |---|---| | **Compliance Advisor (Chat)** | Semantische Suche über 38+ Dokumente in Echtzeit | | **Cross-Regulation Mapping** | "Zeige alle Anforderungen zu Verschlüsselung" über alle Quellen | | **Customer Scope-Filtering** | Nur Chunks aus relevanten Regulations für den Kunden | | **Inkrementelle Updates** | Neues Dokument → nur neue Chunks verarbeiten | ### RAG ist NICHT nötig für: | Use Case | Besser: Direct PDF | |---|---| | **Control-Generierung (Batch)** | PDF → PyMuPDF → Strukturparser → Artikel-Index → API | | **PDF-QA/Verifizierung** | Substring-Match direkt im PDF (schneller, exakter) | | **Artikel/§-Extraktion** | Regex-basierte Extraktion aus PDF-Text | ### Hybrid-Ansatz (Empfehlung) ``` Control-Generierung: PDF → Strukturparser → Artikel-Index → Anthropic API (KEIN RAG nötig, direkt aus PDF) Runtime-Betrieb: Qdrant-RAG für semantische Suche, Chat, Scope-Analyse (RAG mit angereicherten Chunks + Struktur-Metadaten) ``` ## Fehler und Root Causes ### 1. Doppelte Ingestion = Doppelte Controls **Problem:** Gleiche PDFs unter verschiedenen Namen ingestiert (z.B. "Maschinenverordnung" und "Verordnung (EU) 2023/1230") → unterschiedliche Chunks (anderes Chunking) → anderer Hash → doppelt verarbeitet → doppelte Controls. **Root Cause:** - `regulation_name` aus Chunk-Metadaten statt aus kanonischer Quelle - UNIQUE-Constraint nur `(chunk_hash, collection, document_version)` — nicht global - Kein Check ob `regulation_code` bereits in einer Collection existiert **Fix (implementiert):** - `REGULATION_LICENSE_MAP` enthält jetzt kanonische `name`-Werte die den DB-Einträgen entsprechen - `source_citation.source` wird aus `REGULATION_LICENSE_MAP.name` genommen, NICHT aus `chunk.regulation_name` - Phase 5 Cleanup: 3.301 Duplikate hart gelöscht **Fix (noch offen):** - Chunk-Hash UNIQUE Constraint global machen: `(chunk_hash, document_version)` statt `(chunk_hash, collection, document_version)` - Vor Ingestion: Check ob `regulation_code` bereits in einer Collection existiert ### 2. Chunks verlieren Strukturinformation **Problem:** Chunks werden mitten im Absatz abgeschnitten. § und Artikelnummern fehlen in den Chunk-Metadaten. Kontext des Kapitels/Abschnitts geht verloren. **Root Cause:** - `chunk_strategy=recursive` mit `chunk_size=512, chunk_overlap=50` — zu kleine Chunks - Chunking beachtet keine Dokumentstruktur (Artikel-/Paragraphengrenzen) - Keine Einleitung/Kapitelkontext als Prefix **Empfehlung für Re-Ingestion:** - **Strukturiertes Chunking:** Chunks an Artikel-/Paragraphengrenzen schneiden - **Kontext-Prefix:** Kapiteleinleitung und übergeordnete Struktur mitliefern - **Metadaten anreichern:** `article`, `paragraph`, `article_type`, `section_hierarchy` - **Größere Chunks:** Mindestens 1024 Tokens, besser volle Artikel/Paragraphen ### 3. Cross-Collection-Duplikate **Problem:** `nist_csf_2_0` in `bp_compliance_ce` (67 Chunks) UND `bp_compliance_datenschutz` (162 Chunks). EU-Verordnungen sowohl in `bp_compliance_ce` als auch `bp_compliance_gesetze`. **Root Cause:** Keine Collection-Zuordnungsregeln. Manuelle Zuweisung bei Ingestion. **Fix:** `cleanup-qdrant-duplicates.py` Script bereinigt Cross-Collection-Duplikate. **Empfehlung:** Klare Collection-Zuordnungsregeln: - `bp_compliance_ce` = EU-Verordnungen + internationale Standards - `bp_compliance_gesetze` = Deutsche + österreichische Gesetze (NUR nationale Gesetze) - `bp_compliance_datenschutz` = EDPB/WP29 Leitlinien + Privacy Frameworks ### 4. OWASP Multilingual Controls **Problem:** 324 OWASP Top 10 Controls in ZH, AR, ID, FR, ES, PT — Übersetzungen derselben 10 Kategorien. Kein Mehrwert, aber 324 doppelte Controls generiert. **Root Cause:** Multilingual PDFs/GitHub-Quellen ohne Spracherkennung ingestiert. **Fix:** 324 als `duplicate` markiert und gelöscht. **Empfehlung:** Bei Ingestion Spracherkennung + Deduplizierung. Nur DE + EN behalten. ### 5. Fehlende Artikel/Paragraph-Extraktion **Problem:** Chunks haben `article` und `paragraph` oft leer oder falsch. Die LLM-basierte Extraktion bei der Control-Generierung ist unzuverlässig. **Root Cause:** Ingestion-Pipeline extrahiert keine Strukturinformation aus dem PDF. **Fix (implementiert):** PDF-QA-Pipeline (`pdf_qa_all.py`) matched `source_original_text` gegen Original-PDFs und extrahiert korrekte Artikel/Paragraphen — 86% Match-Rate. **Empfehlung:** Bei Re-Ingestion direkt in den Chunk-Metadaten speichern. ### 6. Job-Tracking nicht persistent **Problem:** Generation-Jobs laufen als Background-Tasks. Kein Logging, welche Chunks verarbeitet, Status nur über API abfragbar. Bei API-Timeout oder Restart geht der Fortschritt verloren. **Root Cause:** `asyncio.create_task()` hat keinen Recovery-Mechanismus. **Fix (teilweise):** `canonical_generation_jobs` Tabelle trackt Jobs. `canonical_processed_chunks` markiert verarbeitete Chunks. **Empfehlung:** - Job-Log in DB persistieren (nicht nur stdout) - Fortschritt in `canonical_generation_jobs.progress` als JSONB speichern - Chunk-Level-Status: verarbeitet / übersprungen / Fehler - Recovery-Fähigkeit: Job kann von letztem Checkpoint fortgesetzt werden ## Empfohlene Metadaten für Re-Ingestion ### Chunk-Level Metadaten (Qdrant Payload) ```json { "chunk_text": "...", "regulation_code": "eu_2016_679", "regulation_name_de": "DSGVO (EU) 2016/679", "regulation_name_en": "GDPR (EU) 2016/679", "article": "25", "article_title": "Datenschutz durch Technikgestaltung und datenschutzfreundliche Voreinstellungen", "article_type": "article", "paragraph": "1", "section_hierarchy": ["Kapitel IV", "Abschnitt 2", "Artikel 25"], "chapter_context": "Kapitel IV — Verantwortlicher und Auftragsverarbeiter", "pages": [45, 46], "effective_date": "2018-05-25", "publication_date": "2016-04-27", "document_version": "2016-04-27", "source_language": "de", "source_url": "https://eur-lex.europa.eu/...", "celex": "32016R0679", "license": "EU_LAW", "license_rule": 1, "source_type": "law", "category": "datenschutz", "chunk_position": 42, "total_chunks": 423 } ``` ### Dokument-Level Metadaten (Corpus Version) ```json { "regulation_code": "eu_2016_679", "canonical_name_de": "DSGVO (EU) 2016/679", "canonical_name_en": "GDPR (EU) 2016/679", "document_type": "eu_regulation", "effective_date": "2018-05-25", "publication_date": "2016-04-27", "supersedes": null, "superseded_by": null, "source_pdf": "gdpr_regulation_eu_2016_679.pdf", "source_pdf_sha256": "abc123...", "total_articles": 99, "total_recitals": 173, "total_annexes": 0, "ingestion_date": "2026-03-20", "ingestion_version": "v2" } ``` ## Pipeline-Härtung Checkliste ### Vor Ingestion - [ ] Prüfen ob `regulation_code` bereits in einer Collection existiert - [ ] PDF-SHA256 gegen bekannte PDFs prüfen (Duplikat-Erkennung) - [ ] `regulation_name` aus `REGULATION_LICENSE_MAP` verwenden, NICHT aus Chunk-Metadaten - [ ] Spracherkennung: Nur DE + EN ingestieren - [ ] Dokument-Metadaten (effective_date, publication_date) recherchieren ### Während Ingestion - [ ] Strukturiertes Chunking an Artikel-/Paragraphengrenzen - [ ] Kontext-Prefix mit Kapiteleinleitung - [ ] Chunk-Metadaten anreichern (article, paragraph, article_type, section_hierarchy) - [ ] Fortschritt in DB loggen ### Nach Ingestion - [ ] Chunk-Count pro `regulation_code` prüfen (Sanity Check) - [ ] PDF-QA gegen Original-PDF laufen lassen - [ ] Cross-Collection-Duplikat-Check - [ ] Corpus-Version in DB eintragen ### Control-Generierung - [ ] `source_citation.source` aus `REGULATION_LICENSE_MAP.name`, NICHT aus Chunk-Metadaten - [ ] Harmonisierung: Threshold 0.85 für Duplikate innerhalb gleicher `regulation_code` - [ ] Cross-Regulation-Harmonisierung bei ähnlichen Themen (z.B. DSGVO Art. 25 ↔ NIS2 Art. 21) - [ ] Job-Fortschritt persistent in DB speichern ## Workflow: Mac Mini → Production Sync ``` 1. Mac Mini: PDF → Qdrant (lokal, http://macmini:6333) 2. Mac Mini: Control-Generierung → PostgreSQL (shared, 46.225.100.82:54321) 3. QA: PDF-Match, Dedup, Source-Normalisierung 4. Qdrant Migration: macmini:6333 → qdrant-dev.breakpilot.ai (scripts/migrate-qdrant.py) 5. Deploy: git push gitea → Coolify Build + Deploy ``` **WICHTIG:** PostgreSQL ist SHARED — Änderungen auf Mac Mini sind sofort in Production sichtbar. Qdrant hat getrennte Instanzen (lokal + production) und muss manuell synchronisiert werden. ## Scripts | Script | Beschreibung | |---|---| | `scripts/ingest-phase-h.sh` | Haupt-Ingestion: 38 Dokumente → Qdrant | | `scripts/cleanup-qdrant-duplicates.py` | Qdrant Duplikat-Cleanup (8 Schritte) | | `scripts/migrate-qdrant.py` | Qdrant Migration: lokal → production | | `scripts/qa/phase5_normalize_and_cleanup.py` | DB Normalisierung + Hard Delete | | `scripts/qa/pdf_qa_all.py` | PDF-Match QA |