LLM-Coding-Experiment: Entwicklung eines generischen Informationsextraktionssystems#

Teil einer Serie über methodische Erkenntnisse aus LLM-gestützten Entwicklungsprojekten

Einleitung und Kontext der Serie#

Über mehrere Monate hinweg wurden verschiedene Software-Tools mit LLM-Unterstützung entwickelt, primär als Lernprojekte zur Exploration von Möglichkeiten und Grenzen des LLM-gestützten Codings. Diese Dokumentation beschreibt eines dieser Projekte: ein generisches System zur Informationsextraktion aus Webseiten, das strukturierte Steckbriefe generiert.

Der Fokus dieser Dokumentation liegt auf methodischen Erkenntnissen zur LLM-gestützten Entwicklung, nicht auf dem Tool selbst. Die beschriebenen Erfahrungen sollen übertragbare Prinzipien für ähnliche Projekte liefern.

Ausgangslage und Motivation#

Das Problem des Vorgängersystems#

Das Projekt entstand nicht als isoliertes Experiment, sondern als Reaktion auf die Limitationen eines bestehenden Systems. Eine “KI-Kartierung” zur Erfassung von KI-Initiativen an deutschen Hochschulen existierte bereits, erwies sich jedoch als zu unflexibel. Das System konnte nur einen Typ von Steckbriefen erzeugen. Für jede neue Kartierungsanforderung – etwa KI-Handreichungen, KI-Services, Forschungsprojekte allgemein oder kooperative Vorhaben zur Erbringung von Diensten an mehreren Hochschulen – wäre erheblicher Programmieraufwand nötig gewesen.

Die Inflexibilität manifestierte sich auf mehreren Ebenen: Neue Felder erforderten Code-Änderungen, unterschiedliche Steckbrief-Strukturen waren nicht vorgesehen, und die Qualitätssicherung der extrahierten Daten war unzureichend.

Zielsetzung des neuen Systems#

Die zentrale Anforderung lautete: ein System, das verschiedene Kartierungen ermöglicht, ohne dass für jeden Steckbrief-Typ individuell programmiert werden muss. Die Flexibilität sollte durch Konfiguration statt durch Code erreicht werden.

Konkret wurden folgende Designziele definiert:

  • Prompt-basierte Konfiguration neuer Datenfelder in Minuten statt Wochen
  • Qualitätssicherung als integraler Bestandteil des Extraktionsprozesses
  • Entity-Normalisierung für übergreifende Suchbarkeit
  • Skalierbare Architektur für parallele Verarbeitung vieler Quellen
  • Admin-Interface für Prompt-Verwaltung, Review und Inline-Editing

Das System sollte auch produktiv eingesetzt werden können, nicht nur als Prototyp dienen.

Funktionsweise des Systems#

Grundkonzept: Vom Markdown zum Steckbrief#

Das System folgt einer klaren Pipeline: Webseite → Markdown → LLM-Extraktion → LLM-Validierung → strukturierte Daten → Steckbrief → statische Website.

Der erste Schritt ist das Crawling der Quell-URL. Da relevante Informationen oft über mehrere Unterseiten verteilt sind – Team-Informationen auf /team, Kontaktdaten auf /kontakt, Details auf /publikationen – crawlt das System bis zu fünf Seiten pro Quelle. Die Priorisierung der Unterseiten erfolgt regelbasiert über Keyword-Scoring: Links mit Begriffen wie “team”, “kontakt” oder “about” erhalten höhere Priorität. Diese regelbasierte Priorisierung war eine bewusste Entscheidung gegen eine LLM-basierte Variante, um Komplexität und API-Kosten zu reduzieren.

Das gecrawlte HTML wird in strukturiertes Markdown konvertiert, das alle Seiten mit Metadaten (URL, Seitentyp, Crawl-Zeitpunkt) zusammenfasst. Dieses konsolidierte Markdown bildet die Grundlage für alle weiteren Extraktionen.

Prompt-basierte Konfiguration#

Der Kern des Systems ist ein prompt-basiertes Konfigurationskonzept. Ein Steckbrief wird als Markdown-Template mit Variablen definiert, beispielsweise:

# {project_name}

**Institution:** {institution}
**Leitung:** {project_lead}

## Beschreibung
{description}

Für jede Variable existiert ein Prompt-Paar:

  • Extract-Prompt: Beschreibt präzise, welche Information aus dem Quelltext extrahiert werden soll. Der Prompt enthält Beispiele für korrekte und inkorrekte Extraktionen, um dem LLM klare Orientierung zu geben.

  • Validate-Prompt: Bewertet die Qualität des Extraktionsergebnisses anhand definierter Kriterien wie Eindeutigkeit, Plausibilität und Übereinstimmung mit dem Quelltext.

Prompts können in Feldgruppen organisiert werden (Basis, Team, Details), die im Steckbrief unterschiedlich dargestellt werden. Zusätzlich unterstützt das System einstufige Dependencies: Ein Prompt kann das Ergebnis eines anderen Prompts als Kontext nutzen, etwa wenn die Abteilung nur sinnvoll extrahiert werden kann, wenn die Institution bereits bekannt ist.

Zwei-Phasen-Extraktion mit Qualitätssicherung#

Der Extraktionsprozess läuft zweiphasig ab, was das zentrale Qualitätsmerkmal des Systems darstellt:

Phase 1 (Extract): Ein LLM extrahiert die Rohdaten aus dem gecrawlten Markdown. Die Temperatur ist niedrig eingestellt (0.1), um konsistente, deterministische Ergebnisse zu erzielen. Der Prompt ist präzise formuliert und enthält Positiv- und Negativbeispiele.

Phase 2 (Validate): Ein separater LLM-Aufruf bewertet die Qualität des Ergebnisses aus Phase 1. Der Validierungs-Prompt erhält das Rohergebnis und den Original-Kontext. Das Ergebnis ist ein strukturiertes Format:

  • Quality-Level: HIGH, MEDIUM, LOW oder INSUFFICIENT
  • Numerischer Score: 0.0 bis 1.0
  • Optionale Anmerkungen: z.B. “Zu vage”, “Widersprüchlich”
  • Bereinigtes Ergebnis oder “INSUFFICIENT”

Die finale Confidence ergibt sich als Minimum aus der Roh-Confidence von Phase 1 und dem Validation-Score aus Phase 2. Felder unterhalb eines konfigurierbaren Schwellenwerts (Standard: 0.6, für kritische Felder wie Projektnamen: 0.8) landen automatisch in einer Review-Queue zur manuellen Prüfung.

Diese Zwei-Phasen-Architektur erwies sich als entscheidend für die Datenqualität. Die Validierungsphase fängt systematisch Fehler ab, die in der Extraktionsphase entstehen – etwa zu vage Formulierungen, Verwechslungen oder Halluzinationen.

Entity-Normalisierung#

Das System normalisiert Entitäten wie Hochschulnamen automatisch. Die Herausforderung: LLMs extrahieren Institutionsnamen inkonsistent. “TU Berlin”, “Technische Universität Berlin” und “TUB” bezeichnen dieselbe Institution, erscheinen aber als unterschiedliche Einträge.

Die Lösung nutzt ebenfalls LLM-Aufrufe. Das System verwaltet eine Datenbank kanonischer Namen mit bekannten Varianten. Bei jeder Extraktion eines Entity-Felds prüft ein LLM-Aufruf, ob der extrahierte Text einer bekannten Entität entspricht.

Die Entscheidungslogik ist confidence-basiert:

  • Confidence > 0.9: Automatische Verlinkung
  • Confidence 0.6-0.9: Review-Queue, Admin entscheidet
  • Confidence < 0.6: Ignorieren

Bei bestätigter Zuordnung wird die neue Schreibweise als Variante gespeichert, sodass zukünftige Extraktionen automatisch zugeordnet werden. Derzeit ist die Entity-Normalisierung für Hochschulen implementiert, perspektivisch auch für Orte, Personen und Technologien vorgesehen.

Technische Architektur#

Gewählter Stack#

Die Architektur basiert auf bewährten Komponenten:

  • FastAPI als Backend-Framework für Admin-API, Public-API und WebSocket-Kommunikation
  • PostgreSQL für persistente Datenhaltung (Sources, Prompts, Extraktionen, Entities)
  • Redis für die Job-Queue mit Priority-Handling
  • Worker-Pool für parallele Verarbeitung (Standard: 3 Worker, 5 parallele LLM-Aufrufe)

Das Admin-Interface nutzt htmx + Alpine.js – eine bewusste Entscheidung für Einfachheit. Kein Build-Step, keine komplexe State-Verwaltung, dennoch reaktive UI mit Inline-Editing und Live-Status-Updates.

Die öffentliche Website wird als statische HTML-Seiten generiert, mit Client-side Search über einen JSON-Index. Dies ermöglicht schnelle Performance ohne Backend-Abhängigkeit für Lesezugriffe.

Robustheitsmuster#

Ein LLM-intensives System stellt besondere Anforderungen an Fehlerbehandlung und Resilienz:

Circuit Breaker: Bei wiederholten LLM-Fehlern (Standard: 10 aufeinanderfolgende Fehler) öffnet der Circuit Breaker und pausiert alle Anfragen für eine konfigurierbare Zeit (Standard: 5 Minuten). Nach dem Timeout wird ein Test-Request gesendet; bei Erfolg schließt der Breaker wieder. Dieses Pattern verhindert, dass ein überlastetes oder ausgefallenes LLM-Backend das gesamte System blockiert.

Retry-Management: Fehlgeschlagene Extraktionen werden mit exponentiellem Backoff wiederholt (2s, 4s, 8s). Nach drei Versuchen wird das Feld als “failed” markiert und landet in der Review-Queue. Ein automatischer Retry erfolgt nach einer Stunde, maximal dreimal täglich.

Parallele Verarbeitung: Der Worker-Pool verarbeitet Jobs aus der Redis-Queue nach Priorität. Ein Semaphore limitiert die gleichzeitigen LLM-Aufrufe, um das Backend nicht zu überlasten. Die Verarbeitung einer Source mit 20 Feldern und Dependencies dauert typischerweise 10-60 Sekunden.

Datenmodell#

Das Datenmodell ist auf die Anforderungen der prompt-basierten Extraktion zugeschnitten:

  • Sources: Gecrawlte Webseiten mit Markdown-Content und Status
  • Prompts: Extract/Validate-Paare mit Feldgruppen und Confidence-Schwellenwerten
  • Categories: Steckbrief-Typen mit Template und aktiven Feldgruppen
  • Extractions: Ergebnisse mit Roh- und validierten Werten, Confidence-Scores, Entity-Links
  • Entities: Kanonische Namen mit Varianten und Metadaten
  • Job-Queue: Priorisierte Verarbeitungsaufträge mit Retry-Tracking

Entwicklungsprozess mit LLM#

Das Phasenmodell#

Der Entwicklungsprozess folgte einem strikten Phasenmodell, das sich als entscheidend für den Projekterfolg erwies:

Phase 1 – Funktionale Diskussion: Intensive Gespräche über Anforderungen und mögliche Lösungsansätze. Verschiedene Architekturen wurden diskutiert, um die Unzulänglichkeiten des Vorgängersystems zu adressieren. Diese Phase endete erst, als klare Konzepte für alle Kernfunktionen definiert waren.

Phase 2 – Architektur-Diskussion: Technische Umsetzungsvarianten wurden diskutiert und bewertet. Welcher Stack? Wie wird die Parallelisierung umgesetzt? Wie sieht das Datenmodell aus? Diese Diskussionen schärften die Konzepte und verhinderten vorschnelle Entscheidungen.

Phase 3 – Grobe Umsetzung: Mit der definierten Architektur wurden große Teile in einem Anlauf implementiert. Die detaillierte Spezifikation ermöglichte es dem LLM, kohärenten Code zu generieren.

Phase 4 – Modulare Verfeinerung: Einzelne Teilbereiche wurden in separaten Sessions mit dem LLM verfeinert. Jede Session hatte einen klaren Fokus (z.B. Entity-Normalisierung, Review-Queue) und eine Teilspezifikation.

Spezifikationstiefe#

Die erstellten Spezifikationsdokumente umfassen drei Hauptdokumente mit zusammen über 50 Seiten:

  • Funktionale Spezifikation: MVP-Scope, Datenflüsse, Qualitätssicherungs-Workflow, Admin-Workflow, Erfolgskriterien
  • Technische Spezifikation: Architektur-Diagramme, vollständige Datenbank-Schemas mit Indizes, Service-Klassen mit Methoden-Signaturen, Code-Beispiele
  • User-View-Spezifikation: Seitenstruktur, Wireframes als ASCII-Art, JavaScript-Logik für Client-side Search, SEO-Anforderungen

Die Spezifikationen enthalten explizite MVP-Abgrenzungen: Was ist im Scope, was explizit nicht, was kann nach Validierung hinzugefügt werden. Diese Klarheit verhinderte Scope Creep während der Implementierung.

Umgang mit Overengineering#

LLMs neigen dazu, zu komplexe Lösungen vorzuschlagen – mehr Abstraktionsebenen, mehr Features, mehr Flexibilität als nötig. Dem wurde auf mehreren Wegen entgegengewirkt:

  • Explizite KISS-Prinzipien in den Spezifikationen
  • Aktives Hinterfragen jedes Architekturvorschlags: “Brauchen wir das wirklich?”
  • Klare Vorgaben bei erkennbarem Overengineering: “Einfachere Lösung bevorzugt”
  • Bewusste Entscheidungen für einfachere Alternativen (htmx statt React, regelbasierte statt LLM-basierte Link-Priorisierung)

LLM als Architektur-Berater#

Interessanterweise stammten einige robuste Patterns aus LLM-Vorschlägen. Der Circuit Breaker etwa wurde vom LLM während der Architektur-Diskussion eingebracht, als es um die Frage ging, wie mit LLM-Backend-Ausfällen umgegangen werden soll.

Das zeigt eine wichtige Erkenntnis: LLMs können nicht nur Code generieren, sondern auch architektonische Best Practices aus ihrem Trainingskorpus einbringen. Die Kunst liegt darin, diese Vorschläge kritisch zu bewerten und nur die wirklich nützlichen zu übernehmen.

Methodische Erkenntnisse#

Übertragbare Prinzipien#

Spezifikation vor Implementierung: Die investierte Zeit in detaillierte Spezifikation zahlt sich durch fehlerfreiere Implementierung aus. Je klarer und vollständiger die Vorgaben, desto besser die Code-Qualität. Der Aufwand verlagert sich von Debugging zu Planung.

Vom Groben zum Feinen: Bei umfangreichen Projekten funktioniert LLM-Coding, wenn man von der Gesamtarchitektur zu Modulen zu Funktionen fortschreitet. Die Architektur muss belastbar sein, bevor Details implementiert werden. Sonst entstehen inkonsistente Module, die später aufwändig integriert werden müssen.

Modulare Verfeinerung: Nach der groben Umsetzung werden Teilbereiche in separaten Sessions verfeinert. Jede Session hat einen klaren Fokus und eine Teilspezifikation. Dies hält den Kontext handhabbar und ermöglicht fokussierte Verbesserungen.

Aktive Steuerung: Der Entwickler muss aktiv steuern, hinterfragen und korrigieren. LLMs sind Werkzeuge, keine autonomen Entwickler. Alle Architekturen und Konzepte müssen kritisch diskutiert werden – sonst kommt es zu vorschnellen Entscheidungen.

Grenzen des Ansatzes#

Skalierung: Bei sehr großen Projekten stößt der Ansatz an Grenzen. Kontext-Fenster sind begrenzt, und die Kohärenz über viele Module hinweg erfordert sorgfältige Planung. Die Modularisierung hilft, löst das Problem aber nicht vollständig.

LLM-Antwortqualität: Die Herausforderung bei der Verwaltung vieler LLM-Aufrufe und der Variabilität der Antwortqualität war erheblich. Die Zwei-Phasen-Extraktion adressiert das Problem, erfordert aber zusätzliche Komplexität.

Debugging: Bei komplexen Fehlern, die über mehrere Module hinweg entstehen, ist manueller Eingriff weiterhin notwendig. LLMs können bei der Fehlersuche helfen, aber die Gesamtsicht behält der Entwickler.

Workflow-Veränderung#

Die Erfahrung mit diesem Projekt hat den Entwicklungs-Workflow verändert. Der Ansatz folgt nun eher Modulen und Funktionsbereichen statt einer linearen Entwicklung. Die Spezifikationsphase erhält deutlich mehr Gewicht, die reine Implementierungsphase wird kürzer und fehlerärmer.

Metriken und Ergebnisse#

Projektumfang#

Das Projekt umfasst:

  • 95 Dateien mit insgesamt 43.346 Zeilen
  • 37 Python-Dateien mit ca. 22.000 Zeilen (16.000 Code-Zeilen)
  • 20 HTML-Templates mit ca. 8.700 Zeilen
  • 3 JavaScript-Dateien für Client-side-Funktionalität
  • SQL-Schemas, YAML-Konfiguration, Dokumentation

Entwicklungsaufwand#

  • Gesamtdauer: ca. 2 Monate
  • Anzahl Sessions: ca. 10
  • Haupt-Iterationen: 3 (grundlegende Architektur-Überarbeitungen)
  • Phasen: Spezifikation → Umsetzung → Deployment → Feinarbeit

Produktivdaten#

  • Konfigurierte Prompts: ca. 100 in 4 Kategorien
  • Verarbeitete Quellen: ca. 100 (Validierungsphase)
  • Verarbeitungszeit pro Quelle: 10-60 Sekunden

Qualitätsergebnisse#

Die Zwei-Phasen-Extraktion erreicht die definierten Erfolgskriterien:

  • Über 80% der Felder werden mit Confidence >0.7 extrahiert
  • Die QS-Schicht filtert unzuverlässige Extraktionen zuverlässig
  • Die Entity-Normalisierung erreicht hohe Auto-Link-Raten bei bekannten Hochschulen
  • Das System funktioniert nach Entwickleraussage “besser als gedacht”

Das System wird aktuell intensiv evaluiert und zeigt sich über verschiedene Kategorien hinweg als erfolgreich. Besonders die Qualitätssicherungsschicht bewährt sich in der Praxis.

Reflexion: Was würde man anders machen?#

Rückblickend haben sich einige Entscheidungen als besonders wertvoll erwiesen, während andere Bereiche Verbesserungspotenzial zeigen.

Bewährte Entscheidungen:

Die frühe Festlegung auf die Zwei-Phasen-Extraktion war richtig. Anfangs erschien der doppelte LLM-Aufruf als Overhead, aber die Qualitätsverbesserung rechtfertigt den Aufwand. Ohne die Validierungsphase wäre die manuelle Nacharbeit erheblich höher.

Die Wahl von htmx + Alpine.js für das Admin-Interface bewährte sich ebenfalls. Die Einfachheit beschleunigte die Entwicklung und reduzierte die Fehlerquellen. Für ein internes Tool ist kein aufwändiges Frontend-Framework nötig.

Verbesserungspotenzial:

Die Prompt-Verwaltung könnte strukturierter sein. Mit 100 Prompts in vier Kategorien wird die Übersicht anspruchsvoll. Ein Versionierungssystem für Prompts und eine bessere Gruppierungslogik wären hilfreich.

Die Testabdeckung ist ausbaufähig. Die Entwicklung fokussierte auf Funktionalität; automatisierte Tests entstanden nicht systematisch. Für ein produktives System sollte dies nachgeholt werden.

Fazit und Ausblick#

Das Experiment demonstriert, dass LLM-gestützte Entwicklung auch bei umfangreichen, architektonisch anspruchsvollen Projekten funktioniert. Die Erfolgsfaktoren sind:

  1. Belastbare Architektur: Die Gesamtstruktur muss vor der Implementierung klar definiert sein. Nachträgliche Architekturänderungen sind aufwändig.

  2. Detaillierte Spezifikation: Je präziser die Vorgaben, desto besser die generierte Code-Qualität. ASCII-Diagramme, Code-Beispiele und explizite Abgrenzungen helfen dem LLM, kohärenten Code zu generieren.

  3. Modularer Ansatz: Vom Groben zum Feinen, mit separaten Verfeinerungs-Sessions pro Modul. Dies hält den Kontext handhabbar.

  4. Aktive Steuerung: Kontinuierliches Hinterfragen von LLM-Vorschlägen und explizite Vorgaben gegen Overengineering sind notwendig.

Die Zwei-Phasen-Extraktion mit integrierter Qualitätssicherung hat sich als robustes Konzept erwiesen. Die Validierungsphase ist nicht nur ein technisches Feature, sondern ein zentrales Designprinzip, das die Zuverlässigkeit des Gesamtsystems erhöht.

Für zukünftige LLM-Coding-Projekte ergibt sich die Empfehlung, mehr Zeit in die Spezifikationsphase zu investieren und Architekturentscheidungen kritisch zu diskutieren, bevor die Implementierung beginnt. Der Aufwand zahlt sich durch effizientere und fehlerärmere Umsetzung aus.