ai-sdk (legal_rag_client/scroll/types) liest die gepinnten Spec-Felder
article_label/regulation_code/article/paragraph/sub/citation_style/is_recital
mit Fallback auf alt-ingestierte Chunks (regulation_id, section); neuer getBool-Helfer.
Advisor + Drafting-Engine bilden die Quellenzeile primaer aus article_label
("BDSG § 38 Abs. 1"), sonst aus den strukturierten Feldern. 17 Tests gruen, tsc sauber.
Vertrag: docs-src/development/rag_reingest_spec.md (§2/§7). Deploy an den Re-Ingest
gekoppelt — neue Felder sind bis dahin leer (graceful Fallback).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9.1 KiB
RAG Re-Ingest-Spezifikation — Zitierfähige Chunks (Cross-Session-Vertrag)
Status: v1 — Vertrag zwischen core-Session (Ingestion/Pipeline) und compliance-Session (Consumer: ai-sdk/Advisor). Datum: 2026-06-19 Ziel: Chunks neu ingestieren mit sauberem Text + vollständigen Legal-Metadaten, damit Controls/Findings zitierfähig werden ("BDSG § 38 Abs. 1", "Art. 13 Abs. 1 lit. c DSGVO") — ohne die teuren Pass 0a/0b/Dedup.
0. Kernentscheidung (Frage 1): Re-Link statt Regenerieren
Controls hängen an control_uuid, nicht an Chunks (canonical_controls hat keine chunk_id/chunk_hash-Spalte; ebenso atom_classification, control_classification, doc_check_controls). Ein Chunk-Re-Ingest bricht keine Control. Das einzige stale-werdende Artefakt ist das Idempotenz-Ledger compliance.canonical_processed_chunks (Key sha256(text)+collection+document_version) — relevant nur für zukünftige Generierung.
→ Pass 0a/0b/Dedup entfallen. Stattdessen werden Controls per Textabgleich an die neuen Chunks re-gelinkt (Zitat-Anreicherung). Reichweite:
- 7 % der 315.914 Controls haben
source_original_text(Re-Link-Anker) → direkter Abgleich. - ~93 % Atome erben das Zitat über
parent_control_uuid. - self-written brauchen kein Chunk-Zitat (eigene Bibliothek).
- unmatched Reste → billiges per-Control-LLM-Zitat (Tier-3), keine Regenerierung.
1. Ingestion-Anforderungen (Frage 2)
chunk_strategy="legal"EXPLIZIT setzen. (Korrektur zur Historie:recursivealiased inzwischen aufchunk_text_legal,embedding-service/main.py:1079/1093, live-verifiziert — Upload mitrecursiveliefertesection:"§ 38",paragraph:"(1)",paragraph_num:1. Trotzdemlegalexplizit, nicht aufs Alias verlassen.)- Deterministische Chunk-ID (heute random) =
sha1(regulation_code|article|paragraph|chunk_index|document_version)→ stabiler Re-Link + Alt/Neu-Koexistenz. chunk_hashIN die Payload schreiben (heute nur im PG-Ledger) =sha256(normalisierter chunk_text).- Echte
document_version(heute hardcoded"1.0") → Re-Chunk kollidiert sonst im Ledger. - Upload-before-delete je Collection (alte Chunks erst nach Verify löschen).
2. PAYLOAD-FELD-VERTRAG (verbindlich — Consumer liest GENAU diese Namen)
Die ai-sdk/Advisor liest die consumer-facing Felder. Ingestion füllt alle.
| Feld | Typ | Consumer | Beschreibung / Beispiel |
|---|---|---|---|
article_label |
string | JA (Anzeige) | Fertig formatiert, direkt druckbar. "BDSG § 38 Abs. 1" · "Art. 13 Abs. 1 lit. c DSGVO". Ingestion formatiert (kennt §- vs Art.-Stil). |
regulation_code |
string | JA | Kurzcode, UPPERCASE: BDSG,DSGVO,TTDSG,DDG,CRA,NIS2 |
citation_style |
enum | JA | paragraph (§-Gesetze) | article (EU-Verordnungen) — steuert §/Art.-Rendering, falls Consumer selbst formatiert |
article |
string | JA | bare Nummer: "38" bzw. "13" (egal ob § oder Art.) |
paragraph |
string | JA | bare Absatz: "1" (nicht "(1)") |
sub |
string|null | JA | feinste Granularität: "lit. c" · "Satz 2" · "Nr. 3" |
is_recital |
bool | JA | Erwägungsgrund vs operativer Artikel |
regulation_name |
string | optional | Volltext: "Bundesdatenschutzgesetz" |
page |
int|null | optional | Seite (PDF-Quellen) |
chunk_text |
string | JA | sauberer Text (keine Soft-Hyphens/OCR-Reste) |
section_header |
string | optional | Kontext-Überschrift, separat (nicht inline im chunk_text) |
chunk_id |
string | — | deterministisch (s. §1.2) |
chunk_hash |
string | — | sha256(normalisierter Text) |
document_id, document_version, chunk_index |
— | — | Identität/Versionierung |
doc_type, use_case[], source_type, license, bundesland, year |
— | Scope | Routing/Scope (source_type: gesetz/leitlinie/urteil) |
Verbindlich: article_label ist der bevorzugte Anzeige-Pfad (Ingestion ownt die Zitat-Formatierung, weil sie die Regulierung kennt). Die strukturierten Teile (regulation_code/article/paragraph/sub) sind zusätzlich für Filtern/Gruppieren da.
3. Normalisierung alt → neu (core-seitiger Transform)
Der legal-Chunker emittiert heute uneinheitlich; Ingestion normalisiert:
| heute (raw payload) | → neu |
|---|---|
section = "§ 38" / "Artikel 13" |
citation_style (§→paragraph, Art/Artikel→article) + article="38"/"13" |
section_title |
section_header |
paragraph="(1)" / paragraph_num=1 |
paragraph="1" |
(lit./Satz/Nr. aus _PARAGRAPH_RE) |
sub (best-effort) |
regulation_id/regulation_short (extra_metadata) |
regulation_code (UPPERCASE) |
| — | article_label (formatiert aus regulation_code+style+article+paragraph+sub) |
article_label-Formatierung:
paragraph-Stil:"{regulation_code} § {article} Abs. {paragraph}"(+" {sub}")article-Stil:"Art. {article} Abs. {paragraph} {sub} {regulation_code}"
4. Control-Re-Link (core)
Erweiterung von control-pipeline/services/citation_backfill.py (heute 3-Tier, Tier-1 = sha256(source_original_text) → Chunk-Hash-Index): Tier-1 bricht beim Re-Chunk (neuer Text → neuer Hash) → Fuzzy/Embedding-Alignment ergänzen (source_original_text ↔ neue Chunk-Texte, Substring + Cosine). Präzedenz: PDF-QA-Matcher (~52 % Trefferquote). Füllt canonical_controls.source_citation = {regulation_code, article, paragraph, sub, page, source_type, license, url}. Atome erben über parent_control_uuid. doc_check_controls re-derivieren danach automatisch zitierfähig (derive_doc_check_controls.py liest source_citation->>'article'/'source').
Beim künftigen Generieren IMMER source_original_text setzen (warum heute nur 7 % re-linkbar sind).
5. Pipeline-Reihenfolge
- Re-Ingest je Collection:
strategy="legal", deterministische IDs, neuedocument_version, normalisierte Payload +chunk_hash. - Verify: Zähler alt/neu + Stichprobe
article/paragraph/article_labelbefüllt. - Re-Link (
citation_backfillerweitert) →source_citation; Atome erben. - Reste → Tier-3-LLM-Zitat.
- Alte Chunks löschen.
- (optional, nur für künftige Generierung) Ledger
canonical_processed_chunksneu aufbauen.
6. Arbeitsteilung + Akzeptanz
core-Session: Ingest-Spec umsetzen, citation_backfill Hash→Fuzzy→Embedding, Payload-Normalisierung (§3), deterministische IDs/chunk_hash/document_version, AGG-Lücke.
compliance-Session (Consumer): ai-sdk legal_rag_client.go + Advisor/Drafting auf die §2-Feldnamen ummappen (kein Deploy vor Pin — jetzt gepinnt), Prod-Qdrant-Verify, 6-Fragen-Re-Test (sind §38 BDSG / AGG / CRA Art. 14 grounded zitiert?).
Akzeptanzkriterium: Advisor rendert für eine Beispielfrage eine echte Fundstelle aus article_label (z. B. "BDSG § 38 Abs. 1"), nicht nur "Quelle: BDSG".
7. Consumer-Detailabschnitt (compliance-Session — implementiert 2026-06-19)
Status: Code steht + getestet (17 Tests grün, tsc sauber). Deploy gekoppelt an den Re-Ingest (liest bis dahin leere Felder → graceful, Advisor zeigt wie heute "Quelle: BDSG").
7.1 ai-sdk: Payload → Response (internal/ucca/legal_rag_client.go searchInternal, Struct legal_rag_types.go)
| Response-Feld (JSON) | gelesen aus Payload | Fallback (alt-Korpus) |
|---|---|---|
article_label |
article_label |
— (leer → Consumer baut selbst) |
regulation_code |
regulation_code |
→ regulation_id |
article |
article |
→ section ("§ 38") |
paragraph |
paragraph |
— |
sub |
sub |
— |
citation_style |
citation_style |
— |
is_recital |
is_recital (bool) |
— |
text |
chunk_text |
— |
regulation_name |
regulation_name_de |
— |
regulation_short |
regulation_short |
— |
category,pages,source_url,score |
category,pages,source,(score) |
— |
→ /sdk/v1/rag/search liefert diese Felder snake_case. Neuer Bool-Helfer getBool ergänzt.
7.2 Advisor + Drafting: Fundstellen-Format
Beide Konsumenten (admin-compliance/lib/sdk/agents/advisor-rag.ts, .../drafting-engine/rag-query.ts) bilden die Quellenzeile so:
source = article_label?.trim() // bevorzugt: druckbar aus Ingestion
|| [regulation_short|regulation_name|regulation_code, article, paragraph, sub]
.filter(Boolean).join(' ') // Fallback: strukturiert zusammensetzen
|| 'Unbekannt'
Ausgabe je Treffer: [Quelle N: {source}]\n{text}
→ Der Advisor druckt article_label direkt (kein §-vs-Art-Ableiten); citation_style nur nötig, falls wir später selbst formatieren. Erfüllt das Akzeptanzkriterium (§6): "BDSG § 38 Abs. 1" statt nur "Quelle: BDSG".
7.3 Deploy-Kopplung
Code ist additiv/safe (neue Felder leer bis Re-Ingest). Kein Solo-Deploy — geht mit dem Re-Ingest-Go-Live live, danach 6-Fragen-Re-Test auf prod (§38/AGG/CRA Art. 14 grounded zitiert?).
8. Offen (core)
- AGG-Lücke: Quelle (§ 15 Abs. 4 — Bewerberdaten-Frist) zu spezifizieren + ingestieren.
- Soft-Hyphen/OCR-Normalisierung des
chunk_text— Regelsatz definieren.