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
- 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>
224 lines
9.1 KiB
Markdown
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 |
|