LLM-gestützte Datenvisualisierung durch Code-Generierung: Eine detaillierte Fallstudie#

1. Experimenteller Kontext und Motivation#

1.1 Die Problemstellung#

Large Language Models (LLMs) zeigen trotz ihrer beeindruckenden Fähigkeiten in vielen Bereichen Schwächen im Umgang mit tabellarischen Daten und numerischen Berechnungen. Die zentrale Frage dieses Experiments war: Wie können LLMs trotz dieser Limitierungen effektiv mit strukturierten Daten arbeiten?

Der gewählte Lösungsansatz basierte auf Code-Indirektion: Statt das LLM direkt mit Zahlen und Berechnungen zu konfrontieren, generiert es Python-Code zur Datenanalyse und Visualisierung. Dieser Code wird in einer kontrollierten, gesicherten Umgebung ausgeführt – die tatsächlichen Berechnungen übernehmen bewährte Bibliotheken wie Pandas und Plotly.

Ein weiteres Lernziel war die Untersuchung von Multi-Agenten-Systemen: Wie können verschiedene spezialisierte Agenten zusammenspielen, um komplexe Aufgaben zu lösen? Konkret sollten Agenten für Intent-Erkennung, Planung und Ausführung koordiniert arbeiten.

1.2 Funktionale Zielsetzung#

Die funktionale Fragestellung war pragmatischer Natur: Lässt sich ein Tool entwickeln, das Nutzer bei der schnellen Auswahl passender Grafiken für ihre Daten unterstützt und einfache Visualisierungen weitgehend automatisch erstellt?

Der Ansatz war bewusst als reines Lernprojekt konzipiert – es ging primär darum herauszufinden, wie weit man mit Agenten und Code-Generierung zur Problemlösung kommt, nicht um die Entwicklung eines produktionsreifen Tools.

2. Das Tool: Interactive Chart Generator#

2.1 Funktionsumfang#

Der entwickelte Interactive Chart Generator verarbeitet CSV- und Excel-Dateien (inklusive Multi-Sheet-Unterstützung mit automatischer Typ-Erkennung) und ermöglicht die Erstellung interaktiver Plotly-Visualisierungen über natürlichsprachige Chat-Anfragen.

Beispielhafte Nutzerinteraktionen:

  • “Erstelle Balkendiagramme für alle Sheets”
  • “Zeige mir die Top 10 Kategorien als Balkendiagramm”
  • “Färbe die Balken grün für positive Werte”
  • “Füge Prozentwerte über den Balken hinzu”

Das System unterscheidet zwischen verschiedenen Intent-Typen: Modifikation eines bestehenden Charts, Erstellung eines einzelnen Charts, Erstellung von Charts für mehrere Sheets, oder reine Analyse-Anfragen ohne direkte Chart-Erstellung.

2.2 Technischer Stack#

Die Implementierung basiert auf:

  • Gradio (Web-Interface): Feste Infrastruktur für alle Experimente dieser Serie
  • Lokale LLMs (HU-Modelle) über OpenAI-kompatible API: llm3-Modell
  • Pandas: Bewährte Lösung für strukturierte Datenverarbeitung
  • Plotly Express: Pragmatische Wahl für interaktive Visualisierungen
  • Python 3.11+: Entwicklungsumgebung

Das System umfasst ca. 8.500 Zeilen Code verteilt auf 33 Dateien (27 Python-Dateien) und wurde vollständig LLM-gestützt entwickelt.

3. Architektur-Evolution: Von zwei Agenten zu Intent→Plan→Execute#

3.1 Version 1: Direkter Chat-Agent + Chart-Agent#

Der initiale Ansatz implementierte zwei Agenten:

  • Ein Chat-Agent für Kommunikation
  • Ein Chart-Agent für Visualisierungserstellung

Dieser Ansatz erwies sich als zu instabil. Das Hauptproblem: Das System konnte nicht zuverlässig zwischen explorativer Diskussion (“Welche Charts wären sinnvoll?”) und konkreten Ausführungsanfragen (“Erstelle jetzt einen Chart”) unterscheiden. Die Fehlerrate war hoch, da oft Charts generiert wurden, wenn nur Beratung gewünscht war, oder umgekehrt.

3.2 Version 2: Einführung der Intent-Erkennung#

Die zweite Iteration führte einen IntentService ein, der Nutzeranfragen analysiert und klassifiziert:

  • modify: Änderung am aktuellen Chart
  • create_single: Einen Chart für ein spezifisches Sheet
  • create_multiple: Charts für mehrere oder alle Sheets
  • analyze: Nur Empfehlungen, keine Chart-Erstellung

Diese Trennung verbesserte die Zielgenauigkeit erheblich. Das System konnte nun besser entscheiden, welche Aktion durchgeführt werden sollte.

3.3 Version 3: Der vollständige Intent→Plan→Execute Workflow#

Die finale Architektur führte einen zusätzlichen Planungsschritt ein, da klar wurde, dass die direkte Übersetzung von Intent zu Ausführung für komplexe Anfragen nicht ausreichte.

IntentService: Analysiert die Nutzeranfrage und extrahiert die Absicht

  • Input: Nutzer-Nachricht, verfügbare Sheets, aktueller Chart-Status
  • Output: UserIntent mit Action-Typ, Ziel-Sheets, Reasoning, Confidence

PlanService: Übersetzt den Intent in einen konkreten Ausführungsplan

  • Input: UserIntent, DataFrame-Metadata, aktueller Chart-Code (bei Modifikation)
  • Output: ExecutionPlan mit detaillierten ChartStep-Objekten oder ModificationStep-Objekten

ExecutionService: Koordiniert die tatsächliche Ausführung

  • Input: ExecutionPlan, Session-Daten
  • Output: ExecutionResult mit erstellten Charts oder Fehlermeldungen
  • Implementiert Retry-Logik und Fehlerkorrektur

Diese drei Services kommunizieren über klar definierte Datenmodelle (UserIntent, ExecutionPlan, ChartStep, ExecutionResult), was Testbarkeit und Wartbarkeit erheblich verbessert.

3.4 Sichere Code-Ausführung#

Die Code-Ausführung erfolgt in einer kontrollierten Sandbox:

safe_globals = {
    'pd': pd,
    'px': px,
    'go': go,
    'np': np,
    'df': df.copy(),
}

safe_builtins = {
    'len': len, 'str': str, 'int': int, 'float': float,
    # ... weitere erlaubte Funktionen, aber OHNE __import__
}

safe_globals['__builtins__'] = safe_builtins
exec(code, safe_globals, safe_locals)

Diese Sandbox-Implementierung kam als eigenes Lernfeld hinzu, nachdem klar wurde, dass Chart-Code durchaus komplex werden kann. Die Restriktion auf vordefinierte Module (ohne __import__) verhindert potentiell gefährliche Operationen.

4. Entwicklungsentscheidung: Pattern-Bibliotheken#

4.1 Das Problem: Instabile Code-Generierung#

Eine zentrale Erkenntnis während der Entwicklung war, dass reine LLM-basierte Code-Generierung für Chart-Erstellung nicht stabil genug funktionierte. Häufige Probleme:

  • Inkonsistente Verwendung von .groupby() (mit/ohne as_index=False)
  • Vergessene .dropna() Aufrufe, führend zu Fehler bei fehlenden Werten
  • Komplexe und fragile Konstrukte wie pd.Categorical() mit verschachtelten Listen
  • Bei .size() fehlte häufig das Umbenennen von ‘size’ zu ‘count’, was zu Spalten-Konflikten führte

4.2 Die Lösung: Hybrid-Ansatz mit 44 Code-Patterns#

Die Implementierung umfangreicher Pattern-Bibliotheken löste diese Instabilität. Das System umfasst:

23 funktionierende Implementierungsmuster:

  • PATTERN_SIMPLE_BAR: Einfaches Balkendiagramm mit Aggregation
  • PATTERN_GROUPED_BAR_TWO_VARS: Gruppierte Balken mit Farbcodierung
  • PATTERN_TIME_SERIES_SIMPLE: Zeitreihen-Visualisierung
  • PATTERN_TOP_N_SIMPLE: Top-N-Darstellung mit Sortierung
  • PATTERN_SEMANTIC_COLOR_DISCRETE_MAP: Semantische Farb-Zuordnung
  • … und 18 weitere

7 Anti-Patterns zur Fehlervermeidung:

  • ANTI_PATTERN_COMPLEX_CATEGORICAL: Warnt vor pd.Categorical() mit komplexen Listen
  • ANTI_PATTERN_NO_DROPNA: Erzwingt explizite NaN-Behandlung
  • ANTI_PATTERN_MISSING_RESET_INDEX: Verhindert Index-Probleme nach groupby
  • … und 4 weitere

9 Modifikationsmuster:

  • MODIFICATION_PATTERN_SINGLE_COLOR: Einfache Farb-Änderung
  • MODIFICATION_PATTERN_ADD_LABELS: Wertelabels über Balken
  • MODIFICATION_PATTERN_CHANGE_TITLE: Titel-Anpassung
  • … und 6 weitere

5 zusätzliche Patterns:

  • Semantische Farb-Erkennung
  • Gradienten für ordinale Skalen
  • Pattern-Auswahl-Strategien

4.3 Funktionsweise des Hybrid-Ansatzes#

Der PlanService erhält diese Pattern-Bibliothek im Prompt. Das LLM wählt dann basierend auf:

  • Datentypen (numerisch, kategorial, datetime)
  • Nutzeranfrage
  • Anzahl der Datenpunkte

das passende Pattern aus und adaptiert es durch Ersetzen der Spalten-Namen. Dieser Ansatz kombiniert LLM-Intelligenz (Auswahl, Adaptation) mit Template-Stabilität (bewährte Implementierungen).

5. Entwicklungsprozess und Workflow#

5.1 Spezifikationsgetriebener Ansatz#

Der Entwicklungsprozess war stark spezifikationsgetrieben, nicht explorativ-iterativ. Für jede Entwicklungsstufe wurde eine umfangreiche Spezifikation erstellt, die dann implementiert wurde.

Typischer Zyklus:

  1. Spezifikationserstellung: Diskussion technischer Ansätze, Architektur, UI-Design, Komponenten-Interaktionen, funktionale Ziele
  2. Implementierung: LLM-gestützte Code-Generierung basierend auf Spezifikation
  3. Anpassungsrunde: Feinjustierung und Bugfixes
  4. Gesamt pro Iterationsstufe: ~1 Stunde

Die initiale Spezifikation war besonders umfangreich und nahm etwa eine Stunde in Anspruch. Die Gesamtentwicklungszeit über alle Iterationsstufen betrug ca. 3 Stunden, plus 30 Minuten für Docker-Deployment (komplexere Requirements).

5.2 Warum Spezifikationen statt Micro-Prompting?#

Die bewusste Entscheidung für hochwertige, detaillierte Spezifikationen basierte auf der Erkenntnis, dass kleinteiliges Micro-Prompting zu fragmentierter, inkonsistenter Entwicklung führt. Wenn Komponenten in vielen kleinen Prompt-Schleifen entwickelt werden:

  • Fehlt der Gesamtkontext
  • Entstehen inkonsistente Namenskonventionen
  • Wird die Architektur unstrukturiert
  • Steigt der Koordinationsaufwand

Umfangreiche Spezifikationen ermöglichen dem LLM:

  • Verständnis der Gesamtarchitektur
  • Konsistente Implementierung über Komponenten hinweg
  • Antizipation von Schnittstellen
  • Kohärente Namensgebung und Strukturierung

5.3 KISS-Prinzip als Gegengewicht#

Bei einer bereits komplexen Lösung (Multi-Agenten-System, Pattern-Bibliotheken, sichere Code-Ausführung) war das KISS-Prinzip (Keep It Small and Simple) eine bewusste Strategie zur Vermeidung von Overengineering.

Konkrete Entscheidungen:

  • Keine übermäßige Abstraktion (z.B. keine Factory-Patterns für einfache Objekt-Erstellung)
  • Direkte Funktionsaufrufe statt komplexer Event-Systeme
  • Sequentielle Workflows statt asynchroner Architekturen
  • Klare Datenmodelle statt verschachtelter Dictionaries

6. Methodische Erkenntnisse: Übertragbare Learnings#

6.1 Intent→Plan→Execute als bewährtes Architektur-Pattern#

Die dreistufige Trennung erwies sich als robust für komplexe NLP-gesteuerte Systeme:

Vorteile:

  • Klare Verantwortlichkeiten pro Service
  • Bessere Testbarkeit (jede Phase isoliert testbar)
  • Gezieltere Fehlerbehandlung
  • Möglichkeit für Dry-Runs (Plan erstellen ohne Ausführung)

Übertragbarkeit:
Dieses Pattern ist nicht spezifisch für Chart-Generierung. Es eignet sich für beliebige Systeme, bei denen natürlichsprachige Anfragen in technische Aktionen übersetzt werden müssen:

  • Code-Generierungs-Tools
  • Automatisierungs-Workflows
  • Datenbank-Query-Systeme
  • Konfigurationsmanagement

6.2 Pattern-Bibliotheken stabilisieren Code-Generierung#

Die Integration von Code-Patterns verbesserte die Stabilität erheblich. Quantitative Aussagen zur Fehlerreduktion sind schwierig, aber qualitativ war der Unterschied deutlich spürbar.

Erkenntnisse:

  • LLMs sind gut darin, aus Beispielen zu lernen und diese zu adaptieren
  • Pattern-Bibliotheken können abstrakte Beschreibungen wirksam unterstützen
  • Anti-Patterns sind ebenso wichtig wie positive Patterns
  • Die Kombination (LLM-Auswahl + Template-Stabilität) ist robuster als reine Generierung

Übertragbarkeit:
Für Domänen mit bekannten Best Practices und häufigen Fehlermustern (Web-Development, API-Integration, Datenbank-Operationen) sind Pattern-Bibliotheken ein vielversprechender Ansatz.

6.3 LLM-Steuerung übertrifft Heuristiken

Trotz nicht-perfekter Zuverlässigkeit erwies sich LLM-gesteuerte Verarbeitung als überlegen gegenüber regel-basierten Heuristiken.

Beispiel Multi-Sheet-Handling:
Eine Heuristik könnte sein: “Wenn ‘alle’ im Text → verarbeite alle Sheets”. Aber Nutzer formulieren vielfältig:

  • “für alle Sheets”
  • “jedes Sheet”
  • “sämtliche Tabellen”
  • “alle Daten”
  • “erstelle überall Charts”

LLMs generalisieren besser über diese Variationen. Die Herausforderung bleibt: 100% Zuverlässigkeit ist schwer zu erreichen. Aber 90-95% mit LLM ist praktikabler als 70% mit Heuristiken, die für neue Formulierungen ständig erweitert werden müssen.

6.4 Retry-Logik und Selbstkorrektur#

Die implementierte fix_code() Methode ermöglicht LLM-basierte Fehlerkorrektur:

for attempt in range(max_retries):
    try:
        fig = execute_code(code, df)
        return fig
    except Exception as e:
        if attempt < max_retries:
            code = llm.fix_code(
                failed_code=code,
                error_message=str(e),
                df_metadata=metadata
            )

Erkenntnisse:

  • Funktioniert für Standardfehler (TypeError, KeyError, AttributeError)
  • Reduziert Fehlerrate moderat (qualitativ: “etwas nach unten gedrückt”)
  • Erreicht keine 100% Zuverlässigkeit
  • Gefahr von Retry-Loops bei systematischen Problemen

Übertragbarkeit:
Retry mit LLM-Korrektur ist sinnvoll für:

  • Syntaktische Fehler
  • Einfache logische Fehler
  • Falsche API-Verwendung

Weniger geeignet für:

  • Komplexe Algorithmus-Probleme
  • Performance-Optimierungen
  • Architektonische Fehler

6.5 Beispiele kompensieren Model-Schwächen#

Das verwendete lokale LLM Qwen3-30B-A3B-Instruct-2507 bewältigt große Kontexte (bis zu 250.000 Tokens), zeigt aber Fehler bei der Code-Generierung.

Die umfangreichen Pattern-Bibliotheken kompensierten diese Schwäche erfolgreich. Dies deutet auf eine allgemeine Strategie hin: Für schwächere Modelle können umfangreiche, hochwertige Beispiele im Kontext die Leistungslücke zu stärkeren Modellen verringern.

7. Herausforderungen und Grenzen#

7.1 Multi-Sheet-Handling#

Die Unterscheidung zwischen Anfragen für einzelne vs. mehrere Grafiken war anfangs problematisch. Nutzer-Formulierungen sind vielfältig:

  • “Erstelle einen Chart” (singular, aber welches Sheet?)
  • “Für alle Sheets” (klar plural)
  • “Zeige die Daten” (unklar)

Lösung:

  • Intent-Patterns mit expliziten Trigger-Wörtern
  • Kontextuelle Analyse (wie viele Sheets verfügbar?)
  • Confidence-Scoring für ambige Fälle

7.2 Semantische Farb-Zuordnung#

Natürlichsprachige Farbangaben wie “grün für positive Werte” erforderten zusätzliche Logik. Das implementierte SemanticColorHelper-Modul:

  • Erkennt ordinale Skalen (Likert-Skalen mit “sehr zufrieden” bis “sehr unzufrieden”)
  • Extrahiert Farb-Intentionen aus User-Nachrichten
  • Generiert color_discrete_map für kategorie-basierte Zuordnungen

Dies war notwendig, um über einfache positionsbasierte Farbzuweisungen hinauszugehen und semantisch korrekte Visualisierungen zu ermöglichen.

7.3 Zuverlässigkeit: Die 100%-Hürde#

Die fundamentale Limitierung bleibt: Es ist schwierig, Lösungen zu implementieren, die zu 100% funktionieren, nicht nur fast immer.

Beobachtungen:

  • Das System funktioniert für die Mehrzahl der Fälle zuverlässig (geschätzt 85-90%)
  • Edge Cases und ungewöhnliche Datenstrukturen führen zu Fehlern
  • Nutzer-Formulierungen außerhalb des trainierten Spektrums können fehlschlagen

Pragmatischer Ansatz:
Statt perfekte Zuverlässigkeit anzustreben, wurde Fokus auf:

  • Graceful Degradation (Fehler transparent kommunizieren)
  • Retry-Mechanismen
  • Umfangreiche Validierung
  • Klare Fehlermeldungen

gelegt. Dies ist für Lern-Tools und explorative Werkzeuge akzeptabel, würde aber für produktionskritische Systeme nicht ausreichen.

8. Validierung und Status#

8.1 Test-Strategie#

Das Tool befindet sich derzeit in der Erprobungsphase. Die Validierungsstrategie umfasst:

  • Tests mit verschiedenen Excel-Dateien unterschiedlicher Komplexität
  • Multi-Sheet-Dokumente mit bis zu 10+ Sheets
  • Unterschiedliche Datentypen (numerisch, kategorial, Zeitreihen, ordinale Skalen)
  • Verschiedene Nutzer-Formulierungen und Intent-Typen

Bisherige Ergebnisse:

  • Multi-Sheet-Handling funktioniert zuverlässig
  • Charts werden für verschiedene Datentypen korrekt generiert
  • Intent-Erkennung arbeitet präzise für Standard-Formulierungen

8.2 Nächste Schritte#

Das Tool wird aktuell einem breiteren Nutzerkreis zugänglich gemacht, um:

  • Edge Cases zu identifizieren
  • Ungewöhnliche Datenstrukturen zu testen
  • Feedback zu Usability und Funktionalität zu sammeln
  • Die Robustheit weiter zu verbessern

9. Übertragbare Erkenntnisse: Zusammenfassung#

Für zukünftige LLM-gestützte Coding-Projekte lassen sich folgende Prinzipien ableiten:

  1. Multi-Agenten mit klarer Phasentrennung (Intent→Plan→Execute) funktionieren besser als monolithische Ansätze

  2. Spezifikationsgetriebene Entwicklung ist iterativem Micro-Prompting überlegen für kohärente, wartbare Systeme

  3. Pattern-Bibliotheken stabilisieren Code-Generierung erheblich, besonders bei Domänen mit bekannten Best Practices

  4. LLM-Steuerung generalisiert besser als Heuristiken, auch wenn 100% Zuverlässigkeit schwer erreichbar bleibt

  5. Code-Indirektion ist ein vielversprechender Ansatz für LLM-Limitierungen bei numerischen und tabellarischen Aufgaben

  6. Umfangreiche Beispiele im Kontext können Leistungslücken schwächerer Modelle verringern

  7. KISS-Prinzipien sind essentiell als Gegengewicht bei bereits komplexen Systemen

  8. Retry mit LLM-Korrektur reduziert Fehler moderat, ersetzt aber keine solide Grundarchitektur