Skip to main content

LLM Evaluation and Observability

Summaryโ€‹

๐Ÿงญ What this page covers

Context Management for LLM Systems and Agentic Patterns covered how to build LLM-powered systems that can retrieve, reason, and act. This page covers how to know whether they are working โ€” evaluation frameworks for pre-deployment quality, and observability patterns for post-deployment monitoring. Without measurement, you are operating blind.

Friday's demo looked great. Tuesday's production traffic looks less great. Nobody changed the code path on purpose, but answers are now shorter, citations are weaker, and costs are somehow up 18%. Welcome to the part of the job where "it felt good in testing" stops being a strategy.

You cannot improve what you cannot measure. LLM systems have unique evaluation challenges: there is rarely a single "correct" answer, quality degrades gradually rather than failing hard, and the failure modes (hallucination, relevance drift, context window truncation) are invisible without instrumentation. Evaluation and observability are not post-launch concerns โ€” they must be designed in from the first iteration.

Evaluation typeWhenWhat it measuresPrimary tools
Unit evaluationDuring developmentSingle input โ†’ expected outputpytest + custom eval harness
Dataset evaluationPre-deploymentQuality across representative sampleAzure AI Evaluation, RAGAS, DeepEval
LLM-as-judgePre/post deploymentCriteria-based quality at scaleGPT-4.1 as evaluator, custom rubrics
Production tracingPost-deploymentLatency, token usage, cost, error rateAzure Monitor, OpenTelemetry, Langfuse
Continuous evaluationOngoingQuality drift detection on live trafficSample of production calls โ†’ re-evaluate

A customer service AI was live for 3 weeks before anyone noticed response quality had degraded. The cause: a system prompt change 2 weeks earlier had shifted the model's tone toward terse, unhelpful answers on edge cases. No alert fired โ€” there were no quality metrics in production. After implementing continuous evaluation (10% sample of live calls evaluated by GPT-4.1 with a quality rubric), similar regressions are now caught within 24 hours.

Evaluation needs a golden dataset before deployment

A golden dataset is a curated set of inputs with expected outputs โ€” or at minimum, expected quality criteria. Without it, evaluation is subjective and unrepeatable. Build the golden dataset during development, ideally with domain expert review. 50โ€“200 examples covering the core use cases, edge cases, and known failure modes is sufficient for most enterprise deployments. More is better; zero is not acceptable.

Tracing every LLM call is the production baseline

Every production LLM call should emit a trace: the full prompt, the response, token counts, latency, model version, and a trace ID. Traces enable debugging, cost analysis, quality sampling, and regression investigation. The cost of tracing is negligible compared to the cost of debugging a production issue without it. OpenTelemetry with a backend (Azure Monitor, Langfuse, Grafana) is the standard integration pattern.

LLM-as-judge enables scale evaluation

Human evaluation is the gold standard but does not scale. LLM-as-judge โ€” using a capable model (GPT-4.1, Claude Sonnet) with a structured rubric to evaluate other model outputs โ€” scales to thousands of examples at low cost. Correlation with human judgement is high (0.7โ€“0.9) for well-designed rubrics. LLM-as-judge is not a replacement for human review of high-stakes outputs, but it enables continuous quality monitoring at production scale.

Why This Mattersโ€‹

๐ŸŽฏ
Quality drift is silent and gradual
LLM quality degrades without raising exceptions

When a database query fails, an exception is raised. When an LLM response becomes less accurate over time โ€” due to a model update, a prompt change, or a distribution shift in incoming queries โ€” no exception is raised. The application continues functioning, returning worse answers quietly. Only a measurement pipeline that continuously evaluates response quality will detect this. Without it, quality degradation can go unnoticed for weeks.

๐Ÿ’ฐ
Token cost compounds at scale
Without token monitoring, cost surprises arrive in the billing cycle

At 10,000 calls per day with an average of 5,000 tokens per call, a 1,000-token context bloat per call costs 10 million extra tokens per day. At $0.01/1K tokens (input), that is $100/day in preventable cost โ€” $3,000/month. Token usage monitoring with per-feature and per-endpoint breakdowns is the only way to identify which features are over-consuming context budget and which prompt changes reduced or increased cost.

๐Ÿ”ฌ
Evaluation gates prevent regressions
A prompt change that improves Case A may silently break Case B

LLM prompts are not deterministic programs. A prompt refinement that improves response quality on the primary use case may reduce quality on edge cases that were not included in the developer's informal testing. An evaluation pipeline that runs the golden dataset on every prompt change โ€” before it reaches production โ€” is the equivalent of a test suite for LLM quality. Without it, prompt engineering is guesswork at scale.

The measurement principle

Define your quality metrics before you build. "Good response" is not a metric. "Groundedness โ‰ฅ 0.85, relevance โ‰ฅ 0.80, no hallucinated citations" is a metric. Define it, measure it, and set a deployment gate against it.

Core Conceptsโ€‹

๐Ÿ“ Evaluation Metricsโ€‹

For RAG (Retrieval-Augmented Generation) systems:

MetricWhat it measuresGood scoreHow to compute
GroundednessDoes the response only use information from the retrieved context?โ‰ฅ 0.85LLM-as-judge: compare response claims to context
RelevanceDoes the response answer the actual question?โ‰ฅ 0.80LLM-as-judge: rubric on question-answer alignment
Retrieval recallDoes the retrieved context contain the answer?โ‰ฅ 0.90Check if gold answer appears in retrieved chunks
Faithfulness (RAGAS)Are all response claims supported by context?โ‰ฅ 0.80RAGAS library
Answer correctnessDoes the answer match the expected answer?Varies by taskExact match, semantic similarity, or LLM judge

For general chat and generation:

MetricWhat it measuresTool
CoherenceIs the response logically structured and readable?LLM-as-judge
FluencyIs the language natural and grammatically correct?LLM-as-judge
Harm / SafetyDoes the response contain harmful content?Azure Content Safety, LLM-as-judge
Task completionDid the response fulfil the user's request?LLM-as-judge with task-specific rubric

โš–๏ธ LLM-as-Judge Patternโ€‹

The judge model is not there to sound wise. It is there to turn "seems good" into a score you can graph, threshold, and compare over time.

Groundedness evaluation prompt template:

You are an expert evaluator for AI responses.

QUESTION: {question}
CONTEXT (retrieved documents): {context}
RESPONSE: {response}

Rate the response on GROUNDEDNESS (1โ€“5):
- 5: Every claim in the response is directly supported by the context
- 4: Almost all claims supported; minor extension beyond context
- 3: Most claims supported; some statements not in context
- 2: Significant claims not supported by context
- 1: Response primarily contradicts or ignores context

Output JSON only:
{"groundedness": <int>, "reasoning": "<one sentence>", "unsupported_claims": ["<claim1>", ...]}

Running at scale with Azure AI Evaluation (Python):

from azure.ai.evaluation import GroundednessEvaluator, RelevanceEvaluator, evaluate

groundedness_eval = GroundednessEvaluator(model_config={
"azure_deployment": "gpt-4.1",
"azure_endpoint": os.environ["AZURE_OPENAI_ENDPOINT"],
"api_key": os.environ["AZURE_OPENAI_API_KEY"],
})

results = evaluate(
data="golden_dataset.jsonl", # {"question": ..., "context": ..., "response": ..., "expected": ...}
evaluators={"groundedness": groundedness_eval, "relevance": RelevanceEvaluator(...)},
output_path="eval_results.json",
)

print(f"Mean groundedness: {results['metrics']['groundedness.groundedness']:.2f}")

๐Ÿ”ญ Production Tracingโ€‹

Every LLM call in production must emit a trace. The OpenTelemetry standard is the recommended approach โ€” it is vendor-neutral and supported by all major observability platforms.

If you only log the final answer, you miss the plot. Good traces tell you what the model saw, what it cost, how long it took, and which version did the talking.

Minimal tracing implementation (Python, OpenTelemetry):

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(
AzureMonitorTraceExporter(connection_string=os.environ["APPINSIGHTS_CONN_STRING"])
))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("llm.service")

def call_llm(prompt: str, user_id: str) -> str:
with tracer.start_as_current_span("llm.call") as span:
span.set_attribute("user_id", user_id)
span.set_attribute("prompt_preview", prompt[:200]) # never log full PII prompt

start = time.monotonic()
response = client.chat.completions.create(
model="gpt-4.1",
messages=[{"role": "user", "content": prompt}],
max_tokens=1000,
)
latency_ms = (time.monotonic() - start) * 1000

span.set_attribute("prompt_tokens", response.usage.prompt_tokens)
span.set_attribute("completion_tokens", response.usage.completion_tokens)
span.set_attribute("latency_ms", round(latency_ms, 1))
span.set_attribute("model", response.model)

return response.choices[0].message.content

๐Ÿ“ˆ Continuous Evaluation in Productionโ€‹

Sample production calls, evaluate them with LLM-as-judge, and alert on quality metric drift.

This is the quiet superpower of mature teams: they find regressions before users start writing annoyed emails.

Recommended production dashboards (Azure Monitor KQL):

// Token usage by endpoint per day
traces
| where message == "llm.call"
| extend endpoint = tostring(customDimensions["endpoint"]),
prompt_tokens = toint(customDimensions["prompt_tokens"]),
completion_tokens = toint(customDimensions["completion_tokens"])
| summarize total_tokens = sum(prompt_tokens + completion_tokens) by endpoint, bin(timestamp, 1d)
| render timechart

// P99 latency by model
traces
| where message == "llm.call"
| extend latency_ms = todouble(customDimensions["latency_ms"]),
model = tostring(customDimensions["model"])
| summarize p99_latency = percentile(latency_ms, 99) by model, bin(timestamp, 1h)
| render timechart

๐Ÿงช Evaluation Pipeline for Pre-Deployment Gatesโ€‹

GitHub Actions integration:

name: LLM Evaluation Gate

on:
pull_request:
paths: ['prompts/**', 'config/**']

jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run evaluation suite
run: python scripts/evaluate.py --dataset golden_dataset.jsonl --baseline baseline_scores.json
env:
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
- name: Check evaluation gate
run: python scripts/check_gate.py --min-groundedness 0.85 --min-relevance 0.80

Realistic Exampleโ€‹

Scenarioโ€‹

A HR policy Q&A chatbot built on internal policy documents. 1,200 calls per day. The team needs to know: are responses grounded in policy? Are they relevant? Is cost under control? Is quality stable week-over-week?

Implementation architectureโ€‹

# Evaluation configuration
EVAL_CONFIG = {
"sample_rate": 0.10, # 10% of live calls โ†’ ~120/day evaluated
"metrics": ["groundedness", "relevance", "safety"],
"thresholds": {
"groundedness": 0.82, # alert if daily average drops below
"relevance": 0.78,
"safety": 0.99, # near-zero tolerance for unsafe content
},
"alert_channel": "teams-llm-alerts",
"dashboard": "azure-monitor-llm-quality",
}

# Daily quality report (automated)
daily_scores = {
"date": "2026-07-15",
"calls_sampled": 118,
"groundedness": 0.87, # โœ… above threshold
"relevance": 0.83, # โœ… above threshold
"safety": 1.00, # โœ… no unsafe responses detected
"avg_prompt_tokens": 4_820,
"avg_completion_tokens": 312,
"avg_latency_ms": 1_240,
"total_daily_cost_usd": 6.73,
}

Week-over-week quality trend dashboard entries:

Week 1: groundedness 0.88, relevance 0.84 โ€” baseline
Week 2: groundedness 0.87, relevance 0.83 โ€” stable
Week 3: groundedness 0.79 โ† ALERT FIRED โ€” investigation reveals system prompt truncation bug
Week 3 (fixed): groundedness 0.87, relevance 0.84 โ€” restored

The alert at week 3 identified a truncation bug that had caused the system prompt to be cut at 4,096 tokens, silently dropping the grounding instruction. Without continuous evaluation, this would have been invisible.

Senior Tech vs Dev Conversationโ€‹

Senior Tech: We already do unit testing. Why do we need a separate evaluation pipeline?

Dev: Unit tests check deterministic behaviour โ€” does the function return the right type, does the API return 200, does the JSON parse correctly. Evaluation checks probabilistic quality โ€” is the response accurate, grounded, relevant, safe. These are fundamentally different things. A unit test that asserts response != "" will pass even if the response is wrong. You need both: unit tests for infrastructure correctness, evaluation metrics for output quality. They serve different purposes and neither replaces the other.

Senior Tech: How many examples do we need in a golden dataset?

Dev: For initial deployment, 50โ€“100 well-curated examples is a usable baseline. Aim for 150โ€“200 before you rely on it for deployment gating. The composition matters more than the count: 60% core happy-path queries, 20% edge cases you discovered during development, 20% known failure modes you want to protect against (prompt injection attempts, ambiguous queries, queries with no good answer in the docs). Domain expert review of the expected answers is non-negotiable for high-stakes systems. Ten wrong expected answers in your golden set will produce ten wrong baseline scores.

Senior Tech: LLM-as-judge feels circular โ€” using an LLM to evaluate an LLM. How reliable is it?

Dev: The research shows 0.75โ€“0.85 correlation with human expert judgement for well-designed rubrics โ€” which is comparable to inter-human annotation agreement on the same tasks. The key conditions: use a higher-capability model as judge than the target model (GPT-4.1 judging GPT-4o is more reliable than the reverse), use structured rubrics with defined score levels rather than open-ended questions, and calibrate by having human raters score 20โ€“30 examples and checking that the LLM judge agrees. It is not perfect. For regulatory-grade quality validation, human review remains required. For continuous production monitoring at scale, LLM-as-judge is the only economically viable approach.

Common Pitfallsโ€‹

PitfallWhat goes wrongPrevention
No golden dataset before deploymentCannot establish a quality baseline; regressions invisibleBuild golden dataset during development, before first prod deployment
Evaluating only on happy-path examplesGood scores mask failure on edge casesInclude 20% edge cases and 20% known failure modes in golden set
No production tracingCannot debug quality issues; no cost visibilityInstrument every LLM call with OpenTelemetry before launch
LLM-as-judge with vague rubricsLow correlation with human judgement; inconsistent scoresDefine score levels explicitly (1โ€“5 with descriptions per level)
One-time evaluation, no continuous pipelineQuality regressions from prompt changes go undetectedRun evaluation on every prompt/config change in CI
Sampling too low in productionQuality drift detected too lateMinimum 10% sample rate; 25%+ for high-stakes applications
Logging full prompt contentPII in traces; compliance violationLog prompt previews (first 200 chars) or hash identifiers only

References and Next Stepsโ€‹