Zum Inhalt

Flex-Kartierung — Architektur

Flex-Kartierung folgt einem container-basierten Schichtenmodell mit klarer Trennung zwischen synchroner Bedienung über die Web-API und asynchroner Verarbeitung in einem Worker-Pool. Aufträge werden über eine Job-Queue entkoppelt, sodass Bedienoberfläche und Pipeline unabhängig voneinander arbeiten. Die Auslieferung der öffentlichen Inhalte ist von der Erfassung getrennt: Sie erfolgt als statische Website gegen ein eigenes Verzeichnis, das von einem Webserver oder CDN ausgeliefert werden kann.

Auf einen Blick

  • Drei Container im Standardbetrieb: Anwendung (FastAPI + Admin-UI + Site-Generator), Worker-Pool und Datenbank, ergänzt um einen Redis-Container für Queue und Cache.
  • Schichten-Trennung: Bedienoberfläche, REST-API, Geschäftslogik (Services), Datenzugriff über Repository-Pattern, Persistenz.
  • Asynchrone Verarbeitung: alle aufwändigen Schritte (Crawl, Extract, Validate, Normalize, Translate, Generate) laufen als priorisierte Hintergrundaufträge.
  • LLM-Aufrufe sind über einen einheitlichen Client gekapselt und durch Circuit Breaker, Retry-Manager und ein worker-übergreifendes Rate-Limit abgesichert.
  • Konfiguration als YAML-Dateien (Kategorien, Prompts, Übersetzungs-Strings) und über Umgebungsvariablen (Backends, Limits, Verhalten).
  • Datenbankschema mit Schema-Migrationen über Alembic; jede Verarbeitungsstufe ist als eigenes, getrennt aktualisierbares Feld modelliert.
  • Statischer Website-Export entkoppelt Auslieferung von Erfassung und ermöglicht Betrieb ohne Anwendungs-Backend für Endnutzende.

Komponenten und Workflow

Schichten

Die Anwendung ist in vier logische Schichten gegliedert. Die Bedienoberfläche umfasst die serverseitig gerenderte Admin-Oberfläche und die generierte öffentliche Website. Die API-Schicht stellt eine Admin-API für die Verwaltung sowie eine Public-API für lesenden Zugriff bereit. Die Geschäftslogik kapselt die fachlichen Abläufe in eigenständigen Services: Crawler, Prompt-Engine, Entity-Normalizer, Übersetzungsdienst, Steckbrief- und Site-Generator sowie die Robustheitskomponenten Circuit Breaker und Retry-Manager. Die Datenzugriffsschicht ist nach dem Repository-Pattern aufgebaut und verbirgt SQLAlchemy-Details vor den Services. Die Persistenz besteht aus einer PostgreSQL-Datenbank für alle Stamm- und Verlaufsdaten und einer Redis-Instanz für die Job-Queue.

Pipeline und Datenfluss

Die Verarbeitung einer Quelle ist als Folge entkoppelter Aufträge in der Job-Queue modelliert. Wird eine URL erfasst, entsteht zunächst ein Crawl-Auftrag. Der Crawler holt die Seite, optional zusätzliche verlinkte Unterseiten derselben Domain, konvertiert das HTML in Markdown und schreibt es zur Quelle in die Datenbank. Anschließend werden Extract-Aufträge für jeden Prompt der zugehörigen Kategorie erzeugt. Ein Dependency-Resolver bestimmt die Ausführungsreihenfolge in Wellen, sodass Felder mit Abhängigkeiten erst ausgeführt werden, wenn ihre Quellfelder vorliegen. Jeder Extract-Auftrag löst eine LLM-Anfrage mit dem Markdown als Kontext aus; das Ergebnis wird als Rohextraktion gespeichert und im Anschluss in einer eigenständigen Validate-Phase erneut durch das LLM bewertet. Felder mit Entity-Bezug durchlaufen die Entity-Normalisierung, bei der das LLM einen Vorschlag aus dem bestehenden Entity-Bestand erzeugt; je nach Konfidenz wird automatisch verlinkt oder eine Review-Aufgabe angelegt. Übersetzbare Felder werden anschließend einzeln ins Englische übersetzt, jeweils mit dem vollständigen Steckbrief als Kontextrahmen und mit Schutz für Eigennamen, URLs und Fachabkürzungen. Sobald alle Felder einer Quelle vorliegen, erzeugt der Steckbrief-Generator das Markdown-Dokument aus dem Kategorie-Template; auf Wunsch wird die statische Website neu generiert.

Diagramm

flowchart TB
    User([Bearbeitende])
    Web[(Hochschul-Webseiten)]
    LLM[(LLM-Backend<br/>OpenAI-kompatibel)]

    subgraph Frontend[Bedienoberfläche]
        AdminUI[Admin-Oberfläche<br/>htmx + Alpine.js]
        PublicSite[Statische Website<br/>HTML + Client-Suche]
    end

    subgraph API[FastAPI-Backend]
        AdminAPI[Admin-API]
        PublicAPI[Public-API]
        SiteGen[Site-Generator]
    end

    Queue[(Redis<br/>Job-Queue)]
    DB[(PostgreSQL)]

    subgraph Workers[Worker-Pool]
        Crawler[Crawler-Service]
        PromptEngine[Prompt-Engine<br/>Extract + Validate]
        Normalizer[Entity-Normalizer]
        Translator[Übersetzungsdienst]
        Steckbrief[Steckbrief-Generator]
    end

    subgraph Robust[Robustheits-Schicht]
        CB[Circuit Breaker]
        Retry[Retry-Manager]
    end

    User --> AdminUI
    AdminUI --> AdminAPI
    PublicSite --> PublicAPI

    AdminAPI --> Queue
    AdminAPI --> DB
    PublicAPI --> DB
    SiteGen --> DB
    SiteGen --> PublicSite

    Queue --> Crawler
    Crawler --> Web
    Crawler --> DB
    Crawler --> PromptEngine

    PromptEngine --> DB
    PromptEngine --> Normalizer
    Normalizer --> DB
    Normalizer --> Translator
    Translator --> DB
    Translator --> Steckbrief
    Steckbrief --> DB

    PromptEngine -.-> CB
    Normalizer -.-> CB
    Translator -.-> CB
    CB -.-> LLM
    Retry -.-> LLM
    PromptEngine -.-> LLM
    Normalizer -.-> LLM
    Translator -.-> LLM

Erläuterung

Bearbeitende interagieren ausschließlich mit der Admin-Oberfläche, die Aufträge synchron in die Datenbank und in die Job-Queue schreibt. Der Worker-Pool zieht Aufträge aus der Queue und führt sie in der durch die Job-Priorität bestimmten Reihenfolge aus: Crawl-Aufträge haben die höchste Priorität, gefolgt von unabhängigen Extract-Aufträgen, der Validate-Phase, abhängigen Extract-Aufträgen, der Entity-Normalisierung, der Übersetzung und schließlich der Steckbrief-Generierung. Jede Stufe schreibt ihr Ergebnis in ein eigenes Feld der Datenbank, sodass nachgelagerte Stufen wiederholbar sind, ohne vorherige Stufen erneut auszuführen.

Alle Aufrufe an das LLM-Backend laufen über einen einheitlichen LLM-Client, der pro Worker und global ein Rate-Limit durchsetzt. Dahinter sitzen Circuit Breaker und Retry-Manager: Bei wiederholten Fehlern öffnet der Circuit Breaker und blockiert weitere Anfragen für eine konfigurierbare Pause; transiente Fehler werden über einen exponentiellen Backoff mit Jitter und einer begrenzten Versuchsanzahl gedämpft. Nicht-retry-fähige Fehler (etwa Validierungsfehler) werden bewusst nicht erneut versucht.

Die öffentliche Website wird durch den Site-Generator aus den veröffentlichten Steckbriefen, den Kategoriedaten und den i18n-Strings erzeugt und als reine Dateibäume in ein gemountetes Verzeichnis geschrieben. Sie kann anschließend von einem Webserver oder einem CDN ausgeliefert werden; ein Backend-Zugriff ist für Endnutzende nicht erforderlich. Pro Sprache entsteht eine eigene URL-Struktur; die Suchfunktion arbeitet clientseitig auf einem mitausgelieferten JSON-Index.

Rolle des LLM in der Pipeline

Das LLM wird in vier Pipeline-Stufen aufgerufen: Extraktion (eine Anfrage pro Prompt mit dem gecrawlten Markdown als Kontext), Validierung (eine Anfrage zur Bewertung des Rohergebnisses, die eine Qualitätsklasse, einen Score und eine Begründung liefert), Entity-Normalisierung (eine Anfrage pro Entity-Kandidat mit dem bestehenden Entity-Bestand als Bezugsraum) und Übersetzung (eine Anfrage pro übersetzbarem Feld). Es kommen keine Embedder, Vektorindizes oder Reranker zum Einsatz; die Pipeline ist als Kette spezialisierter Prompts modelliert. Das System ist damit bewusst auf einen einzelnen LLM-Endpunkt zugeschnitten und an beliebige OpenAI-kompatible Modelle anschließbar.

Nebenläufigkeit und Konfiguration

Der Worker-Pool führt mehrere Aufträge parallel aus; die maximale Zahl gleichzeitiger Worker und gleichzeitiger LLM-Aufrufe ist getrennt konfigurierbar. Innerhalb des Pools begrenzt ein Semaphor die Zahl gleichzeitiger LLM-Anfragen, ein Token-Bucket die Anfragen pro Minute und ein Mindestabstand zwischen Anfragen die Last auf das Backend. Crawler und Übersetzung verwenden eigene Rate-Limits pro Domain bzw. global. Sämtliche Schwellen, Zeitlimits, Worker-Zahlen, Crawler-Optionen, LLM-Adresse und Modelldaten werden über Umgebungsvariablen gesetzt; Anwendungs-Settings nutzen Pydantic-Validierung. Schema-Änderungen an der Datenbank werden über Alembic-Migrationen verwaltet.

Deployment

Standardbetrieb ist ein Docker-Compose-Verbund aus Anwendungs-Container (FastAPI mit Admin-Oberfläche und Site-Generator), Worker-Container (Hintergrundverarbeitung), Datenbank-Container (PostgreSQL) und Cache-/Queue-Container (Redis). Anwendung und Worker teilen ein gemeinsames Verzeichnis für die generierte statische Website; Anwendungscode, Templates und Migrationen werden schreibgeschützt eingebunden.

Technologien

  • Anwendungsframework: FastAPI, Uvicorn, Pydantic, Pydantic-Settings.
  • Datenzugriff und Migrationen: SQLAlchemy (asyncio), asyncpg, Alembic, PostgreSQL.
  • Queue und Cache: Redis (asynchroner Client).
  • Crawler: httpx, BeautifulSoup, lxml, html5lib, markdownify, urllib robotparser.
  • Bedienoberfläche: Jinja2, htmx, Alpine.js.
  • LLM-Anbindung: OpenAI-kompatible Chat-Completions-Schnittstelle über httpx.
  • Site-Generierung: Jinja2, markdown2, statische Auslieferung über Webserver oder CDN.
  • Beobachtbarkeit: structlog (strukturierte JSON-Logs), prometheus-client, Health-Endpunkt.
  • Betrieb: Docker, Docker Compose.