AgenticOps Metrics — Agent KPIs for Operations Monitoring
Reading Time: ~5 minutes
When AI Agents are deployed to production, system health alone cannot determine quality. We must measure Perceived Quality metrics such as "Did it understand user intent correctly?", "Did it call the right tools?", and "Is the answer faithful?". This document covers essential KPI categories and Langfuse·OTel-based instrumentation methods for Agent operations.
1. Why Agent-Specific Metrics are Necessary
1.1 Limitations of Traditional APM
Traditional APM (Application Performance Monitoring) is designed around system metrics such as HTTP success rate, response time, and error rate. However, Agents require additional metrics for the following reasons:
Table 1. Gap between traditional APM metrics and Agent quality metrics
| Traditional APM | Agent Quality Metric | Gap |
|---|---|---|
| HTTP 200 OK | Correct answer | Request success ≠ Result quality |
| Response time (total) | Time to First Token | User-perceived speed differs in streaming |
| Error rate | Hallucination rate | LLM errors return HTTP 200, not 500 |
| CPU/Memory | Token cost | Cloud LLMs charge per token |
| N/A | Tool-call accuracy | Wrong tool call is not a system error |
1.2 Perceived Quality vs System Metrics
Figure 1. System-side versus user-side view of the same response
Agent quality is ultimately judged by whether it accurately performed the user's desired task, which is independent of system success metrics.
2. Core KPI Categories
2.1 Task Success
Measures whether the user's requested task was completed.
Table 2. Task Success metrics: definition and measurement method
| Metric | Definition | Measurement Method |
|---|---|---|
| Task success rate | Percentage of successful conversation sessions | Automated eval (goal attainment) + HITL sampling (10%) |
| Completion time (p50/p95) | Time to task completion | Session duration (seconds) |
| Goal attainment scale | User goal achievement (1-5) | Explicit feedback (thumbs up/down) or LLM-as-Judge |
Example (Customer Support Agent):
The example below uses the Langfuse Python SDK v2 trace()/score() API. SDK v3 changed the API to OpenTelemetry-based spans and create_score(), so pin the SDK version you use and confirm its API before applying this. Also, the task_success value (0.0/1.0) is a score recorded after a separate evaluation step (rule-based or LLM-as-Judge) judges the session outcome; the SDK does not compute it automatically.
# Langfuse automated evaluation example (SDK v2 API)
from langfuse import Langfuse
langfuse = Langfuse()
trace = langfuse.trace(
name="customer-support-session",
session_id="sess_abc123",
metadata={"intent": "refund_request", "channel": "web"}
)
# At session end, a separate evaluation step judges and records success
trace.score(
name="task_success",
value=1.0, # 0.0 = failure, 1.0 = success (from a separate evaluation)
comment="Refund processed and confirmation sent"
)
2.2 Tool Use Accuracy
Measures whether the Agent correctly calls the right tools.
Table 3. Tool Use accuracy metrics: definition and measurement method
| Metric | Definition | Measurement Method |
|---|---|---|
| Tool-call accuracy | Percentage of correct tool calls | (Correct tool calls) / (Total tool calls) |
| Tool invocation rate | Average tool calls per session | Span hierarchy analysis |
| Tool failure rate | Tool call failure percentage | HTTP 5xx, Timeout, JSON parsing error |
Example:
# Record tool call span
span = trace.span(
name="tool_call",
input={"tool": "get_weather", "args": {"location": "Seoul"}},
metadata={"tool_name": "get_weather", "tool_version": "v1.2"}
)
# Evaluation criteria: intent="weather question" → correct tool="get_weather"
# Wrong example: calling "search_web" instead of "get_weather" → accuracy 0.0
span.score(
name="tool_call_accuracy",
value=1.0, # Correct tool selected
comment="Correct tool selected for weather intent"
)
2.3 Quality & Safety
Measures answer quality and safety violations.
Table 4. Quality and safety metrics: definition and measurement method
| Metric | Definition | Measurement Method |
|---|---|---|
| Context faithfulness | Share of answers grounded in the provided context (context-adherence measure) | Ragas Faithfulness (0.0~1.0) |
| Factual-error rate | Share of answers that contradict external facts | Fact-check against reference (human or LLM-as-Judge) |
| Detector-flag rate | Share of responses flagged by a hallucination detector | SelfCheckGPT and similar detector flags / total responses |
| Guardrails violation rate | Input/output filter block rate | input/output filter block count |
| Toxicity incidence | Harmful content generation rate | Perspective API / OpenAI Moderation |
These three metrics measure different phenomena. Context faithfulness captures whether an answer strays from the given context (RAG grounding), factual-error rate captures disagreement with external facts, and detector-flag rate captures the share flagged as suspicious by a detector. Reporting them as a single "hallucination rate" obscures the cause, so measure them separately.
Hallucination Measurement Example (Ragas Faithfulness):
evaluate() returns an aggregate over the whole dataset, so result["faithfulness"] is the dataset mean, not the score of a single trace. To record per-trace scores, extract each sample's score and bind it to its trace, and record the dataset mean separately at the evaluation-run level.
from ragas.metrics import faithfulness
from ragas import evaluate
# RAG Agent evaluation (dataset level)
result = evaluate(
dataset=test_dataset,
metrics=[faithfulness],
llm=ChatOpenAI(model="gpt-4o-mini")
)
# 1) Record per-sample scores on their traces
scores_df = result.to_pandas()
for row, trace_id in zip(scores_df.itertuples(), trace_ids):
langfuse.score(
trace_id=trace_id,
name="faithfulness",
value=float(row.faithfulness), # 0.0~1.0, per sample
)
# 2) Record the dataset mean at the evaluation-run level
dataset_mean = float(scores_df["faithfulness"].mean())
Guardrails Violation Measurement:
# OpenClaw AI Gateway PII redaction block
if gateway_response.status == "blocked_pii":
trace.score(
name="guardrails_violation",
value=1.0, # Blocked
comment="PII detected: email, phone"
)
2.4 Cost & Efficiency
Measures Agent operational cost and resource efficiency.
Table 5. Cost and efficiency metrics: definition and measurement method
| Metric | Definition | Measurement Method |
|---|---|---|
| Cost per interaction | Average cost per session (USD) | Σ(input_tokens × price_in + output_tokens × price_out) |
| Token efficiency | Effective token ratio | (Response tokens) / (Total consumed tokens) |
| Cache hit rate | Semantic cache hit rate | (cache hits) / (total queries) |
Cost Tracking Example:
# Record tokens and cost in generation span
generation = trace.generation(
name="llm_call",
model="example-model",
input="What is the weather in Seoul?",
output="The current weather in Seoul is...",
usage={
"input": 1200,
"output": 80,
"total": 1280,
"input_cost": 0.012, # $10 / 1M tokens
"output_cost": 0.0024, # $30 / 1M tokens
"total_cost": 0.0144
}
)
Cache Hit Rate Measurement:
# When semantic cache hit occurs
if cache_hit:
trace.event(
name="cache_hit",
metadata={"cache_key": cache_key, "latency_saved_ms": 2500}
)
2.5 User Experience
Measures user-perceived quality.
Table 6. User experience metrics: definition and measurement method
| Metric | Definition | Measurement Method |
|---|---|---|
| Time to First Token (TTFT) | Time to first content token | first non-empty content token time - request time |
| Task-length quartiles | Quartile distribution of task duration (or step count) | Collect completed-session durations and split at Q1/Q2(median)/Q3 boundaries |
| Escalation rate | Human handoff ratio | (human handoff count) / (total sessions) |
Measure TTFT at the first non-empty content token, not the first streaming chunk. Many streaming APIs send a role delta or empty string as the first chunk, so counting it as content makes TTFT look shorter than it is. Task-length quartiles are defined separately from TTFT by splitting the distribution of completed-session durations (or reasoning-step counts) at the Q1, Q2, and Q3 boundaries; the METR Task Standard is referenced as a basis for classifying task types.
TTFT Measurement Example:
import time
request_time = time.time()
# LLM call (streaming)
first_token_time = None
async for chunk in llm_stream():
# Skip role deltas and empty chunks; measure only the first content token
if not chunk.content:
continue
if first_token_time is None:
first_token_time = time.time()
ttft_ms = (first_token_time - request_time) * 1000
trace.event(
name="time_to_first_token",
metadata={"ttft_ms": ttft_ms, "model": "gpt-4o"}
)
Escalation Rate Measurement:
# Human handoff when Agent detects uncertainty
if confidence_score < 0.7:
trace.event(
name="escalation",
metadata={
"reason": "low_confidence",
"confidence": confidence_score,
"fallback": "human_agent"
}
)
2.6 System Reliability
Measures Agent service stability.
Table 7. System reliability metrics: definition and measurement method
| Metric | Definition | Measurement Method |
|---|---|---|
| Availability | Service uptime ratio | (uptime) / (total time) |
| Error budget | SLO violation allowance consumption | 1 - (actual SLI / SLO target) |
| Session continuity rate | Uninterrupted session completion ratio | (Completed sessions) / (Started sessions) |
| Retry exhaustion rate | Retry limit exceeded ratio | (max retries exceeded) / (total requests) |
SLO Example (Task success rate):
Target SLO: Task success rate ≥ 95% (30-day window, per session)
Error budget: 5% of sessions → at 100k sessions/month, 5,000 failed sessions allowed
This SLO is session- (event-) based, so the error budget is spent in sessions. Converting it to a time-availability budget such as "5% = 36 hours of outage" mixes the two bases. If a time-based budget is needed, define the SLO itself as a time-availability SLO.
3. Langfuse Trace Schema Proposal
3.1 Span Hierarchy
Agent execution flow is represented with the following hierarchy:
Figure 2. Span hierarchy of Agent execution
3.2 Standard Tags
Apply the following tags to all traces/spans:
agent_name: Agent identifier (e.g.,customer-support-agent)model: LLM model name (e.g.,example-model)prompt_version: Prompt template version (e.g.,v1.2.3)tool: Tool name called (e.g.,get_weather)guardrails: Applied guardrails (e.g.,pii_redaction,prompt_injection)
3.3 Score Events
Quality evaluation is recorded as score events:
task_success: 0.0~1.0faithfulness: 0.0~1.0 (Ragas)cache_hit: 0.0 (miss) / 1.0 (hit)tool_call_accuracy: 0.0~1.0guardrails_violation: 0.0 (pass) / 1.0 (block)
3.4 JSON Example
{
"id": "trace_abc123",
"name": "customer-support-session",
"session_id": "sess_xyz789",
"user_id": "user_456",
"tags": ["agent_name:support-agent", "environment:production"],
"metadata": {
"channel": "web",
"intent": "refund_request",
"customer_tier": "premium"
},
"spans": [
{
"id": "span_001",
"name": "agent_run",
"start_time": "2026-04-18T10:00:00Z",
"end_time": "2026-04-18T10:00:05Z",
"input": "I want to request a refund for order #12345",
"output": "I've processed your refund request...",
"metadata": {
"reasoning_steps": 3,
"tools_called": ["get_order", "process_refund", "send_email"]
}
},
{
"id": "span_002",
"parent_span_id": "span_001",
"name": "tool_call",
"type": "span",
"start_time": "2026-04-18T10:00:01Z",
"end_time": "2026-04-18T10:00:02Z",
"input": {"tool": "get_order", "args": {"order_id": "12345"}},
"output": {"status": "delivered", "amount": 129.99},
"metadata": {
"tool_name": "get_order",
"tool_version": "v2.1",
"latency_ms": 850
}
},
{
"id": "gen_001",
"parent_span_id": "span_001",
"name": "llm_generation",
"type": "generation",
"model": "example-model",
"input": [{"role": "system", "content": "You are a support agent..."}, {"role": "user", "content": "I want a refund..."}],
"output": "Based on your order status...",
"usage": {
"input": 1200,
"output": 80,
"total": 1280,
"input_cost": 0.012,
"output_cost": 0.0024,
"total_cost": 0.0144
},
"metadata": {
"temperature": 0.7,
"prompt_version": "v1.2.3"
}
}
],
"scores": [
{
"name": "task_success",
"value": 1.0,
"comment": "Refund processed successfully"
},
{
"name": "faithfulness",
"value": 0.92,
"comment": "High context adherence"
},
{
"name": "tool_call_accuracy",
"value": 1.0,
"comment": "All tools correctly selected"
}
]
}
4. OpenTelemetry Semantic Conventions
4.1 GenAI Semantic Conventions (As of 2026-09)
OpenTelemetry defines LLM instrumentation standards through GenAI Semantic Conventions (semconv 1.44.0, stability: Development).
The GenAI conventions were split into the semantic-conventions-genai repository in May 2026, and all gen_ai.* attributes in the original semantic-conventions repository are now deprecated. The table below follows registry 0c87594.
Core attributes:
Table 8. Core GenAI Semantic Conventions attributes with examples and descriptions
| Attribute | Example | Description |
|---|---|---|
gen_ai.provider.name | openai | GenAI provider (gcp.gen_ai, gcp.vertex_ai, etc.) |
gen_ai.request.model | example-model | Model name |
gen_ai.request.temperature | 0.7 | Sampling temperature |
gen_ai.request.max_tokens | 2048 | Max output tokens |
gen_ai.usage.input_tokens | 1200 | Input token count |
gen_ai.usage.output_tokens | 80 | Output token count |
gen_ai.response.finish_reasons | ["stop"] | Array of termination reasons (string[]) — stop, length, tool_calls |
4.2 Span Kind
- client: Agent → LLM API call
- internal: Agent internal reasoning logic
4.3 OTel → Langfuse Bridge
# OpenTelemetry instrumentation → Automatic Langfuse transmission
import base64
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# Langfuse uses Basic auth (base64-encoded public_key:secret_key)
AUTH = base64.b64encode(b"pk-lf-...:sk-lf-...").decode()
# Langfuse OTLP is an HTTP endpoint (traces path: /api/public/otel/v1/traces)
exporter = OTLPSpanExporter(
endpoint="https://cloud.langfuse.com/api/public/otel/v1/traces",
headers={"Authorization": f"Basic {AUTH}"},
)
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
# Now all OTel traces are sent to Langfuse
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("agent_run") as span:
span.set_attribute("gen_ai.provider.name", "openai")
span.set_attribute("gen_ai.request.model", "gpt-4o")
# ... Agent execution
5. Grafana/CloudWatch Dashboard Examples
5.1 Top-line Metrics (Executive Level)
┌─────────────────────────────────────────────────────────────┐
│ Task Success Rate (30d) │ 96.2% (↑ 1.2% WoW) │
│ Avg Cost per Interaction │ $0.12 (↓ $0.03 WoW) │
│ Hallucination Rate │ 2.1% (↑ 0.3% WoW) │
│ Escalation Rate │ 3.5% (→ 0.0% WoW) │
└─────────────────────────────────────────────────────────────┘
Grafana Panel Configuration:
The langfuse_trace_score_total metric referenced by the PromQL below is not exposed by Langfuse by default. You must first set up a custom exporter (or recording rule) that exports Langfuse score data to Prometheus for this query to work.
# Task success rate (30-day average)
sum(rate(langfuse_trace_score_total{name="task_success", value="1"}[30d]))
/
sum(rate(langfuse_trace_score_total{name="task_success"}[30d]))
5.2 Drill-down Dashboard (Operations Team)
Tool Call Analysis:
Tool Call Success Rate by Tool
┌──────────────┬──────────┬──────────┐
│ Tool │ Calls │ Success │
├──────────────┼──────────┼──────────┤
│ get_weather │ 1,234 │ 99.2% │
│ search_web │ 892 │ 94.5% │
│ send_email │ 456 │ 100% │
│ get_order │ 789 │ 98.7% │
└──────────────┴──────────┴──────────┘
Guardrails Violation Trend:
Guardrails Violation Rate (7d)
┌─────────────────────────────────────────┐
│ 5% ┤ │
│ 4% ┤ ╭╮ │
│ 3% ┤ ╭╯╰╮ ╭╮ │
│ 2% ┤╭╯ ╰╮╭╯╰╮ │
│ 1% ┼╯ ╰╯ ╰───────────────── │
│ 0% ┴──────────────────────────────── │
└─────────────────────────────────────────┘
Mon Tue Wed Thu Fri Sat Sun
5.3 SLO Dashboard
Error Budget Burn Rate (Availability SLO: 95%)
┌────────────────────────────────────────────────────┐
│ Current SLI: 96.2% │
│ Error Budget: 5% of 720h → 36h/month │
│ Consumed: 12.5h (34.7%) │
│ Remaining: 23.5h (65.3%) │
│ │
│ ██████████████████░░░░░░░░░░░ 34.7% consumed │
│ │
│ Status: 🟢 HEALTHY │
│ Estimated Days Until Budget Exhausted: 45 days │
└────────────────────────────────────────────────────┘
6. Alerting & Anomaly Detection
6.1 Anomaly Pattern Examples
Table 9. Detection rules and response actions by anomaly type
| Anomaly Type | Detection Rule | Response Action |
|---|---|---|
| Guardrails rate spike | Exceeds 3σ (rolling 1 hour) | PagerDuty P2, prompt review |
| Cost spike | Hourly cost > $100 (baseline $20) | Slack alert, activate rate limit |
| Escalation rate increase | Exceeds 10% (baseline 3%) | On-call engineer alert, Agent logic review |
| Tool failure rate | Specific tool > 20% failure | Auto circuit breaker, activate fallback |
6.2 Baseline Setting and Detection Algorithm
Rolling window average-based anomaly detection:
# Example: Guardrails violation rate anomaly detection
import numpy as np
def detect_anomaly(current_rate, historical_rates, threshold_sigma=3, min_samples=14):
"""
Args:
current_rate: Current time period violation rate
historical_rates: Same time period rates from history
threshold_sigma: Standard deviation multiple threshold
min_samples: Minimum samples required for a statistical decision
"""
# Do not judge if the baseline is insufficient or variance is zero/non-finite
if historical_rates is None or len(historical_rates) < min_samples:
return {"anomaly": False, "reason": "insufficient baseline"}
baseline_mean = np.mean(historical_rates)
baseline_std = np.std(historical_rates)
if not np.isfinite(baseline_std) or baseline_std == 0:
return {"anomaly": False, "reason": "zero or non-finite std"}
z_score = (current_rate - baseline_mean) / baseline_std
if z_score > threshold_sigma:
return {
"anomaly": True,
"severity": "high" if z_score > 5 else "medium",
"z_score": z_score,
"baseline": baseline_mean,
"current": current_rate
}
return {"anomaly": False}
# Real-time monitoring example
current_rate = 0.08 # 8% violation rate
historical = [0.02, 0.021, 0.019, 0.022, 0.018, 0.023, 0.020,
0.021, 0.019, 0.022, 0.020, 0.018, 0.021, 0.019] # 14 past samples
result = detect_anomaly(current_rate, historical)
if result["anomaly"]:
print(f"🚨 Anomaly detected: {result['current']:.1%} (baseline {result['baseline']:.1%})")
# Send PagerDuty alert
6.3 PagerDuty/Slack Integration
CloudWatch Alarm → SNS → Lambda → PagerDuty:
# Lambda handler: CloudWatch Alarm → PagerDuty
import boto3
import requests
def lambda_handler(event, context):
# CloudWatch Alarm State Change EventBridge schema
detail = event["detail"]
alarm_name = detail["alarmName"]
state_value = detail["state"]["value"] # OK | ALARM | INSUFFICIENT_DATA
state_reason = detail["state"]["reason"]
# Metric evaluated by the alarm (single-metric alarm)
metric = detail["configuration"]["metrics"][0]["metricStat"]["metric"]["name"]
# PagerDuty Events API v2
payload = {
"routing_key": "PAGERDUTY_ROUTING_KEY",
"event_action": "trigger",
"payload": {
"summary": f"Agent KPI Anomaly: {alarm_name}",
"severity": "warning",
"source": "cloudwatch",
"custom_details": {
"metric": metric,
"state": state_value,
"reason": state_reason
}
}
}
response = requests.post(
"https://events.pagerduty.com/v2/enqueue",
json=payload
)
# Verify delivery before returning success
response.raise_for_status()
return {"statusCode": response.status_code, "body": "Alert sent"}
Slack Alert Example:
🚨 Agent Metrics Alert
**Cost Spike Detected**
- Current hourly cost: $142.50 (baseline $18.20)
- Time: 2026-04-18 14:30 UTC
- Agent: customer-support-agent
- Model: example-model
**Probable Cause**: Unusual traffic spike (3.2k requests vs 800 baseline)
Actions:
- Rate limit activated (100 req/min → 50 req/min)
- Fallback to gpt-4o-mini for non-critical queries
📊 Dashboard: https://grafana.example.com/d/agent-cost
📖 Runbook: https://wiki.example.com/agent-cost-spike
7. AIDLC Stage-by-Stage Application
7.1 Inception: Define Baseline
Define target KPIs at project initiation.
Table 10. Inception-stage KPI targets and current baseline
| KPI | Target (after 90d) | Baseline (Current) |
|---|---|---|
| Task success rate | ≥ 95% | 88% (human baseline) |
| Tool-call accuracy | ≥ 90% | N/A (new) |
| Hallucination rate | ≤ 3% | 12% (initial prototype) |
| Cost per interaction | ≤ $0.15 | $0.32 |
| Escalation rate | ≤ 5% | 18% |
7.2 Construction: CI Regression Gate
Automatically detect metric regressions in each PR. Because the good direction differs per metric (success rate and accuracy are better when higher; hallucination and cost are better when lower), specify per-metric thresholds and directions instead of one blanket threshold.
# .github/workflows/agent-quality-gate.yml
name: Agent Quality Gate
on: [pull_request]
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Ragas evaluation
run: |
pytest tests/test_agent_quality.py --ragas
- name: Check metrics regression
run: |
# Declare per-metric tolerance and direction in thresholds.yml
# task_success_rate: {direction: higher_is_better, max_drop: 0.03}
# hallucination_rate: {direction: lower_is_better, max_increase: 0.02}
# cost_per_interaction: {direction: lower_is_better, max_increase: 0.10}
python scripts/check_regression.py \
--baseline metrics/baseline.json \
--current metrics/current.json \
--thresholds metrics/thresholds.yml
7.3 Operations: Real-time Alerting
Real-time monitoring after production deployment.
Agent KPI SLO (Production)
┌──────────────────────┬──────────┬──────────┬──────────┐
│ Metric │ SLO │ Current │ Status │
├──────────────────────┼──────────┼──────────┼──────────┤
│ Task success rate │ ≥ 95% │ 96.2% │ 🟢 OK │
│ Tool-call accuracy │ ≥ 90% │ 93.5% │ 🟢 OK │
│ Hallucination rate │ ≤ 3% │ 2.1% │ 🟢 OK │
│ Cost per interaction │ ≤ $0.15 │ $0.12 │ 🟢 OK │
│ Escalation rate │ ≤ 5% │ 3.5% │ 🟢 OK │
│ TTFT (p95) │ ≤ 2s │ 1.8s │ 🟢 OK │
└──────────────────────┴──────────┴──────────┴──────────┘
8. References
8.1 Langfuse Documentation
- Langfuse Scoring: Trace/spanAttach quality scores to
- Langfuse Prompt Management: Prompt version management and A/B testing
- Langfuse OTLP Integration: OpenTelemetry bridge
8.2 OpenTelemetry
- GenAI Semantic Conventions: LLM instrumentation standard (semconv 1.44.0)
- semantic-conventions-genai: Canonical repository for GenAI conventions (split off 2026-05)
- OTel Python SDK: Python instrumentation
8.3 Evaluation Frameworks
- Ragas: RAG evaluation (faithfulness, answer relevancy, context precision)
- SelfCheckGPT: Zero-resource hallucination detection
- METR Task Standard: Agent task benchmark
8.4 Related Documentation
- LLMOps Observability Comparison Guide: Langfuse vs LangSmith vs Helicone
- Ragas RAG Evaluation Frameworks: Ragas Metrics Details
- Observability Stack: AIDLC Operationstelemetry foundation
- Predictive Operations: Metric-based failure prediction