← Zurück0406
Client2025·Fallstudie

Banking DB Assistant

Banking DB Assistant übersetzt natürliche Sprache in geprüftes, ausschließlich lesendes SQL gegen ein reales Banking-Schema. Mehrere RAG-Provider hängen an einer Schnittstelle, zwei Read-Only-Stufen sichern die Datenbank ab, und DB-Fehler werden live ins Modell zurückgespielt.

Kunde

Raiffeisen Group IT

Rolle

Team von 5

Status

Ausgeliefert

Jahr

2025

Stack

PY

FastAPI + Python

Async API, SSE streaming, OAuth2 scopes

NG

Angular 21

Chat UI, markdown rendering, signals

PG

Postgres + pgvector

Schema introspection + embeddings

RAG

Multi-Provider RAG

LangChain · LlamaIndex · DSPy · Vanna · MCP

Kern des Projekts

Antworten mit Garantie

Am Anfang sah es nach "Chat über Datenbank" aus. Das war nur die Oberfläche. Entscheidend war die Garantie. Eine Bank kann nicht akzeptieren, dass ein LLM gelegentlich schreibt, gelegentlich halluziniert oder gelegentlich eine ganze Produktivtabelle zieht. Banking DB Assistant wurde deshalb eher ein Sicherheits- und Retrieval-Projekt mit Chat-Oberfläche als ein Chat-Projekt.

5

austauschbare RAG-to-SQL-Provider hinter einer Schnittstelle

3

Safety-Layer: Intent, SQL-Validierung, OAuth-Scopes

SSE

Auto-Repair-Loop streamt DB-Fehler zurück

Problem

Was daran schwierig war

Problem

Banking-Mitarbeitende brauchen oft Antworten aus der Datenbank, schreiben aber kein SQL. Klassische BI-Tools sind zu starr. Ein LLM klingt naheliegend, ist aber riskant: das Modell kann Tabellen erfinden, versehentlich Schreibbefehle erzeugen oder ohne Limit zu viele Daten ziehen. Sobald ein realer Bank-Stakeholder draufschaut, reicht "der Prompt passt schon" nicht mehr. Jede Abfrage muss vor der Engine durch eine Sicherheitsschicht, jeder DB-Fehler muss reproduzierbar weggehen, und der Provider darf austauschbar bleiben, weil sich RAG-Frameworks im Halbjahres-Takt ändern.

Ansatz

Angular-21-Frontend, FastAPI-Backend, Postgres mit pgvector als Wissensspeicher, alles in Docker reproduzierbar. Im Kern sitzt ein Provider-Registry, in der LangChain, LlamaIndex, DSPy, Vanna und ein eigener MCP-Provider als gleichwertige Implementierungen einer RAG2SQL-Schnittstelle hängen. Hybrid-Retrieval (Vector + BM25) zieht die richtigen Tabellen-Snippets aus dem Schema und der Custom-Doku heraus, statt den ganzen Schema-Dump in den Prompt zu kippen. Vor der DB läuft ein zweistufiger Guard: ein Intent-Filter auf der Frage, ein sqlparse-basierter Token-Scan auf dem generierten Statement. Fehler aus der DB werden klassifiziert und über Server-Sent Events zurückgegeben, damit das Modell mit echtem Kontext repariert.

Pipeline

Frage zu geprüfter Antwort

Jeder Schritt hat eine enge Aufgabe: Intent prüfen, nur relevantes Schema retrieven, SQL generieren, auf Token-Ebene validieren, dann streamen mit klassifizierten Fehlern statt Crashes.

Frage

Intent-Filter blockiert Schreib-Verben in der Frage

Retrieval

pgvector + BM25 Hybrid-Suche über Schema und Doku

Generieren

gewählter Provider erzeugt SQL aus Snippet-Kontext

Validieren

sqlparse-Token-Walk gegen verbotene Keywords

Streamen

SSE-Ergebnisse; DB-Fehler triggern Repair-Versuche

Was sich geändert hat

Was es praxistauglicher gemacht hat

Provider-Registry statt einem SDK

Erste Skizze hing an einem einzigen RAG-Framework. Nach zwei Upgrades, die das Chunking gebrochen haben, wurde jeder Provider zu einer Lazy-Factory hinter einer kleinen RAG2SQL-Schnittstelle. Wechsel von LangChain zu LlamaIndex oder DSPy ist jetzt Config, kein Refactor.

Zwei Wächter schlagen einen klugen Prompt

Das Modell zu bitten "bitte nur lesen" hat nie gehalten. Der Intent-Filter blockt Schreib-Verben in der Frage, der SQL-Validator läuft den geparsten Token-Tree und lehnt INSERT, UPDATE, DROP und Freunde auch in CTEs ab. Beide müssen grün sein, bevor Postgres das Statement sieht.

Retrieval schlägt Schema-Dump im Prompt

Den ganzen Banking-Schema-Dump in den Prompt zu kippen war teuer und laut. Hybrid-Suche über pgvector-Embeddings plus BM25-Keyword-Scores zieht nur die relevanten Tabellen, Enum-Typen und Custom-Docs. Der Prompt wurde kleiner, die Antworten wurden besser.

Fehler wurden Teil der Schleife

Frühe Demos starben sofort, wenn ein Spaltenname schief war. Jetzt klassifiziert der Executor die Exception: Verbindungsfehler stoppen, Syntax- und Spaltenfehler gehen zurück an das Modell zum Reparieren. Drei Versuche, danach saubere deutsche Fehlermeldung statt Crash.

Technische Entscheidungen

Warum so gebaut

Sicherheit

  • +Intent-Regex auf der Frage blockt Schreib-Verben, bevor Retrieval überhaupt läuft.
  • +sqlparse-Token-Walk lehnt verbotene Keywords im Modell-Output ab, auch in CTEs.

Retrieval

  • +pgvector plus BM25 Hybrid-Suche zieht nur relevante Schema-Snippets und Custom-Doku.
  • +BankingSchema introspiziert pg_class für Tabellen, Spalten und Enums, mit FK-Join-Hints.

Runtime

  • +Provider-Registry instanziiert LangChain, LlamaIndex, DSPy, Vanna oder MCP lazy nach Name.
  • +Server-Sent Events streamen Zeilen; klassifizierte DB-Fehler triggern begrenzte Auto-Repair-Retries.

Runtime

Fehler gehören zur API

Der Execute-Endpunkt tut nicht so, als hätte das Modell immer recht. Er streamt Zeilen über SSE bei sauberem SQL und klassifizierte, lesbare Fehlermeldungen bei Problemen. Reparierbare Fehler gehen mit begrenztem Retry-Budget zurück ins Modell, Infrastruktur-Fehler stoppen sofort.

Provider-Registry

LangChain, LlamaIndex, DSPy, Vanna und ein eigener MCP-Provider hängen an einer RAG2SQL-Schnittstelle. Neues Framework bedeutet eine Klasse, kein Umbau.

Read-Only-Garantie

Intent-Filter auf der Frage, sqlparse-Token-Walk auf dem SQL, OAuth2-Scopes am Endpunkt. Drei unabhängige Schichten, alle Pflicht.

Auto-Repair-Stream

Server-Sent Events liefern Zeilen oder klassifizierte Fehler. Reparierbare SQL-Fehler gehen zurück ins Modell, mit begrenztem Retry-Budget.

Was daraus rauskam

Gut / nicht gut

Hat gut funktioniert

  • +Eine kleine RAG2SQL-Schnittstelle plus Lazy-Registry hat das Projekt mehrere Framework-Releases ohne Rewrites überlebt.
  • +Hybrid-Retrieval über pgvector und BM25 liefert genaueres SQL als ein vollständiger Schema-Dump im Prompt.
  • +Der zweistufige Guard (Intent-Filter + sqlparse-Token-Walk) gibt dem Stakeholder eine verteidigbare Antwort auf "kann es jemals schreiben?".
  • +DB-Fehler ins Modell zu streamen hat aus fragilen Demos einen stabilen Auto-Repair-Loop gemacht.

Hat nicht funktioniert

  • -Dem Modell auf Englisch zu sagen "nur lesen" war unzuverlässig — explizite Token-Erzwingung war die einzige ehrliche Option.
  • -Ein einziger RAG-Provider pro Umgebung war zu starr; Provider-Wahl pro Request wurde nötig, um Modelle zu vergleichen.
  • -Unbegrenzte Auto-Repair-Versuche haben bei unmöglichen Fragen geloopt — Retries sind jetzt hart begrenzt und werden dem User gezeigt.

Nächste Baustellen

Was als Nächstes zählt

Tenant-spezifische Schema-Whitelists, damit ein Deployment mehrere Banking-Subdomains bedient

Eval-Harness (deepeval ist schon drin) mit echten Raiffeisen-Fragen schärfen, nicht synthetischen

Provider-Wahl in einen kleinen Router schieben, der nach Fragentyp entscheidet, nicht nach User-Klick

Galerie

03

Nächstes Projekt

NoCap Barbers

Du arbeitest an
ähnlichen Themen?

Schreib mir kurz, worum es geht — ich melde mich.

Timo Wilde© 2026