Files
breakpilot-compliance/docs-src/development/rag-pipeline-lessons-learned.md
Benjamin Admin 643b26618f
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 31s
CI/CD / test-python-backend-compliance (push) Successful in 1m35s
CI/CD / test-python-document-crawler (push) Successful in 20s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Has been skipped
feat: Control Library UI, dedup migration, QA tooling, docs
- 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>
2026-03-21 11:56:08 +01:00

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_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)

{
  "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_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