AI Observability & LLM Tracing
Observe LLM applications in production: distributed tracing of model and retrieval calls, LangFuse and OpenTelemetry GenAI conventions, span attributes, and cost dashboards for Java teams.
On this page
Observing an LLM application needs everything normal observability provides plus concerns unique to AI: cost per call, retrieval quality, and output quality that a fast, error-free system can still get wrong. This tutorial covers LLM-specific tracing with OpenTelemetry and tools like LangFuse, building on Spring AI observability.
Key Takeaways
- LLM observability adds cost, retrieval quality and output quality to the usual signals.
- Trace the full path as connected spans — retrieval, generation, tools.
- OpenTelemetry GenAI conventions standardise LLM span attributes across services.
- LangFuse and similar tools give a purpose-built LLM view; wire them alongside your traces.
What LLM observability adds
A traditional dashboard shows latency, errors and throughput. An LLM system can be green on all three while:
- Spending ten times the necessary tokens on every call.
- Retrieving irrelevant context and answering worse than last week.
- Drifting in output quality after a provider updates a model.
None of these show up as an error. LLM observability is about seeing them.
Distributed tracing of the request path
The single most useful thing: connected spans showing where time and tokens go.
@Observed(name = "rag.request")
public RagAnswer answer(String question) {
// Each step is a child span, so the trace shows the waterfall.
List<Document> context = traced("rag.retrieve", () -> {
var docs = retriever.retrieve(question);
span().tag("chunks", docs.size());
span().tag("top_score", topScore(docs));
return docs;
});
return traced("rag.generate", () -> {
var response = generate(question, context);
span().tag("gen_ai.usage.prompt_tokens", response.promptTokens());
span().tag("gen_ai.usage.completion_tokens", response.completionTokens());
span().tag("gen_ai.request.model", response.model());
return response;
});
}A total latency of 3.5s is not actionable; a trace showing retrieval at 40ms and generation at 3.4s tells you exactly where to optimise.
OpenTelemetry GenAI conventions
Rather than inventing attribute names, follow the OpenTelemetry GenAI semantic conventions so your traces are consistent and portable:
// Standard attribute names mean any OTel backend understands your LLM spans,
// and traces are consistent across every service in your system.
span.setAttribute("gen_ai.system", "openai");
span.setAttribute("gen_ai.request.model", "gpt-4o-mini");
span.setAttribute("gen_ai.request.temperature", 0.2);
span.setAttribute("gen_ai.usage.input_tokens", promptTokens);
span.setAttribute("gen_ai.usage.output_tokens", completionTokens);Export with the OTLP exporter to any compatible backend — see Spring AI observability for the setup.
Purpose-built tools: LangFuse and friends
General observability backends show traces; LLM-specific tools add prompt management, output evaluation and agent trajectory views. LangFuse is a common open-source choice:
// Capture a trace with the model call, retrieval and outcome, so LangFuse can
// show token cost, latency, and let you review and score outputs over time.
langfuse.trace(traceId)
.generation(g -> g.model(model).input(prompt).output(response)
.usage(promptTokens, completionTokens))
.score("faithfulness", judgeScore);The cost dashboard
Cost is the signal most unique to LLM systems and the one most likely to surprise you. Track it, tagged, with alerts:
// Per-feature, per-model cost — so "which feature is expensive?" has an answer.
registry.counter("gen_ai.cost.usd", "feature", feature, "model", model)
.increment(costCalculator.costUsd(model, promptTokens, completionTokens));The dashboard should show cost per feature per hour with an alert threshold — this is what catches a runaway agent loop in minutes instead of on the invoice.
Output quality monitoring
The hardest and most important: is the output still good? You cannot check every response, so sample:
// Sample a fraction of production responses for evaluation — property checks,
// an LLM judge, or human review — and track the quality trend over time. A
// drift downward is a regression that latency and error metrics never show.
if (sampler.shouldSample()) {
qualityMonitor.evaluate(request, response);
}A provider can change a model under a version alias, or your input distribution can shift; sampled quality monitoring is how you notice. See LLM evaluation & benchmarks.
The observability checklist
- Connected traces: retrieval, generation, tools as spans
- OpenTelemetry GenAI conventions for portable, consistent attributes
- Token and cost per feature, with alerting on the rate
- Latency split into retrieval and generation
- Sampled output-quality monitoring for drift
- Agent trajectory logging for debugging
- Metadata logged, content handled carefully (personal data)
Next
Frequently Asked Questions
What is the difference between LLM observability and normal observability?
What is LangFuse?
What are the OpenTelemetry GenAI conventions?
What should I trace in an LLM application?
Related tutorials
- AI for Data EngineeringApply LLMs to data engineering in Java: text-to-SQL with safety guards, AI-assisted data cleaning, schema mapping and anomaly detection — where AI helps and where it must be constrained.
- Multi-Tenant AI ArchitecturesBuild multi-tenant AI systems in Java: strict tenant isolation in retrieval, per-tenant quotas and rate limits, cost allocation, and data residency — keeping tenants apart safely at scale.
- Chatbot & Conversational AI ArchitectureDesign production chatbots in Java: intent classification, dialog state management, slot filling, multi-turn context, tool integration and handoff to humans — beyond a single ChatClient call.
- AI Caching StrategiesCut LLM cost and latency with caching: exact-match caching, semantic caching by embedding similarity, provider prompt caching, and invalidation — with Redis and Java examples.