← Zurück
5 Schichten · Anti-Halluzinations-Pipeline

Wie die Engine denkt.

Jeder Befund von Wahrspruch durchläuft fünf Schichten, an denen falsche Aussagen scheitern. Klicken Sie sich durch — Sie sehen für jede Schicht die konkrete Mechanik mit Beispiel.

Schicht 01

Geschlossene Welt.

Die Engine darf nur Klauseln referenzieren, die in unserem kuratierten Katalog stehen. Sie kann buchstäblich keine Quelle erfinden — sie kennt keine andere.

Für jedes Dokument-Chunk macht ein Embedding-Such-Pass im Katalog die Top-5 passendsten Klauseln. Der LLM bekommt nur diese fünf zu Gesicht.

📄 Dokument-Chunk: „Sensitivity 96.4 % (95% CI: 92.1–98.6)"
🔍 pgvector-Embedding-Search → Top-5 Klauseln
IVDR-ANNEX-I-9.1-a · Analytische Leistungsmerkmale
SQL -- Closed-world Retrieval SELECT r.stable_id, r.clause_path, GREATEST( similarity(r.requirement_text, $chunk), similarity(r.normalized_text, $chunk) ) AS score FROM requirements r WHERE r.catalog_version_id = $current_catalog AND r.clause_status = 'ACTIVE' ORDER BY score DESC LIMIT 5;
→ Diese 5 sind die EINZIGEN Optionen. LLM kann keine andere referenzieren.
Schicht 02

Erzwungenes Zitat.

Jeder Befund braucht ein wörtliches Zitat aus dem Dokument — mit Seitenzahl. Ohne Zitat: keine Speicherung. Das ist nicht im Prompt gewünscht, sondern auf API-Ebene erzwungen.

Der LLM ruft eine Tool-Function record_finding, die exact_quote und page_number als required fields hat. Ohne diese Felder verweigert die API den Call.

🛠 Tool-Use erzwingt strukturierte Output
🚫 Schema-Validation: missing field → reject
TypeScript const RECORD_FINDING_TOOL = { name: 'record_finding', input_schema: { type: 'object', required: [ 'requirement_id', 'exact_quote', // ← Pflicht 'page_number', // ← Pflicht 'confidence', 'finding_text' ], properties: { exact_quote: { type: 'string', minLength: 10, description: 'Wörtlicher Auszug' } } } };
Schicht 03

Exakter Textabgleich.

Ein Datenbank-Trigger prüft pro Insert, ob das zitierte Wort im Quelldokument wirklich steht. Wenn nicht: INSERT wird rejected.

Das ist nicht im App-Code, sondern hart auf SQL-Ebene — wenn jemand den App-Code patcht, der Check bleibt. Tamper-resistant by design.

🔒 PL/pgSQL-Trigger, BEFORE INSERT
⚖️ Whitespace-Normalisierung, dann POSITION()-Check
❌ Nicht gefunden → RAISE EXCEPTION
PL/pgSQL · 01_schema.sql CREATE OR REPLACE FUNCTION verify_quote_in_document( p_document_id UUID, p_page_number INTEGER, p_quote TEXT ) RETURNS BOOLEAN AS $$ DECLARE v_page_text TEXT; BEGIN SELECT raw_text INTO v_page_text FROM document_pages WHERE document_id = p_document_id AND page_number = p_page_number; -- Whitespace normalisieren v_page_text := REGEXP_REPLACE(v_page_text, '\s+', ' ', 'g'); p_quote := REGEXP_REPLACE(p_quote, '\s+', ' ', 'g'); RETURN POSITION(p_quote IN v_page_text) > 0; END; $$ LANGUAGE plpgsql;
Schicht 04

Selbstkritik.

Ein zweites Modell bekommt jeden Befund vorgelegt mit der Frage: „Ist das wirklich ein Befund? Oder hat der erste Pass überreagiert?"

Es kennt das Zitat, die Klausel, und die Severity. Es kann ablehnen, herabstufen, oder als unsicher markieren. Was nicht standhält, fliegt raus oder wird in den „Unsicher"-Bucket geroutet.

🧠 Critic-Modell prüft Generator-Befund
⬇️ Severity kann herabgestuft werden
🗑 Befund kann verworfen werden
Critic-Prompt (gekürzt) /* Du bist ein kritischer Reviewer. */ /* Prüfe diesen Befund streng: */ Befund: Klausel: IVDR-ANNEX-I-9.1-a Zitat: "Sensitivity 96.4 %" Severity: MAJOR Text: "Konfidenzintervall fehlt" Aufgaben: 1. Stützt das Zitat die Behauptung? 2. Ist die Severity angemessen? 3. Was ist deine Konfidenz (01)?
→ Critic-Antwort: severity=MINOR, conf=0.78 (CI wird in Tabelle direkt darunter genannt)
Schicht 05

Konfidenz-Routing.

Befunde mit Konfidenz unter 0,70 landen nicht im Report — sondern im „Zweifelsfall"-Bucket für manuelle Sichtung.

Wir liefern nichts aus, wofür wir nicht stehen können. Lieber 12 solide Befunde als 18 mit Rauschen.

📊 Threshold: CONFIDENCE_THRESHOLD = 0.70
✅ ≥ 0,70 → Report (Major/Minor/Hinweis)
⚠️ < 0,70 → „Unsicher" (Reg-Affairs-Review)
scan-engine.ts export const CONFIDENCE_THRESHOLD = 0.70; function routeFinding(f: FindingDraft) { if (f.confidence < CONFIDENCE_THRESHOLD) { return { ...f, severity: 'UNCERTAIN', bucket: 'human_review' }; } return { ...f, bucket: 'report' }; }
→ Bericht: 12 solide Befunde → Review-Bucket: 6 unsichere → Reg-Affairs entscheidet manuell.

Diese 5 Schichten sind kein Marketing.

Sie sind Code, SQL-Trigger, Datenbankschema und API-Contracts. Wenn Sie wollen, schicken wir Ihnen den Audit-Hash eines Test-Scans — und Sie können ihn auf /verify.html öffentlich überprüfen.

→ Beta-Zugang sichern