No related posts found.
Als Observability wird die Fähigkeit beschrieben, den internen Zustand eines (komplexen) Softwaresystems allein durch seine externen Ausgaben zu verstehen. Observability versetzt uns in die Lage, in Zukunft Antworten auf uns heute unbekannte Fragen zu geben, die wir an den internen Zustand eines Softwaresystems haben könnten (sogenannte unknown Unknowns).
Für uns Softwareentwickler bedeutet das, dass wir den internen Zustand unserer Microservices – aber auch die Kommunikation zwischen ihnen – durch Betrachtung der Ausgaben dieser Systeme erkennen können, ohne Änderungen an der Software zu deployen.
In der Literatur werden diese Systemausgaben gern in folgende „drei Säulen“ der Observability geclustert:
- Metriken bieten einen aggregierten Blick auf Zustand und Performance eines Systems über die Zeit. Es handelt sich dabei um numerische, annotierte Datenpunkte, die in festen Intervallen eingesammelt und zentral gespeichert werden (z. B. in Prometheus).
- Traces verfolgen eine Anfrage von Anfang bis Ende durch ein verteiltes System und zeichnen Operationen innerhalb eines Systems sowie die Kommunikation zwischen Systemen auf.
- Logs liefern feingranulare Informationen über individuelle Ereignisse. Sie sind inhaltlich unstrukturiert, können aber in strukturierter Form ausgegeben (JSON) und über Metadaten angereichert werden (Instanz, Version, Kritikalität).
Wer sich auf diese drei Säulen beschränkt, greift allerdings in dem Anspruch zu kurz, den internen Zustand einer Software ausschließlich von außen herleiten zu können. Charity Majors [1], bekannt als „Queen of Observability“ sagt: „There are no three pillars of observability.“ Jegliche nützliche Daten sollten Teil der Software-Observability sein. Dazu gehören z. B. auch JVM Tracer wie der Java Flight Recorder oder externe Webseitenprüfer wie der Prometheus Blackbox Exporter.
Wichtig ist: Nur gemeinsam ergeben die Daten ein umfassendes Bild des Verhaltens eines Systems:
- Über Metriken erkennen wir eine Anomalie.
- Mit Traces kann die verursachende Operation identifiziert werden.
- Logs liefern die Details zur Operation.
Um in der Fehleranalyse Metriken, Traces und Logs korrelieren zu können, müssen diese mit korrelierenden Metadaten annotiert werden. Und da wir von „unkown Unknowns“ sprechen, sollten diese Metadaten möglichst vielfältig sein, denn wir wissen nicht, welche Fragen wir in Zukunft an die Software haben werden.
So wie die Definition, welche Art von Daten eigentlich Teil der eigenen Software-Observability sind, ist auch die Wahl der Observability-Tools einem ständigen Wandel unterlegen. In der Vergangenheit haben Open Source, aber auch kommerzielle Tools meist nur eine der drei Säulen bedient. Das lag sicherlich an den fehlenden Standards, was sich zuletzt aber geändert hat. Hier schafft der OpenTelemetry-Standard Abhilfe.
Tracing mit OpenTelemetry
OpenTelemetry (OTEL) ist ein Projekt der Cloud Native Computing Foundation (CNCF), das aus den Vorgängern OpenTracing und OpenCensus hervorgegangen ist. Beide waren angetreten, um einen einheitlichen Weg zu schaffen, Code zu instrumentieren und Telemetriedaten einheitlich an ein Backend zu schicken. Keines der Projekte konnte sich wirklich durchsetzen, was sich mit deren Zusammenlegung geändert hat. OpenTelemetry ist der Standard für Distributed Tracing, schickt sich inzwischen aber an, Log-Shipping und Metriken neu zu standardisieren.
Das Distributed Tracing mit OpenTelemetry baut sich aus Spans auf. Ein Span wird über ein JSON-Dokument definiert und repräsentiert eine Transaktion innerhalb eines Softwaresystems. Das kann ein HTTP-Aufruf sein oder ein Datenbankzugriff. Spans können eine beliebige Granularität haben. Sie sind hierarchisch aufgebaut, haben einen Start- und Endzeitpunkt und werden (system-)übergreifend zu einem Trace zusammengefasst (Listing 1).
{
"name": "hello",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "051581bf3cb55c13"
},
"parent_id": null,
"start_time": "2025-04-29T18:52:58.114201Z",
"end_time": "2025-04-29T18:52:58.114687Z",
"attributes": {
"http.route": "/users/{id}"
},
"events": [
{
"name": "Guten Tag!",
"timestamp": "2025-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
}
]
}
Beim verteilten Tracing werden die Trace-IDs via HTTP-Header von System zu System weitergereicht. Meist übernimmt das Tracing-Framework diese Manipulation der HTTP Requests transparent. Jedes der an einem verteilten Trace beteiligten Systeme liefert seine Spans an einen zentralen OTEL-Empfänger, der aus den Informationen ein Gesamtbild zusammenpuzzelt (Abb. 1).

Interessant wird es, wenn Spans mit Attributen angereichert werden. OpenTelemetry definiert einen Standardkatalog an Attributschlüsseln und -werten [2] in diversen Kategorien, z. B. für HTTP- oder Datenbanktransaktionen. In diesem Standardkatalog befindet sich auch ein eigener Abschnitt für GenAI-Attribute. Dazu gehören natürlich das in einer GenAI-Operation genutzte Modell (gen_ai.request.model), aber auch die LLM-Operation (gen_ai.operation.name) und die verbrauchten Tokens (gen_ai.usage.total_tokens) (Listing 2).
{
"attributes": {
"gen_ai.request.model": "google/gemini-2.5-flash",
"gen_ai.system": "openai",
"gen_ai.request.temperature": "0.8",
"gen_ai.response.id": "gen-1762722252-LMWMkgi5DkSwFhFaBmu3",
"gen_ai.response.finish_reasons": [ "STOP" ],
"gen_ai.operation.name": "chat",
"gen_ai.usage.input_tokens": "6518",
"gen_ai.usage.total_tokens": "6958",
"gen_ai.response.model": "google/gemini-2.5-flash",
"gen_ai.usage.output_tokens": "440"
}
}
Mittels dieser Attribute lassen sich die gängigen LLM-Operationen im OTEL-Empfänger grafisch aufbereiten. Bei LLM-Anwendungen können wir auf den ersten Blick erkennen, wo Latenz verloren geht, welche Prompts generiert werden, welche RAG-Dokumente selektiert wurden und – durch die aufgezeichnete Token-Nutzung – wie viel uns eine Transaktion gekostet hat.
Observability in LLM-Anwendungen
Zu Beginn eines LLM-Entwicklungsprojekts wird gern viel Zeit auf die Frage verwendet, welches LLM-Modell das am besten geeignete sei. Mit Fortschritt des Projekts wird die Irrelevanz dieser Eingangsfrage immer deutlicher, denn das Modell und der Modellanbieter wurden schon dreimal aktualisiert oder komplett gewechselt: Die wahren Probleme der Entwicklung von LLM-Anwendungen treten an anderer Stelle zutage. Hugo Bowne-Anderson und Stefan Krawczyk beschreiben in [3], wie sich der Entwicklungszyklus bei LLM-Anwendungen anpassen muss, und stellen u. a. folgende Bedingungen für einen erfolgreichen LLM-Entwicklungsprozess auf:
- Möglichst vollständige Observability von Anfang an ist entscheidend, um Probleme zu diagnostizieren.
- Synthetische Daten helfen, den Entwicklungsprozess zu beschleunigen, bevor echte Nutzer mit dem System interagieren.
- Strukturierte Evaluationsmethoden helfen Entwicklungsteam eher dabei, besser zu werden als rein intuitionsgetriebene Verbesserungen.
„In AI systems, evaluation and monitoring don’t come last – they drive the build process from day one“, heißt es bei Bowne-Anderson und Krawczyk.
Im Unterschied zum klassischen Entwicklungsprozess kommen der Überwachung und Evaluierung einer LLM-Anwendung „in freier Wildbahn“ ein wesentlich größerer Anteil zu. In klassischen deterministischen Anwendungen reichen Unit-, Integrations- und Akzeptanztests aus, um die Funktionen einer Anwendung zu testen. Mit der Integration nichtdeterministischer LLMs in den Anwendungscode entsteht ein Portal in die chaotische Entropie der echten Welt. Das führt dazu, dass man LLM-Anwendungen unweigerlich in wesentlich mehr Experimentierschleifen entwickeln muss als klassische Software – denn wir können nur begrenzt vorhersagen, wie das LLM sich verhalten wird. Deshalb ist es für das Gelingen eines LLM-Softwareentwicklungsprojekts essenziell, von Tag eins an ein umfassendes Tracing der Anwendung zu haben.
„You can’t fix what you can’t see“: Der Grundsatz der frühen Observability im LLM-Entwicklungszyklus gilt unabhängig von der eingesetzten LLM-Architektur. Je komplexer und eventuell verteilter diese ist, desto dringender wird er jedoch:
- One-Shot: Ein LLM Call mit einem System- und optional einem User-Prompt. Die Antwort wird von der Anwendung verarbeitet, komplexe Antworten werden als Structured Output im JSON-Format ausgegeben. Hinsichtlich der Observability sind die eingehenden Prompts, das gewünschte JSON-Antwort-Schema (wenn vorhanden) und natürlich die LLM-Antwort von Interesse.
- Retrieval-augmented Generation (RAG): Vor dem LLM-Call werden aus einer Vektordatenbank relevante Dokumente extrahiert. Diese werden dem LLM im Prompt als Kontext mitgegeben, um daraus eine Antwort zu generieren.
- Function Calls: Das LLM arbeitet wie in der One-Shot-Architektur, hat jedoch zusätzlich Tools für das Function Calling registriert. Diese Tools kann das LLM nutzen, um auf Daten zuzugreifen, die nicht in seinem Weltwissen vorhanden sind und die ihm die Anwendung auf Wunsch bereitstellt (z. B. Produkte, Kundendaten, Wetter). Hier interessierten uns zu den Daten oben die registrierten Tools und die erfolgten Tool-Calls.
- Agentic Workflow: Im Endeffekt ist das eine Mischung der beiden vorher genannten Architekturen auf Steroiden: Ein großer Prompt wird in kleinere dedizierte Aufgaben aufgebrochen. Die Übergabe zwischen den einzelnen Prompts geschieht mittels Anwendungscode. Die Anwendung hat die Möglichkeit, Daten in einem Context zwischen den Prompts zu transportieren. Die Workflows können auch verteilt ablaufen. Hier interessiert uns zusätzlich noch die Übergabe von Daten zwischen den einzelnen LLM-Calls und gegebenenfalls die verteilte Anwendungsausführung.
Üblicherweise werden im verteilten Tracing nur Stichproben aufgezeichnet (Sampling), d. h., nur ein gewisser Prozentsatz (zwischen einem und zehn Prozent, je nach Volumen) aller Transaktionen wird an den OTEL-Empfänger geschickt. Beim Sampling in verteilten Umgebungen wird darauf geachtet, dass es auch über alle beteiligten Microservices konsistent ist, also ein einmal aufgezeichneter Trace auch von allen beteiligten Microservices aufgezeichnet und verschickt wird.
Die Erfahrung hat jedoch gezeigt, dass es sich in LLM-Anwendungen anbietet, das Sampling komplett zu deaktivieren. So sind wir nämlich in der Lage, Nutzersessions vollständig nachzuvollziehen. Bezogen auf das Beispiel eines Chatbots möchten wir für eine Nutzersession den gesamten geführten Dialog und nicht nur Teile daraus nachvollziehen können.
Zum Glück lässt sich in unser aller Lieblingsframework Spring Boot Observability und speziell Tracing mit OpenTelemetry sehr leicht aktivieren [6]. Um Span-Definitionen per Java-Annotation (@NewSpan) steuern zu können, muss die Property management.observations.annotations.enabled aktiviert und die spring-boot-starter-aop-Abhängigkeit in die Anwendung inkludiert werden, da die Annotationen als AspectJ Pointcuts implementiert sind. Nutzen wir jetzt Spring AI für den LLM-Zugriff in unserer Anwendung, integriert sich Spring AI nahtlos in die Spring Observability.
Langfuse liebt Spring AI
Doch wohin schicke ich denn nun meine OTEL-Traces und Spans? Jeder Observability-Anbieter (Elastic, Grafana, New Relic, Data Dog) hat einen OTEL-Empfänger im Angebot und kann OTEL-Traces entsprechend aufbereiten. Bei LLM-Anwendungen haben wir aber spezielle Anforderungen an einen OTEL-Empfänger:
- Traces sollen mit einem Fokus auf LLM-Aufrufe aufbereitet werden, denn uns interessieren vor allem die Interaktionen mit dem LLM (Prompts und Tools).
- Traces sollen in einer Session-Übersicht aufbereitet sein, sodass ich Konversationen zwischen Nutzer und z. B. einem Chatbot nachvollziehen kann.
- Die Darstellung soll die verschiedenen LLM-Architekturen (One-Shot, RAG, Agentic Workflows) unterstützen und die Ansicht an diese anpassen.
All das können die verbreiteten OTEL-Empfänger nicht (oder nur zum Teil) leisten, da sie sich bisher (meist) auf die Darstellung verteilter Traces konzentriert haben. Wie so oft in der Informatik gilt deshalb für LLM-Traces: Use the right tool for the job! Hier kommen neue Anbieter wie Arize Phoenix [4] oder Langfuse [5] ins Spiel.
Langfuse (Abb. 2, Kasten: „Observability am Beispiel MidMind“) ist ein Open-Source-Produkt des gleichnamigen Berliner Start-ups und erfreut sich in der LLM-Python-Welt schon länger großer Beliebtheit. Es erfüllt die oben genannten Punkte voll und hat inzwischen auch einen OTEL-Empfänger spendiert bekommen. Damit lässt es sich ideal mit Java-Anwendungen und Java-LLM-Frameworks wie Spring AI kombinieren.

Langfuse ist Open Source und wird in einem Freemium-Modell vertrieben. Der Kern der Anwendung und die Hauptfeatures sind Open Source, bestimmte Zusatzfunktionen benötigen eine kostenpflichtige Lizenz. Die Anwendung lässt sich on Premise betreiben, ein Cloud-Dienst wird ebenfalls angeboten. Zum Testen und Kennenlernen oder für kleinere Projekte kann ich das Freikontingent im Cloud-Offering sehr empfehlen.
Observability am Beispiel MidMind
Was sind denn das für merkwürdige Baby-Beispiele in den Screenshots und den Tests? Die Beispiele stammen aus der digitalen Hebammen-App MidMind, die ich für meine Frau (Hebamme) entwickelt habe. Als einfache RAG-Anwendung auf Basis von Spring AI hilft sie ihr, die Vielfalt an Wissen, Leitlinien und Vorschriften bei Fragen rund um Geburt und Wochenbett instantan zu durchsuchen und zusammenzufassen.
Das Tool hat seine Stärken im Darstellen von Spans innerhalb eines Traces auf einem System und glänzt dort mit Detailreichtum hinsichtlich LLM-Attributen. Über die Session-Übersicht lassen sich sehr schöne User-Sessions (im Beispiel ein E-Commerce-Chatbot, Abb. 3) verfolgen. Über eine User-Übersicht kann ich weitere Sessions eines Nutzers aufrufen. Langfuse möchte aber noch viel mehr sein als ein OTEL-Empfänger und bietet Funktionen zum Einsammeln von Nutzerfeedback, zur Promptoptimierung und zum Kategorisieren von Sessions in Datasets an.

Die Integration in Spring Boot bzw. Spring AI ist denkbar einfach [6]. Nachdem die OpenTelemetry-Dependencies eingebunden sind, muss nur noch der OTEL-Endpunkt von Langfuse konfiguriert werden. Für detailreichere OTEL-Span-Attribute kann der im Langfuse Onboarding empfohlene ObservationFilter implementiert werden. Und schon kann fleißig getract, beobachtet und analysiert werden.
Der klare Nachteil der Lösung ist, dass Langfuse eher eine LLM-Insellösung ist. In größeren Softwarelandschaften ist meist ein gemeinsamer Tracer konfiguriert, um Tracing in verteilten Systemlandschaften abzubilden. Ein solches Set-up ist sinnvoll und wichtig. Dort könnte man Langfuse als zweiten OTEL-Empfänger in einer Anwendung konfigurieren, was ein wenig Custom-Code bedeuten würde.
LLM as a Judge
Ich hatte eingangs dargestellt, dass neben möglichst vollständiger Observability synthetische Daten und strukturierte Evaluationsmethoden helfen, den Entwicklungsprozess zu beschleunigen. Zu Beginn eines LLM-Entwicklungsprojekts beweist man seine Hypothese mittels exemplarischer Daten anhand eines ersten Prompts. Das muss gar nicht notwendigerweise im Anwendungscode geschehen, das API des LLM-Modellanbieters reicht meistens aus.
Ist man in die Anwendungsentwicklung eingestiegen, lassen sich für einen ersten Stresstest der aufgestellten Hypothese – ausgehend von den exemplarischen Daten – mittels einfacher Prompts synthetische Testdaten für die eigene Anwendung generieren. Beim Generieren dieser Daten sollten folgende Informationen miteinfließen:
- Personas: Wer ist die Zielgruppe meiner Anwendung und wie wird diese benutzt? Personas lassen sich ideal mit LLMs simulieren. Ist der Nutzer zögerlich, gibt er immer den vollen Kontext seiner Anfrage mit oder referenziert er frühere Angaben?
- Seed Queries: Um schon in dieser frühen Phase möglichst viel Entropie in den synthetischen Testdaten zu produzieren, sollte der Prompt keiner festen Struktur folgen, sondern die Anfrage sich aus Seed Queries erzeugen („Abo kündigen“, „Versandkosten“). Das LLM erzeugt dann mittels der definierten Persona einen individuellen Testdatensatz.
Schon wenige synthetische Testdaten können große (und dann offensichtliche Bugs) in der LLM-Interaktion der Anwendung frühzeitig aufdecken. Observability und vor allem Tracing hilft in dieser Phase, Schwächen im System frühzeitig zu erkennen und zu beheben.
Aber wie kann ich die synthetischen Daten strukturiert evaluieren? Da wir ja neben einer nichtdeterministischen Anwendungskomponente (dem LLM) nun auch vom LLM generierte Testdaten haben, können wir Ein- und Ausgabe unserer Tests nur schwer mit einer klassischen deterministischen Prüfung versehen. Deshalb sollten wir für die Prüfung auch ein LLM zurate ziehen und ihm die Entscheidung überlassen, ob ein Test erfolgreich war oder nicht.
Spring AI verpackt dieses Evaluation Testing in Unit-Test-Cases. Das Framework stellt einen eigenen FactCheckingEvaluator [7] zur Verfügung, der mittels eines LLM die Ausgabe eines anderen LLM auf einen gegebenen Fakt überprüft. Als Beispiel:
- LLM-Ausgabe: „Ein 9 Tage alter Säugling sollte etwa 1/6 seines aktuellen Körpergewichts pro Tag als Trinkmenge erhalten und benötigt mindestens 8 bis 12 Mahlzeiten in 24 Stunden.“
- Zu prüfender Fakt: „8-12 of meals per day“
Wir können sofort den Vorteil dieses „LLM as a Judge“ erkennen: Wir benötigen keine syntaktische Prüfung der LLM-Ausgabe, sondern können deren Semantik prüfen lassen (in diesem Beispiel sogar sprachübergreifend).
LLM Knowledge Cutoff
Lässt man LLM die Ausgaben der eigenen Anwendung bewerten, muss man je nach Anwendungsfall den Knowledge Cutoff des Weltwissens des genutzten LLM bedenken. Dieser liegt meist 12 bis 18 Monate vor Erscheinungsdatum des Modells. Am Beispiel eines Chatbots für die Produktsuche führt dieser Knowledge Cutoff dazu, dass dieser aktuell weder das iPhone 17 noch das iPhone 16 kennt. Weder die aktuellen ChatGPT- noch Google-Gemini-Modelle sind entsprechend aktuell.
Das kann dazu führen, dass der Chatbot sich standhaft weigert, nach diesen „Fantasieprodukten“ zu suchen. Wenn man dem Chatbot aber das Wissen über die neuen Apple-Modelle in seinem Kontext mitgibt, sind es keine Fantasieprodukte mehr. Allerdings muss man dieses Wissen auch auf das LLM übertragen, das als Judge agiert. Denn sonst erhalten Unterhaltungen, die sich um das iPhone 17 drehen, eine schlechte Bewertung, da der Judge meint, der Chatbot würde Fantasieprodukte von Apple halluzinieren …
Spinnt man den Gedanken des LLM as a Judge konsequent weiter, lässt sich auch die Qualität von Interaktionen in unterschiedlichen Dimensionen bewerten (in Schulnoten oder in Prozent). Neben der Korrektheit der Antworten (Kasten: „LLM Knowledge Cutoff“) könnte dazu auch die Tonalität oder Struktur der Antwort gehören. Auf diese Bewertungen können wir dann Akzeptanzkriterien legen und diese vor einem Anwendungs-Deployment testen.
Auf solche Gedankenspiele ist das Spring-AI-Testing-Framework allerdings nicht ausgelegt bzw. man müsste sich eine entsprechende Erweiterung dazu bauen. Als Java-Entwickler möchten wir uns aber auf unseren Anwendungscode konzentrieren („Fertigungstiefe verringern“) und nicht in Zukunft eine JUnit-Erweiterung pflegen müssen. Wenn wir nun unseren Blick in die Python-LLM-Welt schweifen lassen, stoßen wir relativ schnell auf Promptfoo (Abb. 4). Mit Promptfoo erhalten wir ein umfassendes Testwerkzeug für LLM-Anwendungen, das zum einen die Qualität prüft, aber auch OWASP-Angriffsvektoren [8] testen kann.

Promptfoo kann von Haus aus unterschiedlichste HTTP und WebSocket APIs testen. Standard-APIs wie OpenAI funktionieren direkt ohne eigenen Adapter. Eigene APIs, Protokolle oder komplexe Interaktionen lassen sich über Plug-ins definieren. Diese Provider können jetzt mit Testnachrichten angefragt werden und deren Antworten testet Promptfoo. Am Ende erstellt das Tool einen übersichtlichen Testbericht (Listing 3).
description: "midmind.de"
prompts:
- "{{ prompt }}"
providers:
- id: openai:chat:midmind/app
config:
apiBaseUrl: http://host.docker.internal:8080/v1
apiKey: "-"
defaultTest:
options:
provider: openai:gpt-5-nano
tests:
- vars:
prompt: >
Wieviel Milch sollte ein 9 Tage alter Säugling pro Mahlzeit trinken
und wie viele Mahlzeiten benötigt er in 24h?
assert:
# Literatur
- type: icontains
value: Kapitel 33.4 Tagestrinkmenge
# Content
- type: llm-rubric
value: >
ensure that the output provides a specific
amount of milk in milliliters and the number
of meals per day
- type: factuality
value: 8-12 of meals per day
Interaktionen können sowohl deterministischen Tests (icontains – reguläre Ausdrücke) als auch LLM-gestützten Tests unterzogen werden. Dank standardisierter Prompts schreibt sich ein „factuality“-Faktenchecker sehr schnell, und komplexere Tests können mittels der llm-rubric-Prüfung formuliert werden. Bewertungen einer Antwort bzw. einer Interaktion können mittels des g-eval realisiert werden. Dieser (und weitere *-relevance–)Tests bewerten eine Antwort mit einem Wert zwischen 0 und 1 und schlagen unterhalb eines konfigurierten Schwellenwerts fehl.
Promptfoo kann On Premise aufgesetzt oder – wie Langfuse – als Cloud-Lösung eingekauft werden. Mittels einer zentralen Promptfoo-Instanz können die Ergebnisse unterschiedlicher Läufe vorgehalten und untereinander verglichen werden. Promptfoo kann aber auch ohne zentrale Instanz rein lokal genutzt werden. Die Ergebnisse können z. B. als HTML-Report exportiert werden, der dann aber zugegebenmaßen eher hässlich rudimentär daherkommt. Die Sahnehaube auf dem Kuchen ist aber die bereitgestellte GitHub Action, mit der sich Promptfoo maximal einfach in den eigenen CI/CD-Prozess integrieren lässt. Wichtig ist, die Testsuite vollständig, aber knapp zu halten. Denn nur in der CI-Pipeline lassen sich Probleme frühzeitig entdecken und harte Qualitätsschranken erzwingen.
Tests mit synthetischen Daten erzeugen eine Baseline der Qualität unserer Anwendung. Aber sie bilden nicht die reale Welt ab und deshalb ist es wichtig, echte Nutzer auf/in die Anwendung zu bekommen. Nur so können wir:
- echtes Nutzerverhalten mit dem synthetisch Erzeugten abgleichen,
- gewählte Personas der Realität anpassen und
- die Bewertungen von Interaktionen mit echten Businesskennzahlen rückschließen.
Um möglichst schnell echte Nutzer auf die Anwendung zu bekommen, sind je nach Anwendungsfall mehrere Szenarien denkbar:
- Shadow-Traffic: Live-Traffic wird von der bestehenden Anwendung auf die neue LLM-Anwendung gespiegelt. Die ideale Möglichkeit, um Unterschiede zwischen alter und neuer, LLM-basierter Anwendungslogik zu analysieren.
- Interne Testgruppe: Ein Friendly-User-Test mit einer besonders leidensfähigen, aber wohlgesonnenen Testgruppe. Neben dem eigenen Team vielleicht noch weitere Kolleg:innen.
- Real User Testing: Eingeladene, eventuell betriebsfremde Nutzer erledigen definierte Aufgaben in der LLM-Anwendung unter Laborbedingungen. Die Tests werden aufgezeichnet und können gemeinsam mit dem Entwicklungsteam ausgewertet werden.
- A/B-Tests: Tests mit echten Nutzern bergen das größte Risiko, können allerdings auch die spannendsten Nutzerinteraktionen erzeugen.
Für synthetische und echte Nutzertests kann es von Vorteil sein, sie z. B. über einen HTTP-Header zu markieren und etwa als Langfuse-Tag zu speichern. So können die Tests später gezielt ausgewertet oder ausgeblendet werden. Denn: Mit Langfuse als OTEL-Empfänger sind wir in der komfortablen Situation, über das Langfuse REST API auf die Interaktionen zugreifen zu können.
Und damit können wir unseren „Full Circle Moment“ erleben und die aufgezeichneten Interaktionen als Testprompts zurück in unser Test- und Evaluationsframework geben. Wo wir vorher noch synthetische Interaktionen bewertet haben, können wir nun reale Nutzerinteraktionen bewerten lassen und diese Daten für die iterative Weiterentwicklung der Anwendung nutzen.
Red Teaming
Je näher ein LLM-Entwicklungsprojekt dem Livegang rückt, desto mehr stellt sich die Frage nach der Sicherheit der Anwendung. Wenn das nichtdeterministische LLM auf die Entropie (a.k.a Wahnsinn) der Welt trifft, können durchaus amüsante Ergebnisse entstehen (Abb. 5).

Allerdings geben solche Beispiele auch Grund zur Besorgnis, denn der Einsatz von LLMs in Anwendungen öffnet einen latent ungeschützten Angriffsvektor. Die OWASP hat die Angriffsszenarien auf GenAI- bzw. LLM-Anwendungen inzwischen in einen eigenen Katalog ausgelagert, der fortlaufend erweitert wird.
Das Thema ist sehr komplex, und ich bin gespannt, ob und wie wir dem Fortschritt in den Angriffsvektoren in den nächsten Jahren Herr werden. In der Anwendungsentwicklung sollte sich niemand anmaßen, mit diesem Fortschritt mithalten zu können. Deshalb ist es durchaus sinnvoll, sich auf externe Tools zu verlassen, die für uns am Puls der Zeit bleiben.
Promptfoo bietet mit dem Red Teaming [9] einen spannenden Ansatz in diese Richtung. Wir definieren in dem Tool die Angriffsvektoren, auf die wir unsere Anwendung testen wollen. Promptfoo generiert daraufhin mittels eines LLM mögliche Angriffsszenarien. Auf Basis dieser Angriffe evaluiert Promptfoo unsere Anwendung auf mögliche Schwächen (Listing 4). Bei der Generierung der Angriffsszenarien wird auf aktuelles Wissen zurückgegriffen, weshalb sie von Zeit zu Zeit neu generiert werden sollten (Kasten: „Promptfoo-Red-Teaming-Konfiguration“).
description: "midmind.de"
targets:
- id: openai:chat:midmind/app
config:
apiBaseUrl: http://host.docker.internal:8080/v1
apiKey: "-"
defaultTest:
options:
provider: openai:gpt-5-nano
redteam:
numTests: 10
plugins:
- harmful:hate
strategies:
- jailbreak
language: de
Promptfoo-Red-Teaming-Konfiguration
In der beispielhaften Red-Teaming-Konfiguration ist das Promptfoo Plug-in harmful:hate konfiguriert, das prüft, ob die Anwendung dazu verleitet werden kann, Hassrede zu erzeugen. Jedes Promptfoo-Plug-in steuert ein LLM, das darauf trainiert ist, Angriffe auf eine spezifische Schwachstelle zu generieren.
Die Strategie „Jailbreak“ definiert, wie die Angriffe ausgeführt werden sollen. Sie ändert einen Prompt iterativ so lange, bis die Guardrails der Anwendung umgangen werden können.
Taming PII
Was wir im Tracing (aber auch im Logging) unbedingt vermeiden wollen, ist das Speichern personenbezogener Daten (Personally Identifiable Information, PII). Nur so umgehen wir die Büchse der Pandora der Datenschutz-Grundverordnung. Für eine Chatbotanwendung ist es offensichtlich, dass die personenbezogenen Daten anonymisiert werden müssen, bevor sie im Tracing-Storage oder in den Logdateien landen. Aber auch bei One-Shot-LLM-Anwendungen weiß man nie, welche Daten sich in den Anfragen verbergen („Entropie der Welt“).
Für die Anonymiserung von PII gibt es selbstverständlich Cloud-Dienste, was auf den ersten Blick widersprüchlich wirken mag. Wir können aber auch Open-Source-Anwendungen wie DataFog [10] oder Microsoft Presidio [11] selbst hosten. Beide Anwendungen können an die eigenen (Anonymisierungs-)Bedürfnisse angepasst werden und benötigen ausgiebiges Testen, damit nicht zu viele oder zu wenige Entitäten anonymisiert werden. Leider wird bei beiden Anwendungen von Haus aus Englisch als einzige Sprache unterstützt. Das bedeutet, dass man hier ein wenig Hand anlegen muss, um auch eine (sinnvolle) deutschsprachige Anonymisierung zu bekommen.
Beide Dienste haben REST APIs und lassen sich leicht von Spring aus via RestClient ansprechen und aus einem ObservationFilter heraus aufrufen. Wir vermeiden zusätzliche Latenz in unserer Anwendung, da das Versenden der Observation Spans (und damit auch das Filtern) asynchron zu Nutzertransaktion geschieht.
Wrap-up
Observability ist ein integraler Bestandteil des Entwicklungszyklus von LLM-Anwendungen und sollte so früh wie möglich eingebunden werden. Sie ist unerlässlich, um die eigene LLM-Anwendung zu verstehen und zu optimieren. Langfuse hat sich als ideales Werkzeug dafür herausgestellt, wird sich aber in den nächsten Jahren neben den großen OTEL-Anbietern behaupten müssen. Das LLM-Observability-Feld bleibt spannend und wird sich in den nächsten Jahren weiter professionalisieren. Mit OpenTelemetry sind wir aber für die Zukunft gut gerüstet!
Links & Literatur
[1] Majors, Charity; Fong-Jones, Liz; Miranda, George: „Observability Engineering“; O’Reilly Media, 2022
[2] https://opentelemetry.io/docs/specs/semconv/registry/attributes/
[3] https://www.oreilly.com/radar/escaping-poc-purgatory-evaluation-driven-development-for-ai-systems/
[4] https://phoenix.arize.com/
[6] https://langfuse.com/integrations/frameworks/spring-ai
[7] https://docs.spring.io/spring-ai/reference/api/testing.html
[8] https://www.promptfoo.dev/
[9] https://www.promptfoo.dev/docs/red-team/
Author
🔍 Frequently Asked Questions (FAQ)
1. Was bedeutet Observability im Kontext von LLM-Anwendungen?
Observability beschreibt die Fähigkeit, den internen Zustand eines Systems allein durch seine externen Ausgaben zu verstehen. Für LLM-Anwendungen ist sie entscheidend, um Fehlerquellen sichtbar zu machen und das Verhalten komplexer, nichtdeterministischer Systeme nachvollziehen zu können.
2. Welche drei Säulen umfasst klassische Observability?
Die klassischen drei Säulen sind Metriken, Logs und Traces. Metriken geben aggregierte Zustandsdaten, Logs liefern detailreiche Ereignisinformationen, und Traces verfolgen Abläufe systemübergreifend von Anfang bis Ende.
3. Was ist OpenTelemetry und warum ist es für LLMs wichtig?
OpenTelemetry ist ein CNCF-Projekt zur Standardisierung von Telemetrie-Daten (Metriken, Logs, Traces). Es bietet eine einheitliche Instrumentierung und unterstützt mittlerweile spezifische Attribute für GenAI-Operationen wie Modellnamen, Tokenverbrauch und Operationstyp.
4. Welche LLM-Architekturen profitieren besonders von Observability?
Alle gängigen LLM-Architekturen – One-Shot, RAG, Function Calling und Agentic Workflows – profitieren von Observability. Je komplexer die Architektur, desto wichtiger wird die Nachvollziehbarkeit von Promptübergaben und verteilten Workflows.
5. Was leistet Langfuse als OTEL-Empfänger für LLM-Anwendungen?
Langfuse bietet LLM-spezifische Aufbereitung von Traces, unterstützt verschiedene LLM-Architekturen und ermöglicht eine Session-Übersicht. Es lässt sich einfach in Spring AI integrieren und visualisiert u.a. Promptdetails und Tool-Calls.
6. Was ist Promptfoo und wie unterstützt es LLM-Testing?
Promptfoo ist ein LLM-Testframework, das sowohl synthetische als auch realweltliche Prompts prüft. Es unterstützt deterministische und LLM-basierte Tests, ermöglicht Red Teaming und lässt sich via GitHub Actions in CI/CD-Pipelines integrieren.
7. Welche Tools helfen bei der PII-Anonymisierung im Tracing?
Für DSGVO-konformes Tracing eignen sich Tools wie DataFog oder Microsoft Presidio. Diese lassen sich via REST API integrieren und anonymisieren personenbezogene Daten vor dem Speichern.


