Architektur¶
LLM-Chat ist als containerisierte Webanwendung mit klarer Schichtentrennung aufgebaut. Eine Gradio-basierte UI nimmt Eingaben entgegen und gibt Streaming-Antworten aus; eine modulare Geschäftslogik kapselt Sitzungs-, Dokument- und Bildverarbeitung sowie die Anbindung an den Sprachmodell-Server. Die Anbindung an das Sprachmodell ist asynchron implementiert und über Modell-Profile parametrisiert. Konfigurations- und Verbindungsparameter werden vollständig über Umgebungsvariablen gesetzt.
Auf einen Blick¶
- Webanwendung in Python 3.12, ausgeliefert als Container-Image, Bereitstellung über einen einzelnen HTTP-Port.
- UI-Schicht mit Gradio (
MultimodalTextbox,Chatbot,Sidebar,Accordion); ausgelagertes CSS und ein eigenes Theme. - Geschäftslogik strikt getrennt von der UI in Modulen für Sitzungen, Dokumente, Bilder, Streaming und Export.
- Asynchrone LLM-Anbindung mit
AsyncOpenAIund Streaming-Verarbeitung; Sampling- und Reasoning-Parameter pro Modell-Profil konfigurierbar. - Dokumenten-Pipeline mit zweistufiger Extraktion (schneller, formatspezifischer Extraktor → Universal-Fallback) und nachgelagerter Bereinigung.
- Anwendungs-State pro Browser-Tab über
gr.State; Dokumente sind sitzungsübergreifend, aber tab-lokal. - Konfiguration vollständig über Umgebungsvariablen mit dokumentierten Defaults; lokale Verarbeitung ohne externe Drittanbieter-Ressourcen.
Architekturüberblick¶
Die Anwendung gliedert sich in vier Bereiche: die UI-Schicht, den tab-lokalen Anwendungs-State, die Geschäftslogik mit ihren Verarbeitungs-Pipelines sowie die externe Anbindung an einen Sprachmodell-Server.
Komponenten und Datenfluss¶
flowchart TB
User([Browser])
subgraph UI["UI-Schicht (Gradio)"]
Input["Multimodale Eingabe"]
ChatUI["Chat-Bereich"]
Sidebar["Sidebar mit Sitzungen und Dokumenten"]
Export["Word-Export"]
end
subgraph Logic["Geschäftslogik (pro Browser-Tab)"]
SessMgr["SessionManager"]
DocMgr["DocumentManager"]
ImgProc["ImageProcessor"]
DocProc["DocumentProcessor"]
StreamChat["StreamingChat"]
end
subgraph Pipeline["Dokumenten-Pipeline"]
Fast["Schnelle Extraktoren"]
Universal["Universal-Parser"]
Clean["Bereinigung und Tokenzählung"]
end
Profiles[("Modell-Profile<br/>Qwen3 / Qwen3.5 / Kimi / GLM / Default")]
LLM[("LLM-Endpunkt<br/>OpenAI-kompatibel")]
User --> Input
User --> Sidebar
Input --> ImgProc
Input --> DocProc
DocProc --> Fast
Fast -- "bei Fehlschlag" --> Universal
Fast --> Clean
Universal --> Clean
Clean --> DocMgr
ImgProc --> SessMgr
Sidebar --> SessMgr
Sidebar --> DocMgr
SessMgr --> StreamChat
DocMgr --> StreamChat
Profiles --> StreamChat
StreamChat -- "Streaming-Anfrage" --> LLM
LLM -- "Antwort-Stream" --> StreamChat
StreamChat --> ChatUI
SessMgr --> ChatUI
SessMgr --> Export
UI-Schicht¶
Die UI-Schicht ist mit Gradio realisiert. Eine multimodale Eingabezeile akzeptiert Text und Datei-Anhänge gemeinsam; die Klassifikation als Bild oder Dokument erfolgt über die Dateierweiterung. Die Sidebar zeigt den Sitzungsverlauf, geladene Dokumente mit Token-Auslastung sowie eine zusätzliche Upload-Zone und enthält den Auslöser für den Word-Export. Markdown- und LaTeX-Rendering erfolgen direkt im Chat-Bereich; das CSS ist in einem eigenen Modul zentralisiert.
Anwendungs-State¶
Pro Browser-Tab wird ein AppState über gr.State instanziiert. Er bündelt die Session-Verwaltung, die Dokumentenverwaltung, die Prozessoren für Dokumente und Bilder, den Streaming-Client sowie die geltende Konfiguration. Diese Aufteilung gewährleistet, dass mehrere parallele Browser-Tabs einander nicht beeinflussen, während innerhalb eines Tabs der Dokumentenpool sitzungsübergreifend erhalten bleibt.
Dokumenten-Pipeline¶
Eingehende Dokumente durchlaufen eine zweistufige Pipeline. Zunächst wird je nach Dateiformat ein leichtgewichtiger Extraktor versucht: PDF über pdfminer.six, DOCX über python-docx, PPTX über python-pptx, XLSX über openpyxl, HTML über einen eigenen Tag-Parser, reine Textformate über eine Mehrfach-Encoding-Lesung. Schlägt der schnelle Pfad fehl oder existiert für das Format kein entsprechender Extraktor (DOC, PPT, XLS), übernimmt unstructured mit formatspezifischen Partitionern; für PDF ist über die Konfiguration zwischen einer schnellen Variante und einer hochauflösenden Layout-Analyse wählbar. Im Anschluss werden mehrfache Leerzeilen und Whitespaces reduziert, optional Seitenzahlen entfernt und zu kurze Absätze ausgefiltert. Der Token-Bedarf wird approximiert und gegen das konfigurierte Budget geprüft, bevor das Dokument an den DocumentManager übergeben wird.
Bild-Pipeline¶
Bilder werden vom ImageProcessor validiert (Format, Dateigröße), in der EXIF-Orientierung korrigiert, bei Bedarf auf eine maximale Kantenlänge skaliert und in den RGB-Farbraum konvertiert. Die Übergabe an das Sprachmodell erfolgt als Base64-kodierte JPEG-Datei innerhalb der image_url-Struktur der OpenAI-API. Eine persistente Kopie wird zusätzlich für die Anzeige im Chat-Verlauf zwischengespeichert.
LLM-Anbindung und Modell-Profile¶
Die Anbindung an das Sprachmodell ist im Modul StreamingChat gekapselt und nutzt AsyncOpenAI mit aktivem Streaming. Vor jedem Aufruf wird das aktive Modell-Profil ermittelt: zunächst über eine explizite Konfigurationsvariable, andernfalls über einen Substring-Match auf den Modellnamen, ansonsten über das Default-Profil. Pro Profil sind getrennte Parametersätze für Reasoning- und Instruct-Modus hinterlegt; sie umfassen reguläre OpenAI-Parameter (Temperature, top-p, Penalty-Werte), vLLM-spezifische Zusatzparameter (top-k, min-p, repetition-penalty) und chat_template_kwargs zur Steuerung des Reasoning-Verhaltens. Vorkonfiguriert sind Profile für Qwen3, Qwen3.5, Kimi und GLM; weitere Modelle werden über das Default-Profil bedient.
Während des Streamings werden die eingehenden Chunks fortlaufend auf <think>-Markierungen geprüft. Solange ein Reasoning-Block geöffnet ist (auch bei nur teilweise empfangenem Tag), wird die UI mit einer kompakten Statusanzeige aktualisiert; sobald der Block geschlossen ist, wird er aus der sichtbaren Antwort entfernt und der reguläre Text wieder zeichenweise ausgegeben.
Workflow einer Anfrage¶
Eine Nutzereingabe wird wie folgt verarbeitet: Beim Absenden werden Text und Anhänge zwischengespeichert und das Eingabefeld geleert (Anti-Blink-Pattern). Die Anhänge werden klassifiziert und parallel verarbeitet — Dokumente durchlaufen die Extraktions-Pipeline und werden dem DocumentManager zugeführt, Bilder werden vom ImageProcessor aufbereitet und der aktuellen Nachricht zugeordnet. Liegt kein Eingabetext vor, wird ein kontextabhängiger Standard-Prompt verwendet (Bildbeschreibung beziehungsweise Dokumentenfrage). Die ChatSession setzt anschließend die vollständige Nachrichtenfolge zusammen: System-Prompt mit Datumshinweis und gegebenenfalls aktuellem Dokumentkontext, gefolgt vom bisherigen Chat-Verlauf und der neuen Eingabe. Diese Liste wird an StreamingChat übergeben, dort werden die profilabhängigen Parameter angewendet und die Anfrage als Streaming-Aufruf an den LLM-Endpunkt gesendet. Die zurückkommenden Chunks werden gefiltert und stückweise an die UI weitergereicht. Nach der ersten Antwort einer Sitzung wird zusätzlich ein Kurztitel über einen separaten, nicht-streamenden Aufruf erzeugt.
Nebenläufigkeit und Robustheit¶
Die LLM-Anbindung ist asynchron; eine laufende Generierung kann über ein Stop-Signal zeitnah unterbrochen werden, ohne den restlichen State zu beschädigen. Verbindungsfehler werden in benutzerfreundlich formulierte Hinweise übersetzt (Server überlastet, Modell nicht gefunden, keine Verbindung) und unterbrechen den Chat nicht dauerhaft. Bei der Dokumentextraktion wird der Fallback-Pfad nur bei tatsächlich leerem oder fehlgeschlagenem Schnellpfad eingeschlagen und protokolliert.
Konfiguration und Deployment¶
Sämtliche Konfiguration erfolgt über Umgebungsvariablen, die in einer dataclass-basierten AppConfig gebündelt werden. Dies umfasst Verbindungsparameter zum LLM-Endpunkt, Token-Budgets, Bild- und Dokumentlimits, Verarbeitungsoptionen sowie Server- und Pfadangaben. Eine optionale .env-Datei wird automatisch geladen; ohne sie greifen dokumentierte Defaults. Die Auslieferung erfolgt als Container-Image auf Basis von python:3.12-slim mit den für unstructured benötigten System-Bibliotheken (libmagic, poppler, OpenGL/glib). Die Anwendung lauscht standardmäßig auf einem konfigurierbaren Port und ist über einen ebenfalls konfigurierbaren URL-Pfad einbindbar.
Technologie-Übersicht¶
- Sprache und Laufzeit: Python 3.12.
- UI: Gradio.
- LLM-Client:
openaiPython-SDK (AsyncOpenAI) gegen einen OpenAI-kompatiblen Endpunkt. - Modell-Profile: vorkonfiguriert für Qwen3, Qwen3.5, Kimi und GLM; Default-Profil für weitere Modelle.
- Schnelle Dokumentenextraktion:
pdfminer.six(PDF),python-docx(DOCX),python-pptx(PPTX),openpyxl(XLSX), eigener HTML-Parser. - Universal-Extraktion:
unstructuredmit formatspezifischen Partitionern. - Bildverarbeitung:
Pillow. - Word-Export:
python-docxmit eigenständigem Markdown-Renderer. - Konfiguration: Umgebungsvariablen, optional über
python-dotenv. - Deployment: Docker (
python:3.12-slim) mit System-Abhängigkeiten fürunstructured[pdf].