ObservabilityOpenTelemetryLLM APIMonitoringProduction Engineering

Observabilidad de LLM APIs con OpenTelemetry (guía 2026)

1 min de lectura

El 62% de las fallas de LLM en producción pasan desapercibidas para el monitoreo HTTP por más de 48 horas. Un código de estado 200 OK significa que el servidor respondió —no que la respuesta fue correcta, que el contexto recuperado estaba fresco o que el agente no se encerró en 47 llamadas de herramientas redundantes. Tu dashboard está en verde mientras las alucinaciones llegan a los clientes que pagan y una regresión de la plantilla de prompts envenena cada respuesta desde el despliegue del martes.

Esta guía construye un stack de observabilidad de tres capas —OTel base, convenciones semánticas GenAI, tipos de span OpenInference— que convierte cada request de usuario en un árbol de trazas forense. Un llamado de función lo registra. El muestreo por cola retiene cada traza de falla sin explotar tu presupuesto de almacenamiento.

Por qué tu dashboard de monitoreo es ciego a las fallas de LLM

Por qué el APM estándar falla para aplicaciones de LLM

HTTP 200 no significa “respuesta correcta”. Significa que el servidor respondió. El proyecto OpenTelemetry proporciona el formato de transporte y la infraestructura del collector —pero las aplicaciones de LLM necesitan convenciones semánticas sobre esa base. El APM estándar falla porque las aplicaciones de LLM fallan de maneras que los códigos de estado HTTP no pueden expresar:

  • Alucinación. El modelo devolvió una respuesta confiada y bien formateada. Cada hecho en ella es incorrecto. Estado HTTP: 200.
  • Negativa silenciosa. El modelo debía haber respondido. Se negó —cortésmente, en JSON perfecto. Estado HTTP: 200.
  • Pico de costos. Un request generó 32,000 tokens de pensamiento porque el esfuerzo de razonamiento se configuró en “alto” para una tarea de clasificación simple. Estado HTTP: 200. Ningún dashboard muestra el conteo de tokens de pensamiento.

No necesitas ver “el request tuvo éxito”. Necesitas ver “la relevancia del contexto recuperado fue 0.3, lo que causó una puntuación de fidelidad de generación de 0.4, lo que significa que el usuario recibió una respuesta incorrecta a pesar de que todo se veía verde”. Rastrear llamadas de embeddings junto con los completados de chat es esencial cuando la calidad de recuperación baja —instrumentar ambos endpoints bajo una sola traza revela el panorama completo.

La arquitectura de tres capas

No son tres opciones. Necesitas las tres.

Capa 1: OpenTelemetry base. El formato de transporte (OTLP), la propagación de contexto (W3C trace context), el pipeline del collector. Este es el sustrato —cada backend de observabilidad habla OTLP. Cada microservicio emite spans de OTel. Sin esta capa, estás encerrado en el formato propietario de un proveedor. Con ella, puedes cambiar de backend sin re-instrumentar ni una línea.

Capa 2: Convenciones semánticas OTel-GenAI. Atributos de span estandarizados para operaciones de LLM: gen_ai.system (qué proveedor), gen_ai.request.model (qué versión de modelo), gen_ai.usage.input_tokens y gen_ai.usage.output_tokens (consumo de tokens), gen_ai.operation.name (chat vs. embeddings vs. ejecución de herramientas). Sin esta capa, todos tus spans de LLM se ven idénticos —no puedes distinguir un completado de chat de una llamada de embeddings.

Capa 3: Tipos de span OpenInference. Catorce tipos de span conscientes de LLM que las convenciones GenAI aún no enumeran: LLM, CHAIN, RETRIEVER, TOOL, EMBEDDING, AGENT, RERANKER, GUARDRAIL, EVALUATOR, CONVERSATION, VECTOR_DB y más. Sin esta capa, la traza de tu pipeline RAG es una lista plana de llamadas HTTP. Con ella, ves EMBEDDING —RETRIEVER —RERANKER —LLM como etapas distintas —y sabes exactamente qué etapa agregó el pico de latencia de 800ms.

El árbol de trazas como unidad mínima de comprensión

Una línea de log aislada no puede diagnosticar un problema de LLM. La pregunta nunca es “¿qué devolvió esta llamada de API?” Es “¿cuál fue la cadena causal completa: consulta de usuario —clasificación de intención —chunks recuperados —puntuaciones del reranker —prompt final —respuesta del LLM —puntuación de evaluación?” Un árbol de trazas captura esa cadena. Una traza = el registro forense completo de una interacción de usuario.

Por qué la observabilidad es innegociable

Costos sin visibilidad

Un bucle de agente sin monitoreo quemó $5,000 durante un fin de semana en un despliegue que investigué. El agente cayó en un bucle de llamadas de herramientas el viernes por la noche —search_kb("return policy") devolvió “sin resultados”, así que el agente llamó search_kb("return policy EU"), luego search_kb("return policy Europe"), luego 44 variaciones más —cada una una llamada completa de API de LLM con contexto. Nadie se dio cuenta hasta la alerta de facturación del lunes.

La atribución de costos por span —gen_ai.cost.input, gen_ai.cost.output, gen_ai.cost.total adjuntos a cada span de LLM— lo atrapa en minutos, no en días. Configura una alerta: si cualquier traza individual supera los $2.00 en costos de API acumulados, dispara una notificación. El costo de la infraestructura de alertas es menor que un incidente de fin de semana.

La atribución de costos es la capa de detección. Para la capa de prevención —estrategias de caching, selección de niveles de modelo y procesamiento por lotes— consulta nuestra guía de estrategias de optimización de costos.

Calidad sin visibilidad

Una migración de modelo de GPT-4o a GPT-5.5 se veía limpia en los dashboards HTTP. La latencia mejoró 15%. La tasa de errores no cambió. Lo que el dashboard no mostró: el modelo nuevo manejaba la salida estructurada ligeramente diferente —null apareció en tres campos que nunca antes habían sido null. El formato era JSON válido. La lógica de negocio que lo consumía se rompió silenciosamente.

Las puntuaciones de evaluación adjuntas al span atrapan esto en cinco minutos. Tu rúbrica de evaluación se ejecuta contra las trazas de producción continuamente. Cualquier caída en fidelidad, adherencia al contexto o cumplimiento de formato dispara una alerta —antes de que los usuarios lo noten, antes de que se acumulen tickets de soporte, antes de que las métricas de calidad del trimestre reciban un golpe. Para el pipeline de CI/CD que ejecuta estas evaluaciones antes del despliegue, consulta nuestra guía de testing y evaluación.

Cumplimiento sin visibilidad

Los auditores de SOC 2 Type II preguntan: “Muéstranos el registro completo de llamadas de API para el usuario X en la fecha Y —qué datos se enviaron, qué modelo los procesó, qué se devolvió?” Si tus llamadas de API de LLM no producen trazas estructuradas con la política de retención correcta, la respuesta es: “no podemos”. Eso no es un hallazgo. Es una calificación —una palabra mucho más cara en un informe de auditoría.

Las trazas estructuradas satisfacen el requisito de pista de auditoría. Para los controles de control de acceso y manejo de datos que completan la preparación para SOC 2, el logging de acceso estructurado y las políticas de retención alineadas con tu marco de cumplimiento cierran la brecha.

Cómo configurar la observabilidad de LLM

Paso 1: registro en un solo llamado

Un llamado de función en tu módulo de inicio. Eso es todo.

from fi_instrumentation import register, ProjectType, SemanticConvention

trace_provider = register(
    project_name="checkout_assistant",
    project_type=ProjectType.OBSERVE,
    semantic_convention=SemanticConvention.OPENINFERENCE,
    metadata={"git_sha": "abc123", "environment": "production"},
    batch=True,
)

from openinference.instrumentation.openai import OpenAIInstrumentor
from openinference.instrumentation.langchain import LangChainInstrumentor

OpenAIInstrumentor().instrument(tracer_provider=trace_provider)
LangChainInstrumentor().instrument(tracer_provider=trace_provider)

El parámetro semantic_convention es la decisión arquitectónica clave aquí. Configúralo en OPENINFERENCE, OTEL_GENAI o OPENLLMETRY —tu código de instrumentación no cambia. Solo cambia el nombramiento de atributos en los spans emitidos. Esto importa cuando cambias de backend de observabilidad: Datadog espera una convención, Langfuse otra, SigNoz una tercera. Un interruptor de configuración. Sin cambios de código.

Cobertura: 50+ frameworks de Python, 39 paquetes de TypeScript, 24 módulos de Java, C#. OpenAI, Anthropic, LangChain, LlamaIndex, Haystack, DSPy —todo auto-instrumentado.

Paso 2: enriquecimiento de spans

Un span sin user_id, session_id y prompt_version es un huérfano. Puedes ver qué pasó pero no a quién o con qué configuración.

from contextlib import contextmanager

@contextmanager
def using_attributes(**kwargs):
    # Attach attributes to the current span; all child spans inherit them
    with tracer.start_as_current_span("user-interaction") as span:
        for key, value in kwargs.items():
            span.set_attribute(key, value)
        yield span

with using_attributes(
    session_id="sess_a1b2c3",
    user_id="user_42",
    metadata={
        "prompt_template": "checkout_v3.2",
        "ab_bucket": "treatment",
        "feature_flag": "new_upsell_logic"
    }
):
    response = client.chat.completions.create(...)

El conjunto mínimo de atributos para cada traza: session.id, user.id, prompt.version, feature.id, tenant.id. Sin estos, tus datos de trazas no pueden responder “¿el prompt checkout_v3.2 causó la regresión, o el cambio de versión del modelo?” —la primera pregunta que harás durante un incidente.

Paso 3: eval-as-span-attribute

Las puntuaciones de evaluación que viven en una base de datos separada, requiriendo un join manual contra IDs de trazas, son puntuaciones que nadie mira. EvalTag lo arregla: declara evaluadores en el momento del registro, y sus puntuaciones se escriben directamente en el span original como atributos gen_ai.evaluation.<rubric>.score —con cero latencia de request añadida.

register(
    project_name="checkout_assistant",
    evaluators=[
        "GROUNDEDNESS",         # Are claims supported by retrieved context?
        "CONTEXT_ADHERENCE",    # Is the answer using the provided context?
        "PROMPT_INJECTION",     # Is there an injection attempt in the input?
        "TASK_COMPLETION",      # Did the model complete the requested task?
    ],
)

El evaluador se ejecuta asincrónicamente —el usuario recibe su respuesta sin esperar la puntuación. La puntuación aparece en el span en segundos. Tu dashboard se actualiza. Si GROUNDEDNESS cae por debajo de 0.7 en una ventana de 5 minutos, tu alerta se dispara. Sin pipeline de evaluación separado. Sin correlación manual. Un árbol de trazas, una fuente de verdad.

Paso 4: muestreo por cola

El muestreo por cabeza —“mantén el 10% de todas las trazas al azar”— es el valor por defecto en la mayoría de las configuraciones de APM. Es catastróficamente incorrecto para aplicaciones de LLM. Las fallas son raras. Los valores atípicos de costo son raros. Las salidas de baja calidad son raras. Una muestra aleatoria uniforme del 10% tira el 90% de las trazas que realmente importan.

El muestreo por cola invierte esto: el collector ve la traza completa antes de decidir si conservarla. La regla de retención:

  • Mantén el 100% de trazas con errores (5xx, timeout, límite de tasa)
  • Mantén el 100% de trazas con cualquier puntuación de evaluación por debajo del umbral
  • Mantén el 100% de trazas con costo por encima de p95
  • Mantén el 1–10% de trazas limpias, rápidas y correctas

Tus costos de almacenamiento se mantienen controlados. Las trazas que realmente necesitas para depurar permanecen disponibles.

Paso 5: retención de tres niveles

No pagues precios de ClickHouse por datos de cumplimiento regulatorio que accedes una vez al año.

NivelAlmacenamientoDuraciónContenido
CalienteClickHouse / InfluxDB14-30 díasTodas las trazas retenidas —dashboards en vivo y alertas
TibioColumnar (S3/Parquet)90 díasTrazas completas —cumplimiento y depuración retrospectiva
FríoAlmacenamiento de objetos (S3 Glacier)1-7 añosTrazas comprimidas —retención regulatoria

El nivel caliente es para operaciones. El nivel tibio es para depurar el incidente del último trimestre. El nivel frío es para auditores. Cada nivel cuesta aproximadamente un orden de magnitud menos que el anterior.

Patrones de trazas de producción para cargas específicas

Topología de trazas RAG

Una traza plana de un pipeline RAG es inútil. Necesitas ver cada etapa como un span distinto:

EMBEDDING span [model: text-embedding-3-small, tokens: 450, latency: 32ms]
  → RETRIEVER span [vector_db: pgvector, top_k: 20, index: hnsw, latency: 8ms]
    → RERANKER span [model: bge-reranker-large, candidates: 20→5, latency: 45ms]
      → LLM span [model: gpt-4o, input_tokens: 2840, output_tokens: 380, latency: 1.2s]

Cuando la calidad de recuperación baja, miras el span RETRIEVER —¿las puntuaciones de similitud son bajas? Verifica si el modelo de embeddings se desvió. Cuando la calidad de generación baja pero la recuperación se ve bien, miras el span LLM —¿se están alimentando los chunks recuperados en el orden correcto? ¿El prompt del sistema está intacto? La topología te dice dónde mirar, no solo que algo está mal. Para la arquitectura completa del pipeline RAG detrás de este modelo de trazas, consulta nuestra guía de RAG en producción.

Topología de trazas de agentes

Un agente de 6 nodos de LangGraph trazado en plano es una pesadilla de depuración. Ves 100 spans. No sabes qué nodo disparó qué herramienta, qué herramienta falló o dónde comenzó el bucle.

La topología correcta:

Root: AGENT span [session_id, user_id, task]
  —Reasoning span: "plan to answer user's question about order status"
    —TOOL span: lookup_order(order_id="ORD-12345") [latency: 180ms, status: success]
  —Reasoning span: "order found, now check shipping"
    —TOOL span: track_shipment(tracking_id="ZYX-987") [latency: 340ms, status: success]
  —LLM span: synthesis [model: claude-sonnet-4, input_tokens: 1520, output_tokens: 210]

Para LangGraph específicamente, agrega langgraph.node.name, langgraph.node.type y eventos de aristas condicionales a cada span. Sin estos, cuando tu agente de 6 nodos se atasca en un bucle, no puedes decir qué nodo es el problema. Con ellos, la traza se renderiza como un grafo de topología —y el nodo en bucle es visualmente obvio. Para los patrones de orquestación multi-agente que generan estas trazas, consulta nuestra guía de arquitectura multi-agente.

Atribución de costos por span

Cada span de LLM lleva gen_ai.cost.input, gen_ai.cost.output, gen_ai.cost.cache_read y gen_ai.cost.total. A nivel de gateway, mantén presupuestos jerárquicos: organización —equipo —usuario —sesión. Genera desgloses de costos mensuales por equipo, por modelo, por caso de uso —automáticamente, desde los datos de trazas. Sin conciliación de facturación manual. Sin “la partida de IA es una caja negra”. Para configurar el throttling de requests por usuario y por endpoint que evite que los bucles de agentes descontrolados exploten tu presupuesto, consulta la documentación de límites de tasa.

Errores de observabilidad que cuestan en producción

Instrumentación solo con SDK del proveedor

Instrumentaste con el SDK nativo de Datadog porque era el camino más rápido a un dashboard. Seis meses después, tu equipo quiere evaluar Langfuse para trazado específico de LLM. Cada sitio de llamada necesita re-instrumentación.

Arreglo: instrumenta con OTel. Es la capa de abstracción. Cambia backends cambiando la configuración del exportador, no el código de instrumentación. Los SDKs de proveedores son objetivos de salida, no frameworks de instrumentación.

Muestreo aleatorio uniforme

Tu tasa de muestreo es 10%. Estás descartando aleatoriamente el 90% de tus trazas —incluida aquella en la que un usuario fue cobrado dos veces porque el agente hizo un bucle, aquella en la que un intento de inyección de prompt casi tuvo éxito, y aquella en la que un solo request consumió $18 en tokens de pensamiento.

Arreglo: muestreo por cola. El collector ve la traza completa, luego decide. Fallas, valores atípicos de costo y salidas de baja calidad: conserva el 100%. Trazas limpias: conserva un pequeño porcentaje para comparación de línea base.

Sin topología de LangGraph en trazas de agentes

Desplegaste un agente multi-nodo. Tus trazas muestran 87 spans por request de usuario en una lista plana. El martes pasado tu agente se atascó en un bucle. Tomó tres horas identificar el nodo culpable —porque “87 spans planos” no te dice el grafo de ejecución.

Arreglo: langgraph.node.name y langgraph.node.type en cada span. Eventos de aristas condicionales. Tu visor de trazas debe renderizar al agente como un grafo, no una lista.

Omitir spans emitidos por el gateway

Traces meticulosamente tu código de aplicación. Pero accedes a los LLM a través de una plataforma de API unificada —y los spans del gateway (latencia del lado del proveedor, decisión de enrutamiento, hit/miss de caché, disparador de fallback) son invisibles para tu trazador de aplicación. Cuando la latencia se dispara, no puedes decir si es tu código, el gateway o el proveedor.

Arreglo: los spans del gateway son parte de tu traza. Si tu plataforma de API emite spans OTel, configura tu collector para ingerirlos. Un endpoint de API unificado significa un punto de integración para la observabilidad del gateway —configúralo una vez, cada llamada de modelo queda cubierta.

Para patrones de despliegue que combinan observabilidad con cadenas de fallback, lógica de reintentos y monitoreo de costos entre modelos, consulta la guía de optimización de producción.

FAQ

¿Necesito las tres capas (OTel base + GenAI + OpenInference)?

Sí. OTel base previene el bloqueo del proveedor. Las convenciones semánticas de GenAI estandarizan atributos específicos de modelo (conteos de tokens, identidad del modelo) para que tus dashboards no se rompan cuando cambies de proveedores. Los tipos de span de OpenInference te dan topología consciente de LLM —sin ellos, cada span es “una llamada de API” y no puedes distinguir recuperación de generación de ejecución de herramientas.

¿Cuál es la sobrecarga de rendimiento?

Creación de spans y configuración de atributos: menos del 1% de impacto en latencia. Evaluadores (EvalTag): cero impacto en la latencia orientada al usuario —se ejecutan asincrónicamente después de enviarse la respuesta. Muestreo por cola: se ejecuta en el collector, no en el proceso de tu aplicación. La sobrecarga total es insignificante comparada con la latencia de 200ms–10s de las propias llamadas a la API de LLM. Para reducir costos de entrada en trazas repetidas, las estrategias de prompt caching combinan naturalmente con la atribución de costos a nivel de span.

¿Auto-alojar o SaaS para observabilidad?

Auto-alojar: SigNoz (nativo OTel, dashboards GenAI) + ClickHouse + Grafana. Bueno si ya ejecutas infraestructura OTel. SaaS: Langfuse Cloud (traza primero, ClickHouse afinado), Datadog LLM Observability. Bueno si quieres un dashboard en 10 minutos. La abstracción de OTel significa que puedes empezar con SaaS y migrar a auto-alojado sin re-instrumentar.

¿Cómo enmascaro PII de las trazas de LLM?

Enmascara en el collector —no en el código de la aplicación. Patrones de regex para números de tarjetas de crédito, SSNs y direcciones de correo. Clasificadores NER para nombres y direcciones físicas. Reglas personalizadas para API keys y tokens de acceso. El principio: los secretos crudos nunca cruzan el límite de tu red. Se eliminan en el procesador del collector antes de exportar a cualquier backend externo.

¿Cuál es el camino más simple hacia la observabilidad unificada de LLM?

Un punto de integración. Cuando cada llamada de modelo —GPT, Claude, Gemini, DeepSeek— fluye a través de un endpoint de API, configuras la exportación de OTel una vez. Los spans emitidos por el gateway (latencia del lado del proveedor, decisiones de enrutamiento, tasas de hit de caché, disparadores de fallback) llegan pre-formateados junto a tus spans de aplicación. Sin coser trazas de tres SDKs de proveedores diferentes. Sin preguntarse si el pico de latencia está en tu código, el gateway o el proveedor —porque los tres están en el mismo árbol de trazas. Empieza con una API key para todos los modelos y ve trazas unificadas en la plataforma TokSpan.

La observabilidad para aplicaciones de LLM no es una preocupación de “organización madura”. Es una preocupación de “primer despliegue de producción”. El costo de no tenerla se mide en incidentes de fin de semana, regresiones silenciosas de calidad y calificaciones de auditoría —todo lo cual cuesta más que configurar las tres capas descritas aquí.

El patrón de registro toma un llamado de función. El adjunto eval-as-span toma cero latencia adicional. El muestreo por cola mantiene tu factura de almacenamiento bajo control mientras retiene cada traza que importa. Empieza con un modelo en un servicio. Instrumenta. Observa el árbol de trazas por un día. Encontrarás algo que no sabías que estaba pasando —todos lo hacen en su primer día con observabilidad real.