Implementing AI Observability: Tracing and Metrics for GenAI
Implement AI observability for generative apps with tracing, token and latency metrics, error diagnostics, and eval loops to improve reliability, cost control, and quality.
Generative applications fail differently from traditional APIs. A request can return 200 OK and still be wrong, unsafe, too expensive, or too slow. Without observability, teams cannot tell whether the problem came from retrieval, prompt assembly, model behavior, tool calls, or downstream rate limits.
AI observability gives you a way to answer those questions in production, quickly and consistently.
This guide covers a practical stack for tracing, metrics, and evaluation loops so you can ship generative systems with confidence.
What is AI Observability
AI observability is the practice of instrumenting generative request paths so you can inspect:
- where time was spent,
- how many tokens were used,
- what failed and why,
- whether quality stayed acceptable,
- how behavior changed after prompt/model releases.
In practice, this means combining three signal types:
| Signal | Purpose | Typical examples |
|---|---|---|
| Traces | Request-level execution graph | spans for retrieval, model call, tool call |
| Metrics | Trend and SLO monitoring | p95 latency, token usage, error rate |
| Logs/events | Rich contextual diagnostics | guardrail blocks, retries, policy actions |
Tip: Observability is not just for outages. It is your fastest feedback loop for quality and cost optimization.
Why it Matters
Most teams start with app logs and discover they are insufficient for GenAI operations. Key production questions require correlated telemetry:
- Why did quality drop after a prompt change?
- Why is token spend up 35% this week?
- Why does p95 look fine but streaming feels slow?
- Which tool calls cause the most errors?
AI observability answers these through structured traces and metrics tied to deployment versions, model IDs, and request paths.
Common production risk areas
- Debugging blind spots across multi-service agent chains
- Cost drift from context growth and retries
- Latency regression from retrieval/tool bottlenecks
- Quality regressions after model/prompt updates
- Compliance exposure from over-collecting sensitive prompts
Architecture / How it Works
A robust observability design for generative apps typically follows this shape:
Client -> API Gateway -> Orchestrator -> Retrieval/Tools -> Model Provider
| | |
+------ Traces / Metrics / Events ------+
|
Collector / Backend
|
Dashboards + Alerts + Eval ReportsTrace model
- One root span per user request.
- Child spans for each stage: retrieval, prompt build, model call, tool execution, post-processing.
- Attributes for provider/model/version, request class, and outcome.
- Events for meaningful milestones (first token, tool retry, guardrail decision).
Metrics model
Track both system and AI-specific metrics:
| Metric family | Examples | Why it matters |
|---|---|---|
| Latency | p50/p95/p99, TTFT | User experience and SLA control |
| Token usage | input/output/cached tokens | Cost and context discipline |
| Reliability | error rate by class, retry count, rate-limit hits | Stability and capacity planning |
| Quality | groundedness, relevance, safety flags, user feedback | Release gating and trust |
Governance model
- Redact or hash sensitive content by default.
- Keep full prompt/response capture opt-in and sampled.
- Propagate correlation IDs end-to-end.
- Attach deployment metadata on every trace (
model_snapshot,prompt_version,feature_flag).
“If your telemetry cannot answer who changed what and when quality shifted, you are not production-ready.”
Step-by-Step Example
The following Python example uses OpenTelemetry-style instrumentation with a generative model call.
Step 1: Install dependencies
pip install openai opentelemetry-api opentelemetry-sdk \
opentelemetry-exporter-otlp-proto-httpStep 2: Configure tracing and metrics exporters
import os
from opentelemetry import trace, metrics
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
resource = Resource.create({
"service.name": "genai-app",
"deployment.environment": os.getenv("ENV", "prod")
})
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint=os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")))
)
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer("genai-app")
metric_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint=os.getenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"))
)
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
metrics.set_meter_provider(meter_provider)
meter = metrics.get_meter("genai-app")Step 3: Define key metrics
request_latency = meter.create_histogram("genai.request.latency.s", unit="s")
time_to_first_token = meter.create_histogram("genai.request.ttft.s", unit="s")
token_usage = meter.create_histogram("genai.request.tokens", unit="{token}")
error_counter = meter.create_counter("genai.request.errors")
quality_score = meter.create_histogram("genai.response.quality.score", unit="1")Step 4: Instrument model calls
import time
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def ask_model(user_input: str, session_id: str):
start = time.perf_counter()
attrs = {
"gen_ai.operation.name": "chat",
"gen_ai.provider.name": "openai",
"gen_ai.request.model": "gpt-4.1",
"app.session_id": session_id,
"app.prompt_version": "v2025-03-13"
}
with tracer.start_as_current_span("genai.chat", attributes=attrs) as span:
try:
response = client.responses.create(
model="gpt-4.1",
input=[
{"role": "developer", "content": "Be accurate and cite context."},
{"role": "user", "content": user_input}
]
)
usage = getattr(response, "usage", None)
input_tokens = getattr(usage, "input_tokens", 0) if usage else 0
output_tokens = getattr(usage, "output_tokens", 0) if usage else 0
token_usage.record(input_tokens, {"token.type": "input", "model": "gpt-4.1"})
token_usage.record(output_tokens, {"token.type": "output", "model": "gpt-4.1"})
span.set_attribute("gen_ai.usage.input_tokens", input_tokens)
span.set_attribute("gen_ai.usage.output_tokens", output_tokens)
elapsed = time.perf_counter() - start
request_latency.record(elapsed, {"model": "gpt-4.1", "status": "ok"})
span.add_event("response_received", {"latency_ms": int(elapsed * 1000)})
score = 0.90
quality_score.record(score, {"eval": "online_lightweight", "model": "gpt-4.1"})
span.set_attribute("app.quality.score", score)
request_id = getattr(response, "_request_id", None)
if request_id:
span.set_attribute("provider.request_id", request_id)
return response.output_text
except Exception as exc:
elapsed = time.perf_counter() - start
request_latency.record(elapsed, {"model": "gpt-4.1", "status": "error"})
error_counter.add(1, {"model": "gpt-4.1", "error.type": exc.__class__.__name__})
span.record_exception(exc)
span.set_attribute("error", True)
span.set_attribute("error.type", exc.__class__.__name__)
raiseStep 5: Add a lightweight eval loop
def nightly_eval(dataset: list[dict]) -> dict:
passed = 0
for row in dataset:
pred = ask_model(row["input"], session_id="nightly-eval")
ok = row["must_include"].lower() in pred.lower()
passed += int(ok)
pass_rate = passed / max(1, len(dataset))
return {"pass_rate": pass_rate, "cases": len(dataset)}Run this against a stable benchmark set and block releases when quality or cost thresholds fail.
Best Practices
- Define a stable trace taxonomy before broad rollout.
- Attach deployment metadata to all traces and metrics.
- Track tokens, cost, latency, and quality together per request class.
- Implement dynamic sampling: retain all errors and slow traces.
- Keep sensitive content capture minimized and policy-controlled.
- Use evals as CI gates, not post-release reports.
Minimum alert set
- p95 latency regression beyond threshold
- sudden token-per-request spike
- error-class distribution shift
- quality score drop after model/prompt rollout
Common Mistakes
- Recording only API status codes and missing semantic failures.
- Monitoring averages only and ignoring p95/p99 or TTFT.
- No end-to-end correlation IDs across app and provider telemetry.
- Capturing raw prompts by default, creating compliance risk.
- Separating observability from release workflow.
Anti-pattern
"We shipped the new prompt because tests passed locally."For GenAI, local spot checks are insufficient without trace-backed production metrics and eval comparison.
Real-World Use Cases
- RAG support assistants: isolate retrieval vs generation failure modes quickly.
- Agentic workflows: identify tool-call bottlenecks and retry storms.
- Cost governance: detect token inflation by prompt version and auto-roll back.
- Release management: compare model snapshots with eval pass/fail gating.
- Compliance evidence: prove policy checks and redaction paths executed.
Conclusion
AI observability is not optional infrastructure. It is core product capability for any team operating generative systems at scale. Tracing explains behavior, metrics expose trends, and evaluation loops protect quality during change.
Start small but structured: define a trace model, collect latency/token/error metrics, and tie releases to measured outcomes. Once that foundation exists, your team can move faster with less risk and clearer accountability.
Further Reading
- https://opentelemetry.io/docs/concepts/signals/traces/
- https://github.com/open-telemetry/semantic-conventions/tree/main/docs/gen-ai
- https://www.w3.org/TR/trace-context/
- https://developers.openai.com/api/reference/overview#debugging-requests
- https://developers.openai.com/api/docs/guides/rate-limits
- https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-overview