Projekte
Dokumentenextraktion und KIGebaut und im Einsatz2026

Ein Klick in Salesforce, und aus dem Dokument werden Daten

Deutsche Finanzdokumente werden erkannt, ausgelesen und gegen ein Schema geprüft, das für ihren Typ geschrieben wurde, und anschließend in den Datensatz zurückgeschrieben, der sie angefordert hat.

PythonFastAPIKubernetesAzure OpenAIAzure FilesLlamaIndexSalesforce
Problem
Deutsche Finanzdokumente wurden von Hand gelesen und ins CRM getippt, bevor überhaupt eine Kreditentscheidung möglich war.
Ergebnis
Ein Klick auf einen Dokumentendatensatz gibt die ausgelesenen Zahlen an genau diesen Datensatz zurück, mit erkanntem Dokumenttyp und nachvollziehbarem Wert für Wert.
Meine Rolle
Architektur und Umsetzung des gesamten Systems, vom Klick im CRM bis zum Dienst auf Kubernetes.
Stand
In Produktion. Das größte auf dieser Seite beschriebene System.

Das Problem

Der Kunde ist ein deutsches Finanzdienstleistungsunternehmen für Ärztinnen und Ärzte sowie andere Heilberufe. Kreditentscheidungen stützen sich dort auf eine Reihe deutscher Finanzdokumente: den Jahresabschluss, die Einnahmenüberschussrechnung, die viele Praxen stattdessen einreichen, und die laufende betriebswirtschaftliche Auswertung, die eine Steuerberatung im Jahresverlauf erstellt.

Diese Dokumente wurden von Hand gelesen. Jemand öffnete jedes einzelne, suchte die Zahlen, die für diesen Dokumenttyp zählen, tippte sie ins CRM, und erst danach war die Entscheidung möglich. Das war langsam, es band Personen, und es war auf die Art teuer, auf die manuelle Datenerfassung immer teuer ist: Die Kosten wachsen mit der Menge, und die Genauigkeit wird durch Übung nicht besser.

Deutsche Finanzdokumente sind außerdem nicht ein Format. Jeder Typ hat sein eigenes Layout, sein eigenes Vokabular und seine eigene Vorstellung davon, welche Zahlen zählen, und die Felder, die sich beim einen zu extrahieren lohnen, sind nicht die des anderen. Ein einziger Prompt über ein einziges Schema sammelt entweder alles schlecht ein oder einen Typ gut und den Rest gar nicht.

Sie kommen zudem lang an, und oft in schlechtem Zustand. Ein Modell hat ein begrenztes Kontextfenster, und der lohnende Inhalt ist ein kleiner Bruchteil des Textes, weshalb die Entscheidung darüber, was überhaupt geschickt wird, den Großteil der Ingenieursarbeit ausmacht. Und was ankommt, ist das, was die Praxis eben hatte: ein sauberer Export, ein Handyfoto, ein schiefer Scan.

Was gebaut wurde

Ein Dienst zur Dokumentenextraktion, aus Sicht der Nutzenden ein Klick im CRM und aus Sicht der Entwicklung eine FastAPI-Anwendung auf Kubernetes. Ein Klick auf einen Dokumentendatensatz schickt dessen Parameter und Metadaten an den Dienst; der Dienst liest die Datei von einem Azure-Share, erkennt, um welche Art Dokument es sich handelt, verarbeitet sie mit LlamaIndex, extrahiert gegen ein für diesen Typ geschriebenes Schema, berechnet die abgeleiteten fachlichen Spalten und alle nötigen Kalkulationen, schreibt die Ergebnisse in den Datensatz zurück und legt die JSON- und CSV-Artefakte wieder auf dem Share ab.

Jeder Dokumenttyp bringt seinen eigenen Prompt, sein eigenes Schema und seine eigenen Regeln mit, statt sich einen allgemeinen Prompt zu teilen. Abgedeckt sind die deutschen Finanzdokumente, auf denen die Kreditentscheidung tatsächlich beruht, und ein neuer Typ ist ein neues Schema samt Prompt und Regelwerk statt eine Änderung an der Pipeline.

Eingang ist, was der Kunde schickt. Scans, Fotos, Bilder, verkehrt herum liegende Seiten, Handschrift unterschiedlicher Qualität. Die Verarbeitungsstufe ist darauf ausgelegt, das anzunehmen, statt eine saubere Textebene vorauszusetzen, denn ein System, das nur ordentliche Exporte liest, ist ein System, dessen unordentliche Hälfte weiterhin jemand von Hand erledigt.

Das Modell läuft auf Azure OpenAI in der Umgebung des Kunden selbst, sodass nichts in der Kette, einschließlich des Modellaufrufs, eine von ihm kontrollierte Grenze verlässt.

Die Verarbeitungskette

  1. Salesforce

    Der Auslöser. Ein Klick auf einen Dokumentendatensatz, der dessen Parameter und Metadaten als Anfrage verschickt.

    dann
  2. Azure Files

    Wo die Dokumente liegen und wohin die Ergebnisse zurückgeschrieben werden.

    dann
  3. FastAPI auf Kubernetes

    Der Dienst. Erkennt den Dokumenttyp, verarbeitet ihn und stellt das passende Schema samt Prompt für diesen Typ zusammen.

    • identify
    • parse
    • select schema
    • fit the context window
    dann
  4. Modell

    Azure OpenAI, in der Umgebung des Kunden selbst. Extraktion gegen das Schema für diesen Dokumenttyp.

    dann
  5. Strukturierte Ausgabe

    JSON und CSV, dazu die fachlichen Spalten, die Salesforce erwartet, und jede nötige Kalkulation.

    dann
  6. Salesforce

    Zurückgeschrieben in den Datensatz, bereit für die Entscheidung, die daran hing.

Ein Rundlauf, der in Salesforce beginnt und dort endet. Eine Person klickt ein Dokument an; alles zwischen dem Klick und den gefüllten Feldern ist dieser Dienst, und sie verlässt dabei nie das CRM, in dem sie ohnehin arbeitet. Nichts in der Kette verlässt die Azure-Umgebung des Kunden, auch der Modellaufruf nicht.

Entscheidungen

Das Modell läuft in der Umgebung des Kunden selbst.

Azure OpenAI statt eines öffentlichen API-Endpunkts, im Azure des Kunden. Für ein Unternehmen, das deutsche Finanzdokumente verarbeitet, ist das die erste gestellte Frage und diejenige, die darüber entscheidet, ob ein Projekt überhaupt zustande kommt: Die Dokumente überschreiten nie eine Grenze, die der Kunde nicht kontrolliert, und es kommt kein zweiter Auftragsverarbeiter hinzu, der in ein Verzeichnis müsste. Es ist dieselbe Haltung wie der Eigenbetrieb der Pipeline im ersten Projekt, und aus demselben Grund. Die bequeme Wahl stand in beiden Fällen zur Verfügung und wäre in beiden Fällen die falsche gewesen.

Wenn er etwas nicht lesen kann, scheitert er laut, statt zu raten.

Ein Durchlauf, der kein brauchbares Ergebnis erzeugen kann, meldet einen Fehler mit Fehlercode und Begründung und lässt sich wiederholen. Er gibt nicht seine beste Vermutung zurück. Das ist das mit Abstand wichtigste Verhalten in einem System, das eine Kreditentscheidung speist, denn die Alternative ist keine fehlende Zahl, sondern eine selbstbewusst falsche in einem Feld, auf das jemand hin handeln wird. Ein Fehlschlag kostet einen erneuten Versuch. Eine plausible falsche Zahl kostet die Entscheidung, die sie verzerrt, und nichts weiter unten in der Kette kann sie von einer richtigen unterscheiden.

Jeder Dokumenttyp bekommt sein eigenes Schema, seinen eigenen Prompt und seine eigenen Regeln.

Deutsche Finanzdokumente sind nicht ein Format. Jeder Typ hat sein eigenes Layout, sein eigenes Vokabular und seine eigene Vorstellung davon, welche Zahlen zählen, und die Felder, die sich beim einen zu extrahieren lohnen, sind nicht die des anderen. Ein einziger allgemeiner Prompt über alle hinweg erzeugt eine Ausgabe, die für jeden Typ plausibel und für keinen richtig ist, und genau das ist die Schwachstelle, die Dokumentenextraktion in der Vorführung gut und im Betrieb schlecht dastehen lässt. Die Trennung nach Typ kostet vorab mehr und ist der Grund, warum die Ergebnisse tragen.

Einen Dokumenttyp hinzuzufügen ist eine Datenfrage, keine Codeänderung.

Ein neuer Typ ist ein Schema, ein Prompt und dessen Regeln, kein neuer Zweig durch die Pipeline. Das lässt die Abdeckung wachsen, ohne dass sich die Struktur des Systems ändert, und es bedeutet, dass diejenigen, die die Dokumente verstehen, es erweitern können, ohne den Dienst anzufassen. Eine Pipeline, die für jedes neue Formular eine Entwicklerin braucht, hört in dem Moment auf zu wachsen, in dem diese Entwicklerin beschäftigt ist.

Zu entscheiden, was das Modell zu sehen bekommt, ist der Großteil der Ingenieursarbeit.

Diese Dokumente kommen lang an, das Modell hat ein begrenztes Kontextfenster, und der lohnende Inhalt ist ein kleiner Bruchteil des Textes. Alles vor dem Modellaufruf existiert, um eine einzige Frage gut zu beantworten: Welchen Teil dieses Dokuments muss das Modell sehen, um dieses Schema zu füllen. Wird das falsch beantwortet, rettet kein Prompt die Sache, und deshalb stehen Erkennung und Verarbeitung vor der Extraktion, statt in sie hineingefaltet zu sein.

Der Auslöser sitzt dort, wo die Arbeit ohnehin stattfindet.

Das gesamte System ist erreichbar, indem man in Salesforce ein Dokument anklickt. Niemand meldet sich an einem zweiten Werkzeug an, lädt eine Datei hoch, wartet, lädt ein Ergebnis herunter und fügt es wieder ein. Das klingt nach Bequemlichkeit und geht in Wahrheit um Akzeptanz: Ein Extraktionsdienst, der einen Umweg verlangt, wird die erste Woche genutzt und dann liegen gelassen, und der Wert dieses Dienstes liegt darin, dass er auf einem Weg liegt, den jemand ohnehin gegangen ist.

Die Ergebnisse gehen dorthin zurück, wo entschieden wird.

Die Ausgabe ist keine Datei, die jemand erst suchen muss. Die extrahierten Felder, die abgeleiteten fachlichen Spalten und jede Kalkulation landen wieder auf dem Salesforce-Datensatz, der den Durchlauf ausgelöst hat, und das ist die Maske, an der entschieden wird. Die Artefakte werden zusätzlich auf den Azure-Share geschrieben, sodass JSON und CSV für alles Nachgelagerte vorliegen, aber das eigentliche Ziel ist der Datensatz.

Was dabei herauskam

Vorher: Handarbeit

Ersetzt wurde die Arbeit von Menschen, die Zahlen aus Dokumenten abtippen.

Jedes Dokument wurde geöffnet, gelesen und von Hand ins CRM eingegeben, bevor eine Entscheidung möglich war. Das war langsam, es band Personen, und die Kosten wuchsen mit der Menge. Diesen Schritt übernimmt jetzt der Dienst, was diese Personen für Arbeit frei macht, bei der sorgfältiges Lesen eines Dokuments tatsächlich zählt. Eine Zahl dazu, was das eingespart hat, steht hier nicht, weil der Kunde das gemessen hat und ich nicht.

Er nennt den Grund

Ein Fehlschlag ist ein Code und eine Begründung, kein leeres Feld.

Wenn die Extraktion kein brauchbares Ergebnis erzeugen kann, scheitert der Durchlauf ausdrücklich, meldet, auf welchen Fehler er gestoßen ist, und lässt sich wiederholen. Das Entwurfsziel war, dass keine Ausgabe schlechter ist als eine falsche Ausgabe in einem System, das eine Kreditentscheidung speist, und die Fehlercodes sind das, was einen erneuten Versuch zu einer informierten Handlung macht statt zu einer hoffnungsvollen.

Was auch immer ankommt

Scans, Fotos, Bilder, gedrehte Seiten, schlechte Handschrift.

Die Verarbeitungsstufe setzt nichts über die Eingangsqualität voraus, weil die Dokumente aus Tausenden einzelner Praxen kommen und nicht aus einem System, das sich vereinheitlichen ließe. Eine Pipeline, die eine saubere Textebene verlangt hätte, hätte die leichte Hälfte automatisiert und die schwere genau dort gelassen, wo sie war.

Was es nicht leistet

  • Er meldet Fehlschläge, nicht Fehler. Wenn er ein Dokument nicht lesen kann, sagt er es, und der Durchlauf lässt sich wiederholen. Was er nicht kann, ist Ihnen zu sagen, dass er erfolgreich war und dabei eine Zahl falsch gelesen hat, und das ist der schwierigere und gefährlichere Fall. Er ist das gesamte Thema des Projekts zur Rechnungsextraktion an anderer Stelle in diesem Bereich.
  • Die Abdeckung ist eine Liste, keine Fähigkeit. Er liest die Dokumenttypen, für die es ein Schema, einen Prompt und deren Regeln gibt. Ein Typ, den noch niemand geschrieben hat, ist ein Typ, den er nicht lesen kann.
  • Er ist für deutsche Finanzdokumente und deren Vokabular und Layouts gebaut. Er ist kein allgemeiner Dokumentenextraktor und war nie als solcher gedacht.
  • Er sitzt in der Mitte einer Kette, deren beide Enden ihm nicht gehören. Ist das CRM oder der Azure-Share nicht erreichbar, gibt es keine Extraktion.
  • Die Eingangsqualität setzt weiterhin eine Untergrenze. Er ist darauf gebaut, einen schlechten Scan anzunehmen, statt ihn abzuweisen, und das ist nicht dasselbe wie jeden schlechten Scan richtig zu lesen. Das ehrliche Ergebnis ist in diesem Fall ein Fehlschlag mit Begründung statt einer Vermutung.

Wo es steht

In Produktion und gut im Einsatz. Er hat einen manuellen Prozess ersetzt und ist die größte auf dieser Seite beschriebene Arbeit.

Der naheliegende nächste Schritt ist, dass diese Daten künftig ein Modell tragen und nicht nur ein Formular: eine bewertete Kreditbeurteilung auf Basis der extrahierten Historie. Dieses Projekt ist im Grundsatz vereinbart und nicht begonnen, und es ruht aus Gründen auf Kundenseite, nicht aus technischen.