- Control Library: parent control display, ObligationTypeBadge, GenerationStrategyBadge variants, evidence string fallback - API: expose parent_control_uuid/id/title in canonical controls - Fix: DSFA SQLAlchemy 2.0 Row._mapping compatibility - Migration 074: control_parent_links + control_dedup_reviews tables - QA scripts: benchmark, gap analysis, OSCAL import, OWASP cleanup, phase5 normalize, phase74 gap fill, sync_db, run_job - Docs: dedup engine, RAG benchmark, lessons learned, pipeline docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.1 KiB
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_nameaus Chunk-Metadaten statt aus kanonischer Quelle- UNIQUE-Constraint nur
(chunk_hash, collection, document_version)— nicht global - Kein Check ob
regulation_codebereits in einer Collection existiert
Fix (implementiert):
REGULATION_LICENSE_MAPenthält jetzt kanonischename-Werte die den DB-Einträgen entsprechensource_citation.sourcewird ausREGULATION_LICENSE_MAP.namegenommen, NICHT auschunk.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_codebereits 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=recursivemitchunk_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 Standardsbp_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.progressals 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)
{
"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)
{
"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_codebereits in einer Collection existiert - PDF-SHA256 gegen bekannte PDFs prüfen (Duplikat-Erkennung)
regulation_nameausREGULATION_LICENSE_MAPverwenden, 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_codeprüfen (Sanity Check) - PDF-QA gegen Original-PDF laufen lassen
- Cross-Collection-Duplikat-Check
- Corpus-Version in DB eintragen
Control-Generierung
source_citation.sourceausREGULATION_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 |