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

224 lines
9.1 KiB
Markdown

# 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 |