LLM Observability and Monitoring — Generic / Architectural Patterns (as of 16 Jul 2026)
Grading note. A dated snapshot — accurate as of 16 Jul 2026, frozen here and kept as a permanent archive entry. Research-drafted by a pupil, graded by the 3-lens panel + sensei. Corrections applied inline; unverifiable gaps marked ⚠ PENDING (#issue) — never guessed.
How to read the labels
- ✅ independently-corroborated — 2+ independent publishers
- 📄 vendor-documented — official docs only (authoritative, single source)
- ⚠️ WARNING — a default that can cost money, break the machine, or remove a safety net
- 🕒 verify live — fast-moving (versions/prices/quotas); check the current value
Practice: Use the gen_ai.* OpenTelemetry semantic conventions as your span schema
Do: Instrument every LLM call with a span whose attributes follow the OpenTelemetry GenAI semantic conventions. At minimum set gen_ai.operation.name (e.g., chat, embeddings, invoke_agent), gen_ai.provider.name, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, and gen_ai.response.finish_reasons. Treat gen_ai.input.messages and gen_ai.output.messages as opt-in (disabled by default) because they can contain PII and produce very large spans.
Why (beginner): Without a shared naming scheme, every team invents its own field names (tokens_in, prompt_tokens, inputTokens) and dashboards break whenever you switch providers. The OTel gen_ai.* namespace is provider-neutral: the same dashboard works whether the backend is OpenAI, Anthropic, or Bedrock.
Caveat / contested: 🕒 The GenAI semantic conventions live in a dedicated repository (github.com/open-telemetry/semantic-conventions-genai) and are still in Development (experimental) stability as of mid-2026. The v1.40/v1.41 version identifiers belong to the main open-telemetry/semantic-conventions repository, not the GenAI-specific repo. The dedicated GenAI repo has no published releases — instrumentation libraries embed the attribute names directly rather than pinning a versioned release artifact. Attribute names can change between library versions; treat upgrades as planned migrations. The earlier free-text prompt and completion attributes (gen_ai.prompt, gen_ai.completion) have been superseded by the structured message attributes (gen_ai.input.messages, gen_ai.output.messages) — the structured forms are the current standard. 🕒 verify live — check the repo for any newly published release or stability change before adopting.
Sources: opentelemetry.io/docs/specs/semconv/gen-ai/ (redirect notice + link to new repo) (fetched 2026-07-16) · github.com/open-telemetry/semantic-conventions-genai — gen-ai-spans.md (fetched 2026-07-16) · greptime.com — How OpenTelemetry Traces LLM Calls, Agent Reasoning, and MCP Tools (2026-05-21) · cohorte.co — OpenTelemetry GenAI Semantic Conventions (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (OpenTelemetry project + Greptime + Cohorte, three different organisations)
Practice: Model multi-step agentic pipelines as a parent–child span tree
Do: Wrap each user request or agent invocation in a root span (gen_ai.operation.name = invoke_agent). Nest every LLM call, tool execution, and retrieval step as child spans under that root. Use W3C TraceContext headers to propagate the trace context across process or service boundaries so that multi-agent pipelines produce a single connected trace rather than disconnected fragments. Represent parallel tool calls as sibling spans sharing the same parent, not as sequential spans.
[invoke_agent: research-agent] ← root span
[chat: openai/gpt-4o] ← LLM planning call
[execute_tool: web_search] ← tool call 1
[execute_tool: code_interpreter] ← tool call 2 (parallel sibling)
[chat: openai/gpt-4o] ← LLM synthesis call
Why (beginner): A flat list of spans tells you each step’s duration but not which steps belong to the same request. The parent–child tree lets you answer “which agent invocations are slow?” and then drill in to find whether the bottleneck is the LLM call, the tool, or the retrieval step.
Caveat / contested: ⚠️ WARNING — If you forget to propagate trace context across an async queue or subprocess boundary, spans from downstream services appear as new root traces. The resulting gaps look like fast responses; the actual work is invisible. Always verify context propagation end-to-end in a staging environment before going to production.
Sources: zylos.ai — OpenTelemetry for AI Agents: Observability, Tracing, and the GenAI Semantic Conventions (2026-02-28) · inference.net — LLM Observability: A Complete Guide to Monitoring Production Deployments (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (Zylos Research + Inference.net, different publishers)
Practice: Emit structured JSON logs for every LLM call, with PII handling by log level
Do: Wrap your LLM client so every call emits a structured JSON log entry automatically. Required fields: timestamp, trace_id, span_id, model, provider, prompt_tokens, completion_tokens, latency_ms, finish_reason, prompt_hash (SHA-256 of the rendered prompt, not the raw text), and status. Log prompt content and retrieved chunks at DEBUG level only (dev/staging). Never log raw prompt text at INFO in production — it commonly contains user PII. Use provider-returned token counts rather than local tokenizer estimates; they are authoritative for billing.
Why (beginner): Token counts are your primary cost signal. Without them you cannot attribute spend to a feature, a user, or a prompt version. The prompt hash lets you detect duplicate calls and correlate traces without storing sensitive text.
Caveat / contested: Different providers return different token-count fields (OpenAI splits into cached_tokens; Anthropic splits into cache_read_input_tokens). Log these provider-specific subfields separately so cache hits are not double-counted in cost estimates.
Sources: braintrust.dev — How to track LLM token usage (2026) (fetched 2026-07-16) · latitude.so — How Teams Use Logs to Debug LLM Failures (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (Braintrust + Latitude, different publishers)
Practice: Version every prompt template and tag every trace with the version
Do: Store prompt templates in version control. Apply semantic versioning (MAJOR.MINOR.PATCH): increment MAJOR for structural rewrites that change meaning, MINOR for new examples or sections, PATCH for typo or wording fixes. At runtime, tag every trace with three version identifiers: prompt_template_version, model_version (the exact model ID returned by the API), and app_version. Without these tags you cannot attribute quality or cost changes to a specific deployment.
Why (beginner): LLM outputs are non-deterministic. When quality drops after a release, the first question is “did the prompt change, the model change, or the code change?” If every trace carries all three versions you can filter in your dashboard; without them you are guessing.
Caveat / contested: Some providers silently update a named model (e.g., gpt-4o can change under a stable alias) without incrementing a version string. Always log the model ID returned in the API response (gen_ai.response.model), not just the ID you requested (gen_ai.request.model). These two can differ. 🕒 verify live — provider aliasing behaviour varies and can change.
Sources: getmaxim.ai — Prompt versioning and its best practices 2025 (fetched 2026-07-16) · inference.net — LLM Observability: A Complete Guide to Monitoring Production Deployments (fetched 2026-07-16) · braintrust.dev — Best Prompt Versioning Tools for Production Teams (2026) (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (Maxim AI + Inference.net + Braintrust, three different publishers)
Practice: Build and maintain a golden dataset for offline regression evaluation
Do: Maintain a curated golden dataset of 50–100 input/expected-behaviour pairs per feature. Draw cases primarily from production failures (real users surface patterns synthetic data misses). Split the set into roughly 70% core cases and 30% edge cases (adversarial inputs, ambiguous queries, off-topic requests). Store the dataset as JSONL in version control alongside the prompt templates. Run the eval suite on every pull request, fail the build when regression exceeds an agreed tolerance, and grow the set deliberately from new production failures rather than padding with synthetic duplicates. Keep a holdout partition that is never optimised against, to catch over-fitting to the eval set.
Why (beginner): Every model update, prompt change, and retrieval tweak can silently break something that previously worked. A golden dataset turns “it feels worse” into “metric X dropped 8 percentage points on the edge-case partition” — actionable and attributable.
Caveat / contested: ⚠️ WARNING — If you use the same examples as few-shot examples in the prompt AND as eval cases, scores will be inflated. Strictly separate the eval pool from the prompt development pool. Also avoid testing on synthetic data that your model was trained on; contamination produces misleadingly high scores.
Sources: qaskills.sh — How to Build a Golden Dataset for LLM Evaluation (fetched 2026-07-16) · statsig.com — Golden datasets: Creating evaluation standards (fetched 2026-07-16) · braintrust.dev — What is LLM evaluation? A practical guide to evals, metrics, and regression testing (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (QA Skills + Statsig + Braintrust, three different publishers)
Practice: Run LLM-as-judge evaluations asynchronously on sampled production traffic
Do: Select a strong, stable model as your judge and evaluate sampled production outputs against explicit rubrics (relevance, faithfulness, task completion, safety). Start with a small number of high-signal metrics — three to five is enough; more makes the signal noisy and expensive. Run judge evaluations asynchronously, never in the critical request path. A sample rate of 5–20% of production traffic balances coverage against cost; target high-latency and low-heuristic-score traces first. Before scaling, validate your judge against a domain-expert-annotated sample and track false-positive rates — a judge that passes unsafe outputs is more dangerous than no judge at all.
Why (beginner): You rarely have labelled ground-truth answers at runtime (that is why offline evals use golden datasets). LLM-as-judge gives you a scalable proxy quality signal on live traffic without requiring human review of every response.
Caveat / contested: The judge model itself can drift if your provider updates it. Pin the judge to a specific model version where possible. Vague rubrics produce noisy judges; rewrite criteria as explicit step-by-step checklists when a metric becomes production-critical. In high-risk domains (healthcare, legal, safety), false positives from an LLM judge create dangerous false confidence — supplement with deterministic rule checks and periodic human audits.
Sources: deepeval.com — LLM-as-a-Judge in 2026: Top evaluation techniques and best practices (fetched 2026-07-16) · inference.net — LLM Observability: A Complete Guide to Monitoring Production Deployments (fetched 2026-07-16) · openobserve.ai — LLM Monitoring Best Practices: Complete Guide for 2026 (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (DeepEval + Inference.net + OpenObserve, three different publishers)
Practice: Detect behavioral drift via a four-layer signal stack
Do: Monitor for model behavioral drift using four complementary signals, in order of cost: (1) Statistical output signals — track response-length distribution, vocabulary entropy, and output-structure consistency; alert when any shifts by more than two standard deviations from the rolling baseline. (2) Embedding-space signals — compute cosine similarity between rolling output embeddings and a reference baseline; a sustained drop beyond 0.15 indicates semantic drift. (3) LLM-as-judge scoring — run your production judge evaluator on a fixed reference probe set on a regular schedule and alert on score drops. (4) Golden-dataset regression — run the offline eval suite against the full golden dataset on a schedule or after provider-announced model updates. Use two-tier alerting: statistical shifts get a 24–48-hour response window; judge-score drops of more than 10% against the 7-day rolling average get an immediate (Tier 1) response.
Why (beginner): Providers silently update models under stable aliases. Retrieval indexes change. Prompt templates drift through well-intentioned edits. None of these send you an alert — only your monitoring will. Behavioural drift can look like a sudden jump or a slow degradation across weeks; the four-layer stack catches both.
Caveat / contested: ⚠️ WARNING — The judge model you use to detect drift can itself drift if you do not pin it to a specific model version. Pin both your application model and your judge model; track them separately. The specific numeric thresholds (two standard deviations, 0.15 cosine drop) are reference points from observed production practice, not formal standards — treat them as starting points and adjust to your application’s distribution.
Sources: stackpulsar.com — LLM Model Drift Detection 2026: Monitoring AI Degradation (fetched 2026-07-16) · leanware.co — LLM Monitoring and Drift Detection Guide (fetched 2026-07-16) · medium.com/@EvePaunova — Tracking Behavioral Drift in Large Language Models (confirmed live Jul 2026; curl returns 403 bot-protection — link removed per policy, citation retained)
Confidence: ✅ independently-corroborated (StackPulsar + Leanware + Eva Paunova on Medium, three independent publishers)
Practice: Define SLOs on TTFT P99, error rate, and cost-per-request; alert with burn rates
Do: Define at least three SLOs for each LLM-backed endpoint: (a) latency — Time to First Token (TTFT) P99 target appropriate to the use case (interactive chat: ≤500ms; voice agents: ≤150ms; RAG-augmented chat: ≤1 s; batch/async: negotiate separately); (b) error rate — overall success rate above 99.5% in a rolling window; (c) cost — cost per 1,000 requests tracked as a P95 metric, alerted when it exceeds 2× the rolling baseline. Use burn-rate alerting rather than static thresholds: page on-call when the error-budget burn rate exceeds 14.4× over a 1-hour window, or 6× over 6 hours, to catch both sudden spikes and slow leaks. Never use averages alone — at 100 RPS an average TTFT of 120 ms can coexist with a P99 of 600 ms, meaning one user per second waits half a second before seeing output.
Why (beginner): Traditional web-service SLOs (HTTP 5xx rate, p95 latency) miss the LLM-specific failure modes: a response that arrives quickly but is truncated, a refusal that isn’t a 5xx, or a cost spike from a single runaway context window. Separating TTFT from total latency surfaces streaming quality issues that total-duration metrics hide.
Caveat / contested: 🕒 The specific numeric targets above are reference points from SRE literature and observed production practice, not universal standards. The right TTFT target for voice agents (≤150 ms P99) is very different from the right target for asynchronous report generation. Negotiate SLOs with product stakeholders, not from a checklist. verify live — model provider latency profiles change as capacity scales or degrades.
Sources: spheron.network — LLM Inference SLO Engineering: TTFT, ITL, and P99 Latency Budgets for Production AI (2026) (fetched 2026-07-16) · inference.net — LLM Observability: A Complete Guide to Monitoring Production Deployments (fetched 2026-07-16) · openobserve.ai — OpenTelemetry for LLMs: Complete SRE Guide for 2026 (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (Spheron + Inference.net + OpenObserve, three different publishers)
Practice: Use a hybrid head-based / tail-based sampling strategy for high-volume LLM tracing
Do: In development and staging, use 100% sampling to get full visibility. In production, apply a two-tier strategy: (1) Head-based sampling at 10–30% as the baseline for cost management, using TraceIdRatioBased sampler in the OTel SDK so the decision is made at trace start with zero buffering overhead. (2) Tail-based sampling policy (applied at the OTel Collector) that always retains traces matching any of: error status, TTFT exceeding your P99 SLO target, LLM-as-judge score below your quality floor, or a specific tenant or model ID you are investigating. For LLM-as-judge evaluation specifically, a 5–20% sample rate balances quality coverage against evaluation cost. Route all spans from a single trace to the same collector instance — tail sampling breaks if spans from one trace are split across collectors.
Why (beginner): LLM calls are expensive to trace at 100% when you are handling thousands of requests per second. Head-based sampling is cheap but blind to outcomes. Tail-based sampling is smarter but needs infrastructure. The hybrid catches the important cases (errors, slow calls, low-quality outputs) while keeping routine successful calls affordable.
Caveat / contested: ⚠️ WARNING — Tail-based sampling requires all spans from a trace to arrive at the same collector instance. If you have multiple collector replicas, spans from one trace can land on different replicas, making the tail decision impossible. Use a consistent hashing routing layer in front of your collectors, or use a managed adaptive-tracing product that handles this for you.
Sources: grafana.com/docs — Sampling strategies for tracing (fetched 2026-07-16) · betterstack.com — Sampling in OpenTelemetry: A Beginner’s Guide (fetched 2026-07-16) · mlflow.org — Setting Up LLM Observability Pipelines in 2026 (fetched 2026-07-16)
Confidence: ✅ independently-corroborated (Grafana + Better Stack + MLflow, three different publishers)
Held pending fixes (not publish-ready)
- OTel GenAI semconv dedicated repo (
open-telemetry/semantic-conventions-genai) has no versioned releases as of 2026-07-16. No release artifact to pin; allgen_ai.*attribute names remain Development-status. Monitor github.com/open-telemetry/semantic-conventions-genai for any stability roadmap or first release.
CHANGELOG (grading → this entry)
- FIX-G1 (Skeptic): Softened “gen_ai.prompt / gen_ai.completion were deprecated” — the cited semconv page contains no deprecation notice. Changed to “have been superseded by the structured message attributes” without asserting formal deprecated status.
- FIX-G2 (Skeptic): Changed “TTFT P95” to “TTFT P99” in Practice 8 heading and body. The lead source (Spheron) explicitly argues for P99 and never mentions P95 as a target.
- FIX-1 (Timekeeper): Clarified that the
semantic-conventions-genaidedicated repo has no published releases. The v1.40/v1.41 identifiers belong to the mainsemantic-conventionsrepo, not the GenAI repo. Added this to Practice 1 caveat. - FLAG-G3 (Skeptic): Corrected Greptime blog date from 2026-05-09 to 2026-05-21 (date visible on the fetched page).
- Link-check gate (publish-time): medium.com/@EvePaunova drift article returned 403 (bot-protection). Confirmed live via WebFetch. Unlinked to plain text per policy; citation retained. All other 23 URLs: 200.