Logging para agentes de IA (qué loguear, qué redaccionar, qué alertar)

Logging que sirve en incidentes: trace IDs, eventos de tool calls, stop reasons, redacción y alertas. Incluye snippets en Python + JS.
En esta página
  1. Problema (por qué estás aquí)
  2. Por qué esto falla en producción
  3. Diagrama: pipeline mínimo de eventos
  4. Código real: instrumenta el tool gateway (Python + JS)
  5. Fallo real (con números)
  6. Trade-offs
  7. Cuándo NO hacer esto
  8. Checklist (copy-paste)
  9. Config segura por defecto (YAML)
  10. Implementar en OnceOnly (opcional)
  11. FAQ (3–5)
  12. Páginas relacionadas (3–6 links)

Problema (por qué estás aquí)

En dev, tu agente “funciona”.

En producción, una vez cada ~200 runs:

  • un usuario dice “envió lo equivocado”
  • el coste se dispara 15 minutos
  • entra en loop con una API flaky hasta timeout

Y tú tienes… casi nada:

  • una “respuesta final”
  • dos logs sueltos
  • quizá un error del tool sin contexto

Eso te deja con el peor debugging: adivinar con una tarjeta conectada.

Esta página es logging que vuelve los incidentes aburridos (en el buen sentido).

Por qué esto falla en producción

Los agentes fallan como sistemas distribuidos porque lo son:

  • el modelo es un planner poco fiable
  • los tools son side effects (HTTP/DB/tickets/email)
  • retries + timeouts generan comportamientos emergentes

Si no logueas el loop, no puedes responder:

  • ¿qué tool calls ocurrieron y en qué orden?
  • ¿con qué args (o al menos args_hash)?
  • ¿qué devolvió el tool (o qué redaccionamos)?
  • ¿por qué terminó el run (stop_reason)?
  • ¿qué request/user lo disparó?

Si no logueas stop_reason, no estás observando. Estás coleccionando vibes.

Diagrama: pipeline mínimo de eventos

Código real: instrumenta el tool gateway (Python + JS)

Empieza por la frontera. Los tools son donde vive el gasto y el daño.

Logueamos:

  • run_id, trace_id, tool_name
  • args_hash (no args en claro por defecto)
  • latencia + estado
  • error_class (normalizada)

Y lo forzamos vía gateway para que no exista el “se me olvidó loguear”.

PYTHON
import hashlib
import json
import time
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional


def stable_hash(obj: Any) -> str:
  raw = json.dumps(obj, sort_keys=True, ensure_ascii=False).encode("utf-8")
  return hashlib.sha256(raw).hexdigest()


@dataclass(frozen=True)
class RunCtx:
  run_id: str
  trace_id: str
  user_id: Optional[str] = None
  request_id: Optional[str] = None


class Logger:
  def event(self, name: str, fields: Dict[str, Any]) -> None: ...


class ToolGateway:
  def __init__(self, *, impls: dict[str, Callable[..., Any]], logger: Logger):
      self.impls = impls
      self.logger = logger

  def call(self, ctx: RunCtx, name: str, args: Dict[str, Any]) -> Any:
      fn = self.impls.get(name)
      if not fn:
          self.logger.event("tool_call", {
              "run_id": ctx.run_id,
              "trace_id": ctx.trace_id,
              "tool": name,
              "args_hash": stable_hash(args),
              "ok": False,
              "error_class": "unknown_tool",
          })
          raise RuntimeError(f"unknown tool: {name}")

      t0 = time.time()
      self.logger.event("tool_call", {
          "run_id": ctx.run_id,
          "trace_id": ctx.trace_id,
          "tool": name,
          "args_hash": stable_hash(args),
      })

      try:
          out = fn(**args)
          self.logger.event("tool_result", {
              "run_id": ctx.run_id,
              "trace_id": ctx.trace_id,
              "tool": name,
              "latency_ms": int((time.time() - t0) * 1000),
              "ok": True,
          })
          return out
      except TimeoutError:
          self.logger.event("tool_result", {
              "run_id": ctx.run_id,
              "trace_id": ctx.trace_id,
              "tool": name,
              "latency_ms": int((time.time() - t0) * 1000),
              "ok": False,
              "error_class": "timeout",
          })
          raise
      except Exception as e:
          self.logger.event("tool_result", {
              "run_id": ctx.run_id,
              "trace_id": ctx.trace_id,
              "tool": name,
              "latency_ms": int((time.time() - t0) * 1000),
              "ok": False,
              "error_class": type(e).__name__,
          })
          raise
JAVASCRIPT
import crypto from "node:crypto";

export function stableHash(obj) {
const raw = JSON.stringify(obj);
return crypto.createHash("sha256").update(raw).digest("hex");
}

export class ToolGateway {
constructor({ impls = {}, logger }) {
  this.impls = impls;
  this.logger = logger;
}

call(ctx, name, args) {
  const fn = this.impls[name];
  const argsHash = stableHash(args);

  if (!fn) {
    this.logger.event("tool_call", {
      run_id: ctx.run_id,
      trace_id: ctx.trace_id,
      tool: name,
      args_hash: argsHash,
      ok: false,
      error_class: "unknown_tool",
    });
    throw new Error("unknown tool: " + name);
  }

  const t0 = Date.now();
  this.logger.event("tool_call", {
    run_id: ctx.run_id,
    trace_id: ctx.trace_id,
    tool: name,
    args_hash: argsHash,
  });

  try {
    const out = fn(args);
    this.logger.event("tool_result", {
      run_id: ctx.run_id,
      trace_id: ctx.trace_id,
      tool: name,
      latency_ms: Date.now() - t0,
      ok: true,
    });
    return out;
  } catch (e) {
    this.logger.event("tool_result", {
      run_id: ctx.run_id,
      trace_id: ctx.trace_id,
      tool: name,
      latency_ms: Date.now() - t0,
      ok: false,
      error_class: e?.name || "Error",
    });
    throw e;
  }
}

Combínalo con:

  • budgets (/es/governance/budget-controls)
  • dedupe contra tool spam (/es/failures/tool-spam)
  • unit tests que fijen stop reasons (/es/testing-evaluation/unit-testing-agents)

Fallo real (con números)

Shipeamos un agente de research “read-only” que llamaba http.get.

Un partner empezó a devolver 200 con payload de error (sí). Nuestro wrapper trató “200 == ok” y logueábamos solo “success”.

Impacto:

  • ~18% de runs dieron resúmenes incorrectos pero confiados por ~2 horas
  • ~30 tickets
  • on-call: ~4 horas para demostrar que no era “el modelo alucinando”

Arreglo:

  1. log de error_class normalizada + fallos de validación
  2. guardar args_hash + latencia para localizar hot spots
  3. alerta: validation_fail_rate > 2% por 5 minutos

No necesitas logs perfectos. Necesitas logs que respondan “¿qué pasó?” en <10 minutos.

Trade-offs

  • Loguear args en claro ayuda y también filtra PII. Por defecto: args_hash.
  • Guardar resultados completos facilita debugging y complica compliance. Sampling + redacción.
  • Mucho logging puede ser su propia caída. Empieza por lo que vas a alertar.

Cuándo NO hacer esto

  • Si el agente corre solo en un entorno local/trust, puedes ser más laxo (por un rato).
  • Si estás cambiando el loop cada día: logging ligero pero consistente (IDs + stop reasons).
  • No te fabriques un tracing casero si no lo puedes operar.

Checklist (copy-paste)

  • [ ] run_id, trace_id, request_id, user_id en cada evento
  • [ ] tool_call + tool_result (name, args_hash, latency, ok, error_class)
  • [ ] stop_reason + budgets al final del run
  • [ ] policy de redacción (PII/secrets) + hashes por defecto
  • [ ] alertas: tool calls/run, timeouts, validation fails
  • [ ] una query “incidente” por failure frecuente (dashboard / saved search)

Config segura por defecto (YAML)

YAML
logging:
  ids:
    run_id: required
    trace_id: required
    request_id: required
  tool_calls:
    enabled: true
    store_args: false
    store_args_hash: true
    store_results: "sampled"
    result_sample_rate: 0.01
  pii:
    redact_fields: ["email", "phone", "token", "authorization", "cookie"]
  stop_reasons:
    enabled: true
alerts:
  tool_calls_per_run_p95: { warn: 10, critical: 20 }
  timeout_rate: { warn: 0.02, critical: 0.05 }
  validation_fail_rate: { warn: 0.02, critical: 0.05 }

Implementar en OnceOnly (opcional)

Implementar en OnceOnly
Loguea tool calls con args_hash + stop reasons (seguro por defecto).
Usar en OnceOnly
# onceonly-python: governed audit logs + metrics
import os
from onceonly import OnceOnly

client = OnceOnly(api_key=os.environ["ONCEONLY_API_KEY"])
agent_id = "support-bot"

# Pull last 50 actions (includes args_hash + decisions)
for e in client.gov.agent_logs(agent_id, limit=50):
    print(e.ts, e.tool, e.decision, e.args_hash, e.spend_usd, e.reason)

# Rollups for dashboards/alerts
m = client.gov.agent_metrics(agent_id, period="day")
print("spend_usd=", m.total_spend_usd, "blocked=", m.blocked_actions)

FAQ (3–5)

¿Debería loguear args en claro?
Por defecto no. Log args_hash + campos seguros. Activa args en claro solo en una ventana de incidente, con redacción.
¿Cuál es el campo más útil?
Un run_id/trace_id estable en cada evento. Sin eso no reconstruyes nada.
¿Cómo detecto loops rápido?
Alertra sobre tool_calls/run y repetición de (tool, args_hash). Combínalo con taxonomía stop_reason.
¿Necesito distributed tracing?
Si tus tools llaman otros servicios, sí. Empieza con propagación de trace_id + spans alrededor de tool calls.

Q: ¿Debería loguear args en claro?
A: Por defecto no. Log args_hash + campos seguros. Args en claro solo temporalmente durante incidentes.

Q: ¿Cuál es el campo más útil?
A: run_id/trace_id estable en cada evento.

Q: ¿Cómo detecto loops rápido?
A: Alertra tool_calls/run + repetición (tool, args_hash). Luego lee /failures/tool-spam.

Q: ¿Necesito distributed tracing?
A: Si cruzas servicios, sí. Trace IDs + spans de tool primero.

⏱️ 7 min de lecturaActualizado Mar, 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

Esta documentación está curada y mantenida por ingenieros que despliegan agentes de IA en producción.

El contenido es asistido por IA, con responsabilidad editorial humana sobre la exactitud, la claridad y la relevancia en producción.

Los patrones y las recomendaciones se basan en post-mortems, modos de fallo e incidentes operativos en sistemas desplegados, incluido durante el desarrollo y la operación de infraestructura de gobernanza para agentes en OnceOnly.