Idea en 30 segundos
Agent tracing muestra el camino completo de ejecución de un run.
Un trace está formado por spans (spans): cada span es un paso separado, por ejemplo reasoning, tool call o generación LLM.
Eso da visibilidad a nivel de paso y simplifica mucho el debugging en production.
Problema principal
En muchos sistemas solo se loggea el inicio y el fin del run.
Para agentes eso no alcanza: entre el inicio y la respuesta final puede haber decenas de pasos. Sin tracing, es difícil entender qué hizo exactamente el agente y en qué paso apareció el problema.
La misma solicitud puede seguir caminos distintos: diferente número de pasos, diferentes herramientas, diferente latency.
Sin tracing, incluso preguntas básicas son difíciles:
- ¿Qué paso fue el más lento?
- ¿Por qué el agente volvió a llamar una herramienta?
- ¿Dónde exactamente apareció el error?
- ¿Por qué subieron los tokens en este run en particular?
Por eso tracing es importante: muestra el camino completo de ejecución del run, no solo el resultado final.
Cómo funciona
En tracing hay dos entidades base:
trace— todo el camino de un runspan— un paso dentro de ese trace
En la práctica, un step en runtime suele corresponder a un span, pero no siempre.
Un paso complejo puede tener spans anidados, por ejemplo una llamada de herramienta que internamente hace varias requests HTTP o consultas a base de datos.
Cada span normalmente tiene campos base:
trace_idyrun_idpara correlaciónspan_id(yparent_span_idcuando aplica)step_type(reasoning,tool_call,llm_generate)latency_msystatus(ok/error)
Esta estructura (trace_id, span_id) se basa en el estándar OpenTelemetry (OTel), base de la mayoría de sistemas modernos de monitoreo.
El campo parent_span_id forma parte del modelo OTel de spans jerárquicos, que permite construir el árbol de ejecución (trace tree).
Existen herramientas especializadas para tracing de agentes (por ejemplo LangSmith, Langfuse, Arize Phoenix), pero estos principios son iguales sin importar la plataforma.
Cómo se ve el trace de un run
La forma más fácil de entender tracing es con un ejemplo real de solicitud.
En sistemas reales, cada span-event incluye trace_id, span_id y muchas veces parent_span_id.
En el ejemplo de abajo, esos campos se simplifican para facilitar lectura.
trace_id: tr_9fd2
run_id: run_9fd2
user_query: "Find recent research about battery recycling"
span 1 llm_reasoning 320ms status=ok
span 2 tool_call: search 410ms status=ok
span 3 llm_reasoning 180ms status=ok
span 4 tool_call: fetch 260ms status=error
stop_reason: tool_error
Este trace muestra de inmediato:
- qué pasos ejecutó el agente;
- qué herramientas fueron llamadas;
- cuánto tardó cada paso;
- dónde apareció exactamente la latencia o el error.
Los traces no solo sirven para debugging. También son importantes para evaluations y validación automática de pasos intermedios: sin traces es difícil verificar si el agente actuó correctamente, no solo si dio una respuesta final correcta.
Cuándo usar
Tracing no siempre es necesario.
Para escenarios simples — una llamada LLM sin tools y sin ciclo de ejecución — suele bastar con logging básico.
Pero si el run tiene varios pasos, llamadas de herramientas o iteraciones repetidas, sin tracing se vuelve difícil:
- debugear el comportamiento del agente;
- controlar latency y costos;
- explicar por qué el sistema tomó una decisión concreta.
Ejemplo de implementación
Abajo tienes un ejemplo simplificado de instrumentation runtime para trace y spans. Este enfoque se usa en LangGraph, CrewAI y runtimes de agentes personalizados. En este ejemplo, el run completo también se modela como root span, y los pasos del agente se loggean como spans hijos.
import contextvars
import logging
import time
import uuid
logger = logging.getLogger("agent")
trace_id_ctx = contextvars.ContextVar("trace_id", default=None)
def start_span(run_id, step_type, tool=None, parent_span_id=None):
span_id = str(uuid.uuid4())
started_at = time.time()
logger.info(
"span_started",
extra={
"trace_id": trace_id_ctx.get(),
"run_id": run_id,
"span_id": span_id,
"parent_span_id": parent_span_id,
"step_type": step_type,
"tool": tool,
},
)
return span_id, started_at
def finish_span(
run_id,
span_id,
step_type,
started_at,
status,
tool=None,
parent_span_id=None,
error=None,
):
logger.info(
"span_finished",
extra={
"trace_id": trace_id_ctx.get(),
"run_id": run_id,
"span_id": span_id,
"parent_span_id": parent_span_id,
"step_type": step_type,
"tool": tool,
"status": status,
"latency_ms": int((time.time() - started_at) * 1000),
"error": error,
},
)
def run_agent(agent, task):
trace_id = str(uuid.uuid4())
run_id = str(uuid.uuid4()) # en sistemas multi-agente un trace_id puede incluir varios run_id
token = trace_id_ctx.set(trace_id)
logger.info("trace_started", extra={"trace_id": trace_id, "run_id": run_id, "task": task})
stop_reason = "max_steps"
step_count = 0
root_span_id, root_started_at = start_span(run_id, "run", parent_span_id=None)
try:
# en este ejemplo todos los pasos son hijos del root span (sin anidación profunda)
for step in agent.iter(task): # step: reasoning o tool execution
step_count += 1
step_type = step.type # reasoning | tool_call | llm_generate
tool_name = getattr(step, "tool_name", None)
span_id, started_at = start_span(
run_id,
step_type,
tool=tool_name,
parent_span_id=root_span_id,
)
try:
result = step.execute()
finish_span(
run_id,
span_id,
step_type,
started_at,
status="ok",
tool=tool_name,
parent_span_id=root_span_id,
)
except Exception as error:
finish_span(
run_id,
span_id,
step_type,
started_at,
status="error",
tool=tool_name,
parent_span_id=root_span_id,
error=str(error),
)
stop_reason = "tool_error"
raise
if result.is_final:
stop_reason = "completed"
break
finally:
if stop_reason == "completed":
root_status = "ok"
elif stop_reason == "max_steps":
root_status = "error" # simplificado para este ejemplo
else:
root_status = "error"
finish_span(
run_id,
root_span_id,
"run",
root_started_at,
status=root_status,
error=None if root_status == "ok" else stop_reason,
)
logger.info(
"trace_finished",
extra={
"trace_id": trace_id,
"run_id": run_id,
"steps": step_count,
"stop_reason": stop_reason,
},
)
trace_id_ctx.reset(token)
En sistemas reales, trace_id y run_id deben propagarse por toda la cadena de llamadas.
En Python, para eso se usa mucho contextvars, así no hay que pasar el identificador manualmente a cada función.
Por ejemplo, un span en log estructurado puede verse así:
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "span_finished",
"trace_id": "tr_9fd2",
"run_id": "run_9fd2",
"span_id": "sp_21ab",
"parent_span_id": "sp_root_01",
"step_type": "tool_call",
"tool": "search_docs",
"latency_ms": 410,
"status": "ok"
}
Errores típicos
Incluso con tracing agregado, los sistemas suelen seguir siendo difíciles de diagnosticar por errores típicos como estos.
Trace solo al nivel de run, sin spans
Si solo se loggea inicio y fin del run, tracing pierde casi todo su valor: no se ven pasos intermedios y casi no se puede localizar latencia o error.
Falta trace_id en parte de los eventos
Cuando parte de los logs no tiene trace_id o run_id, los eventos no se pueden unir en una sola cadena.
Por eso el debugging tarda mucho más incluso en incidentes simples.
No se trazan llamadas de herramientas
Las herramientas suelen ser la parte más lenta del run. Si los tool calls no entran al trace, cuesta encontrar la causa de latencias y repeticiones. En production esto puede ocultar fallo de herramienta o spam de herramientas.
No hay stop_reason ni estado del span
Sin stop_reason y status, cuesta saber si un run terminó bien o se detuvo por límites o errores.
Como resultado, se complica reproducir incidentes y ajustar alertas correctamente.
Autoevaluación
Abajo tienes un checklist corto de tracing 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
Q: ¿En qué se diferencia un trace de logs normales?
A: Los logs responden «qué pasó». Un trace muestra la secuencia de pasos de un run y ayuda a entender «cómo exactamente pasó».
Q: ¿Qué conviene implementar primero para tracing de agentes?
A: Mínimo: trace_id, run_id, span_id, tipo de paso, latency, status y stop_reason. Eso ya alcanza para debugging base.
Q: ¿Es obligatorio conectar una herramienta externa de tracing desde el inicio?
A: No. Puedes empezar con instrumentation propia y logs JSON. Las plataformas externas se vuelven especialmente útiles cuando crecen la cantidad de runs y de equipos.
Q: ¿Cuándo puede sobrar tracing completo?
A: Para escenarios single-shot simples sin tools y sin ciclo de ejecución, suele bastar logging base. Tracing completo se vuelve especialmente útil cuando el run tiene varios pasos, herramientas externas o iteraciones repetidas.
Páginas relacionadas
Sigue con estos temas:
- Observabilidad para agentes de IA — modelo base de traces, logs y metrics.
- Tracing distribuido de agentes — cómo conectar traces entre varios servicios.
- Debugging de runs de agentes — cómo analizar runs problemáticos paso a paso.
- Logging de agentes — qué eventos registrar en logs.
- Métricas de agentes — qué indicadores seguir en production.