Idea en 30 segundos
Tool usage metrics muestran no solo si el agente funciona, sino como usa herramientas y donde se rompe la tool-layer.
Ayudan a entender que tools se llaman mas, donde crece la latency y donde empiezan llamadas repetidas o con error.
Sin estas metricas, es dificil detectar a tiempo sobrecarga en la tool-layer y crecimiento de costos.
Problema principal
Las metricas generales de run no muestran que pasa exactamente a nivel de herramientas.
Dos runs pueden tener latency total similar, pero en un caso el problema es search lenta, y en otro son llamadas repetidas a fetch.
Sin metricas por tools, esto es dificil de ver antes del incidente.
Ahora vemos como leer estas senales y encontrar problemas.
En production normalmente se ve asi:
- una tool se vuelve un hot spot sin notarse;
- crecen retries, pero la causa no es obvia;
- parte de los runs gasta demasiados pasos justo en herramientas;
- el equipo detecta el problema solo cuando sube error rate o presupuesto.
Por eso conviene monitorear la tool-layer por separado, y no solo con metricas generales de run.
Como funciona
Tool usage metrics se construyen alrededor de eventos tool_call y tool_result.
Las tool metrics se dividen en:
- infra metrics (
tool_latency_p95,tool_error_rate); - behavior metrics (
repeated_tool_calls,tool_calls_per_run,unique_tools_per_run).
Estas metricas responden "como se comporta la tool-layer en el tiempo". Los logs y el tracing siguen siendo necesarios para explicar un run problematico concreto.
Retries suelen ocurrir a nivel runtime, no a nivel de codigo: el agente recibe error de tool como observation e intenta otra vez. Retries no son solo llamadas repetidas, sino una senal de que el agente intenta adaptarse al fallo de herramienta.
Metricas tipicas de production para tools
| Metrica | Que muestra | Para que sirve |
|---|---|---|
| tool_calls_total | cantidad total de llamadas a tools | control de carga en la tool-layer |
| tool_calls_per_run | cuantas tool calls hay por run | deteccion de llamadas excesivas o ciclicas |
| unique_tools_per_run | cuantas tools distintas usa un run | evaluacion de complejidad del workflow |
| tool_error_rate | proporcion de tool calls con error | deteccion temprana de herramientas inestables |
| tool_latency_p50 / p95 | latency tipica y de cola por tools | localizar dependencias lentas |
| repeated_tool_calls | llamadas que repiten la misma tool con los mismos args | deteccion de spam de herramientas |
| tool_cost_per_run | costo estimado de tools dentro de un run | control de presupuesto y deteccion de tools caras |
Para que sean practicas, estas metricas suelen segmentarse por tool, release y, cuando hace falta, model.
Importante: no agregues campos de alta cardinalidad (run_id, request_id, args_hash) en labels, o el storage de metricas se sobrecarga rapido.
Como leer la tool-layer
Que se llama -> como se comporta el agente -> que pasa en el tiempo. Son tres niveles que siempre hay que mirar juntos.
Hay que mirar tendencias en el tiempo y diferencias entre releases, no valores aislados.
Miremos combinaciones de senales:
tool_error_rate↑ +repeated_tool_calls↑ -> tool inestable, el agente reintentatool_latency_p95↑ +tool_cost_per_run↑ -> degradacion de una tool caratool_calls_per_run↑ +unique_tools_per_run↑ -> complejidad excesiva del workflow
Cuando usar
El set completo de tool-metrics no siempre es necesario.
Para un agente simple con 1-2 herramientas, a veces alcanzan tool_calls_total y tool_error_rate.
Pero metricas detalladas de uso de tools se vuelven criticas cuando:
- el agente usa activamente APIs externas o BD;
- hay retries frecuentes;
- es importante controlar costos de herramientas;
- hay que detectar tool spam antes de afectar usuarios.
Ejemplo de implementacion
Abajo tienes un ejemplo simplificado de instrumentacion Prometheus para tool usage metrics. El ejemplo muestra control base: volumen de llamadas, latency, clases de error, repeticiones y carga de herramientas por run.
import hashlib
import json
import time
from prometheus_client import Counter, Histogram
TOOL_CALL_TOTAL = Counter(
"agent_tool_call_total",
"Total tool calls",
["tool", "status", "release"],
)
TOOL_ERROR_TOTAL = Counter(
"agent_tool_error_total",
"Total tool errors by class",
["tool", "error_class", "release"],
)
TOOL_LATENCY_MS = Histogram(
"agent_tool_latency_ms",
"Tool latency in milliseconds",
["tool", "release"],
buckets=(20, 50, 100, 250, 500, 1000, 2000, 5000),
)
TOOL_CALLS_PER_RUN = Histogram(
"agent_tool_calls_per_run",
"Number of tool calls per run",
["release"],
buckets=(0, 1, 2, 4, 8, 12, 16, 24, 32),
)
UNIQUE_TOOLS_PER_RUN = Histogram(
"agent_unique_tools_per_run",
"Number of unique tools used in run",
["release"],
buckets=(0, 1, 2, 3, 4, 6, 8, 12),
)
REPEATED_TOOL_CALL_TOTAL = Counter(
"agent_repeated_tool_call_total",
"Repeated tool calls with same tool+args signature",
["tool", "release"],
)
TOOL_COST_USD_TOTAL = Counter(
"agent_tool_cost_usd_total",
"Estimated total tool cost in USD",
["tool", "release"],
)
STEP_ERROR_TOTAL = Counter(
"agent_step_error_total",
"Total non-tool step errors by type and class",
["step_type", "error_class", "release"],
)
def stable_hash(value):
# default=str da compatibilidad base;
# en sistemas criticos conviene serializacion explicita (por ejemplo ISO 8601)
payload = json.dumps(value, sort_keys=True, ensure_ascii=False, default=str).encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def run_agent(agent, task, release="2026-03-21"):
tool_calls = 0
unique_tools = set()
seen_signatures = set()
try:
for step in agent.iter(task):
step_type = step.type
result = None
if step_type != "tool_call":
try:
result = step.execute()
except Exception as error:
STEP_ERROR_TOTAL.labels(
step_type=step_type,
error_class=type(error).__name__,
release=release,
).inc()
raise
if result and result.is_final:
break
continue
tool_name = getattr(step, "tool_name", "unknown")
args = getattr(step, "args", {})
tool_calls += 1
unique_tools.add(tool_name)
signature = (tool_name, stable_hash(args))
if signature in seen_signatures:
REPEATED_TOOL_CALL_TOTAL.labels(tool=tool_name, release=release).inc()
else:
seen_signatures.add(signature)
started_at = time.time()
try:
result = step.execute()
TOOL_CALL_TOTAL.labels(tool=tool_name, status="ok", release=release).inc()
cost_usd = getattr(result, "cost_usd", None)
if cost_usd:
TOOL_COST_USD_TOTAL.labels(tool=tool_name, release=release).inc(cost_usd)
except Exception as error:
TOOL_CALL_TOTAL.labels(tool=tool_name, status="error", release=release).inc()
TOOL_ERROR_TOTAL.labels(
tool=tool_name,
error_class=type(error).__name__,
release=release,
).inc()
# Este ejemplo hace raise.
# En agentes reales, el error suele pasar al LLM como observation para retry.
raise
finally:
TOOL_LATENCY_MS.labels(tool=tool_name, release=release).observe(
(time.time() - started_at) * 1000
)
if result and result.is_final:
break
finally:
TOOL_CALLS_PER_RUN.labels(release=release).observe(tool_calls)
UNIQUE_TOOLS_PER_RUN.labels(release=release).observe(len(unique_tools))
# tool_cost_per_run normalmente se calcula en dashboard:
# sum(agent_tool_cost_usd_total) / run_count
Asi se ven estas metricas juntas en un dashboard real:
| Tool | calls/min | error_rate | p95 latency | Estado |
|---|---|---|---|---|
| search_docs | 320 | 6.8% | 1.9s | critical: alert |
| fetch_url | 180 | 1.4% | 680ms | warning: p95 en alza |
| db_lookup | 95 | 0.3% | 120ms | ok |
Para error_class, conviene usar un diccionario normalizado de valores para evitar cardinalidad innecesaria.
Investigation
Cuando dispara una alerta:
- encontrar la tool anomala en metricas;
- revisar runs concretos en tracing;
- revisar args y respuestas en logs;
- encontrar root cause (tool, logica del agente o API externa).
Errores tipicos
Incluso con tool metrics agregadas, muchas veces no funcionan como se espera por errores tipicos.
Hay conteo total de llamadas, pero no desglose por tool
tool_calls_total sin desglose por herramienta casi no ayuda en incidentes.
En esta situacion, es dificil encontrar rapido la fuente de fallo de herramienta.
No se rastrean llamadas repetidas
Sin repeated_tool_calls, cuesta ver que el agente llama la misma tool con los mismos args.
Esto suele ocultar la fase temprana de spam de herramientas.
No hay p95 latency por herramienta
El sistema puede parecer estable mientras parte de usuarios ya espera 5+ segundos.
Para tool-layer, el minimo es p50 y p95.
Labels de alta cardinalidad
Agregar run_id, request_id o args_hash en labels sobrecarga rapido el backend de metricas.
Es mejor guardar esos datos en logs, no en labels.
No hay alertas del tool-layer
Sin alertas, las metricas quedan como telemetria pasiva. Asi es facil perder senales tempranas de explosion de presupuesto por exceso de llamadas a APIs externas.
Autoevaluacion
Abajo tienes un checklist corto de tool usage metrics base antes del release.
Progreso: 0/9
⚠ Falta observability base
Será difícil depurar el sistema en production. Empieza con run_id, structured logs y tracing de tool calls.
FAQ
P: En que se diferencian tool usage metrics de metricas generales del agente?
R: Las metricas generales muestran el estado global del sistema. Tool usage metrics muestran lo que pasa especificamente en la tool-layer.
P: Cual es el minimo de tool-metrics para empezar?
R: Empieza con tool_calls_total, tool_error_rate, tool_latency_p95 y tool_calls_per_run.
P: Conviene agregar args_hash en labels?
R: No. Casi siempre crea alta cardinalidad. Para esos datos conviene usar logs estructurados.
P: Como diferenciar un fallo aislado de un problema sistemico en tool-layer?
R: Revisa si el problema se repite para una tool concreta en varios runs y releases. Si se repiten las mismas senales (error_class, latency, repeated_tool_calls), es sistemico.
Paginas relacionadas
Siguiente sobre el tema:
- Metricas de agentes — modelo general de metricas para sistemas de agentes.
- Logging de agentes — eventos necesarios para analizar incidentes.
- Tracing de agente — ruta de un run paso a paso.
- Logging semantico para agentes — vocabulario estable de eventos para analitica.
- Monitoreo de costos para agentes de IA — control de costos en production.