Agent tracing: cómo seguir decisiones del agente

Agent tracing registra cada paso como trace y spans, muestra reasoning y tool calls, y ayuda a debugear runs problemáticos en production.
En esta página
  1. Idea en 30 segundos
  2. Problema principal
  3. Cómo funciona
  4. Cómo se ve el trace de un run
  5. Cuándo usar
  6. Ejemplo de implementación
  7. Errores típicos
  8. Trace solo al nivel de run, sin spans
  9. Falta trace_id en parte de los eventos
  10. No se trazan llamadas de herramientas
  11. No hay stop_reason ni estado del span
  12. Autoevaluación
  13. FAQ
  14. Páginas relacionadas

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 run
  • span — 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_id y run_id para correlación
  • span_id (y parent_span_id cuando aplica)
  • step_type (reasoning, tool_call, llm_generate)
  • latency_ms y status (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.

TEXT
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.

PYTHON
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í:

JSON
{
  "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:

⏱️ 8 min de lecturaActualizado 21 de marzo de 2026Dificultad: ★★★
Integrado: control en producciónOnceOnly
Guardrails para agentes con tool-calling
Lleva este patrón a producción con gobernanza:
  • Presupuestos (pasos / topes de gasto)
  • Permisos de herramientas (allowlist / blocklist)
  • Kill switch y parada por incidente
  • Idempotencia y dedupe
  • Audit logs y trazabilidad
Mención integrada: OnceOnly es una capa de control para sistemas de agentes en producción.

Autor

Nick — ingeniero que construye infraestructura para agentes de IA en producción.

Enfoque: patrones de agentes, modos de fallo, control del runtime y fiabilidad del sistema.

🔗 GitHub: https://github.com/mykolademyanov


Nota editorial

Esta documentación está asistida por IA, con responsabilidad editorial humana sobre la exactitud, la claridad y la relevancia en producción.

El contenido se basa en fallos reales, post-mortems e incidentes operativos en sistemas de agentes de IA desplegados.