Alfred  ·  Extraction Pipeline

Extraction Pipeline

Unstrukturierte Dokumente zu validierten, strukturierten Daten — so wenig LLM wie nötig, so viel Determinismus wie möglich.

Pipeline — Phase 1 (aktiv, 9/10 Stadtwerke-Test)

Die aktuelle Pipeline verarbeitet eingescannte Dokumente in sieben Stufen. Kernänderung gegenüber dem ursprünglichen Ansatz: KVP liefert räumliche Paare als strukturierten Kontext für das LLM. Das LLM bekommt keine Format-Restriktionen — nur Labels, Enums und semantische Hints. Alle Formatprüfungen passieren deterministisch danach.

1

DLA + OCR (Surya /analyze) Deterministisch

Layout-Erkennung + OCR in einem Schritt. Liefert Zeilen mit Bounding Boxes, gruppiert in Layout-Regionen (Sections).

2

OCR-Qualitätsprüfung Deterministisch

Proxy-Score aus Zeichenqualität, Worterkennung, Strukturerkennung und Längenverhältnis. Kein ML — rein heuristisch.

≥ 0.4 → weiter < 0.4 → direkt Human Input
3

KVP — Räumliche Paare Deterministisch Kernänderung

Findet räumlich benachbarte Textblöcke per asymmetrischer Distanz (rechts/unten bevorzugt). Keine Label/Value-Klassifikation — das Zuordnen übernimmt das LLM. Mutual-Nearest-Neighbor-Pairing (Pass 1) + distanzsortierter Fallback (Pass 2). Sektionsübergreifend, ~0ms.

4

LLM-Extraktion (Ollama) LLM

Prompt enthält param_hints (semantische Feldbeschreibungen aus YAML), Enum-Werte und gefilterte KVP-Paare als räumlichen Kontext („ORT steht neben Stuttgart“). Bewusst keine Format/Pattern/MaxLength-Hints — die biased das LLM zum Erfinden passender Werte statt zum Lesen.

5

Deterministische Validierung Deterministisch

Prüft jeden extrahierten Wert gegen die ParamMeta-Restriktionen. Auto-Korrekturen: trim, strip, Datumsformat, Dezimalbereinigung. Ergebnis pro Feld: ok / corrected / failed / missing.

6

Bestätigungsdialog Human

Quelldokument + extrahierte Werte mit Ampel-Status. Fehlgeschlagene Felder hervorgehoben. User bestätigt, korrigiert oder bricht ab.

7

Form Fill + Self-Repair Deterministisch LLM

Formulardaten werden eingetragen. Bei Server-seitigen Validierungsfehlern greift der bestehende Self-Repair-Mechanismus (LLM-Korrektur → ggf. Human Fallback).


Stufe 2 — OCR-Qualitätsprüfung

Surya liefert keinen eigenen Konfidenz-Score. Stattdessen wird ein Proxy-Score aus dem Text selbst berechnet — rein deterministisch, kein ML.

Check Gewichtung Was wird gemessen
Zeichenqualität 30% Anteil druckbarer Zeichen vs. Steuerzeichen, Box-Symbole, Null-Bytes
Worterkennung 30% Anteil erkannter Wörter (einfaches Wörterbuch / Sprachcheck)
Strukturerkennung 20% Enthält der Text erkennbare Muster: Datumsformate, PLZ-artige Zahlen, Namens-Patterns
Längenverhältnis 20% Ist die Textlänge plausibel für die Dokumentgröße, oder verdächtig kurz/leer
Schwellwert bewusst niedrig (0.4). Lieber einmal zu viel extrahieren und im Validierungsgate fangen, als zu früh aufgeben. Nur eindeutig unbrauchbarer OCR-Output wird abgewiesen.

Stufe 3 — KVP Räumliche Zuordnung

KVP findet räumlich benachbarte Textblöcke — ohne zu wissen, was Labels und was Werte sind. Die Zuordnung zu YAML-Feldern ist nicht KVPs Aufgabe. Es liefert nur: „diese zwei Blöcke stehen nebeneinander.“

Schritt Was passiert
Flatten Alle OCR-Zeilen über alle Sektionen sammeln. Noise-Filter: leere Zeilen und Zeilen >80 Zeichen raus.
Nearest Neighbor Für jede Zeile den nächsten Nachbarn per asymmetrischer Distanz finden. Rechts-von und unterhalb werden bevorzugt (Formular-Pattern).
Pass 1 — Mutual Wenn A→B und B→A → Paar mit hoher Konfidenz (gegenseitig nächste Nachbarn).
Pass 2 — Fallback Verbleibende Zeilen, sortiert nach Distanz. A→B aber B→C → trotzdem Paar, niedrigere Konfidenz. Sortierung verhindert, dass entfernte Zeilen nahe Nachbarn „stehlen“.
Filter Paare wo beide Seiten ALL-CAPS ohne Ziffern sind (zwei Headings/Labels) werden vor der LLM-Übergabe entfernt.
Keine Label/Value-Klassifikation. Frühere Versuche mit ALL-CAPS-Heuristik („Großbuchstaben = Label“) scheiterten bei handschriftlichen deutschen Formularen. KVP liefert nur räumliche Paare — die semantische Zuordnung übernimmt das LLM (Phase 1) bzw. GLiNER (Phase 2).

Stufe 4 — LLM-Extraktion

Das LLM bekommt drei Informationsquellen: param_hints (semantische Feldbeschreibungen ohne Beispielwerte), Enum-Listen (erlaubte Werte) und KVP-Paare als räumlichen Kontext.

Bewusst keine Format-Restriktionen im Prompt. Pattern, maxLength und Format-Hinweise biased das LLM dazu, Werte zu erfinden die zum Pattern passen, statt zu lesen was im Dokument steht. Alle Formatprüfungen passieren deterministisch in Stufe 5. Ebenso: param_hints dürfen keine Beispielwerte enthalten („z.B. 70199“ führte dazu, dass das LLM 70199 zurückgab).

Stufe 5 — Deterministische Validierung

Prüft jeden extrahierten Wert gegen die Restriktionen aus ParamMeta. Keine Prüfung ohne Deklaration — wenn ein Feld kein pattern hat, wird es nicht geprüft. Das System macht keine Annahmen.

Check ParamMeta-Feld Aktion bei Fehler
Regex-Match pattern Auto-Fix versuchen (strip non-matching chars), sonst failed
Maximale Länge maxLength Abschneiden + corrected
Erlaubte Werte enum Fuzzy-Match (Levenshtein) gegen enum-Liste, sonst failed
Format format Datum: parse + reformat zu erwartetem Format, sonst failed
Pflichtfeld leer required Status missing

Keine fachliche Logik. “Zählernummer existiert im System” oder “Adresse ist im Versorgungsgebiet” sind Server-Validierungen. Die fängt der bestehende Self-Repair-Mechanismus nach dem Form Fill.


Woher kommen die Restriktionen?

Die ParamMeta-Felder (pattern, maxLength, enum, format) werden aus verschiedenen Quellen befüllt — manuell heute, automatisch morgen.

Szenario-YAML

Jetzt — manuell gepflegt

Der Szenario-Autor trägt bekannte Restriktionen direkt in die YAML-Datei ein. Jedes Szenario definiert seine eigenen Regeln.

Extension Validation Probe

Bald — automatisch entdeckt

Der Run-Typ “Eingabeprüfung” testet Formularfelder gezielt mit ungültigen Werten und leitet daraus technische Restriktionen ab (Regex, Zeichenklassen, Längen).

OpenAPI-Spec (API Actor)

Zukünftig

Wenn der API Actor gegen eine dokumentierte API arbeitet, enthält die OpenAPI-Spec bereits Pattern, Enum und Format-Definitionen für jeden Parameter.

Wichtige Grenze: Nur technische Restriktionen sind automatisch entdeckbar — Zeichenklassen, Längen, erlaubte Werte. Fachspezifische Logik (Adresse muss im Versorgungsgebiet liegen, Zählernummer muss im System existieren) ist nicht durch Probieren ermittelbar und wird weiterhin reaktiv vom Self-Repair behandelt.


Stufe 6 — Bestätigungsdialog

Der neue Bestätigungsdialog ersetzt die bisherige einfache Wertabfrage. Er zeigt das Quelldokument neben den extrahierten Werten, mit Ampel-Status pro Feld.

Quelldokument

Anmeldung Stromversorgung

Name: Maria Huber
Straße: Hauptstr. 12
PLZ / Ort: 80331 München
Zählernr.: 48O7123
Einzug: 1. April 2025
Tarif: ÖkoStrom

Extrahierte Daten

VornameMaria
NachnameHuber
StraßeHauptstr.
Hausnr.12
PLZ80331
OrtMünchen
Zählernr.4807123 (war: 48O7123)
Einzug2025-04-01 (war: 1. April 2025)
TarifOekoStrom

Grün = Wert ok. Gelb = automatisch korrigiert (Original sichtbar). Rot = ungültig oder fehlend, User muss eingeben. Der OCR-Qualitätsscore wird als Info-Badge angezeigt.


Phase 2 — GLiNER mit KVP-Paaren

GLiNER übernimmt das semantische Mapping für eindeutige Fälle (~50ms, deterministisch). Das LLM wird nur noch für Felder aufgerufen, die GLiNER nicht zuordnen kann. Entscheidend: GLiNER bekommt strukturierte KVP-Paare als Input, nicht rohen OCR-Text.

3

KVP — Räumliche Paare Deterministisch

Wie Phase 1. Output: räumliche Paare mit Konfidenz.

4a

GLiNER — Semantisches Mapping Deterministisch Neu

Input: strukturierter Text aus KVP-Paaren („NACHNAME neben Schneider“) + YAML-Feldlabels als Entity-Liste. Muss nur noch semantisch zuordnen, nicht mehr Labels von Werten unterscheiden. ~50ms.

Match → direkt zu Validierung Kein Match → LLM-Fallback
4b

LLM-Fallback LLM

Nur für Felder die GLiNER nicht zuordnen konnte. Selber Prompt wie Phase 1, aber weniger Felder. ~1500ms.

Eskalationskette:

KVP

„was steht nebeneinander?“
~0ms, deterministisch

GLiNER

„welches Feld passt?“
~50ms, deterministisch

LLM

„was bedeutet das im Kontext?“
~1500ms, probabilistisch

Mensch

„bitte prüfen“

Der LLM ist die letzte Instanz vor dem Menschen — nicht die zweite. Wenn GLiNER nicht verfügbar ist, fällt die Pipeline auf Phase 1 zurück (LLM übernimmt alles).


Erkenntnisse aus der Implementierung

Was NICHT funktioniert hat:

Ansatz Problem
GLiNER auf rohem OCR-Text Kann Labels nicht von Werten unterscheiden. Gibt Labels als Werte zurück, klebt Zeilen zusammen. 4/10 Felder.
ALL-CAPS-Heuristik für Label-Erkennung Bei handschriftlichen deutschen Formularen sind auch Werte in Großbuchstaben. Falsche Paare mit 0.95 Konfidenz.
GLiNER-Ergebnisse als LLM-Kontext Falsche GLiNER-Zuordnungen vergiften den LLM-Prompt. 2/10 statt 5/10 ohne GLiNER.
Format-Hints im Extraktions-Prompt LLM erfindet passende Werte statt zu lesen. Auch Beispielwerte in param_hints biased das Ergebnis.

Was funktioniert hat (Phase 1 → 9/10):

Ansatz Wirkung
KVP Spatial Pairing ohne Klassifikation Räumliche Paare als Kontext: „ORT steht neben Stuttgart“ — LLM weiß wo Werte stehen.
param_hints ohne Beispielwerte Semantische Beschreibungen: „oft als NR. beschriftet“ half dem LLM, Hausnummer zu finden obwohl KVP sie nicht gepairt hatte.
Gefilterte KVP-Paare Paare wo beide Seiten ALL-CAPS sind (zwei Headings) werden entfernt → weniger Noise für den LLM.