Observabilidad para apps con LLM: trazas, métricas y convenciones GenAI de OpenTelemetry
Una aplicación con LLM puede responder con un 200, en tiempo razonable, y aun así estar fallando: inventando datos, usando un modelo distinto del que pensabas, perdiendo la caché de prompts y triplicando el costo, o entrando en un bucle de llamadas a herramientas. Los dashboards clásicos de APM (latencia, errores, throughput) no alcanzan para ver nada de eso.
La buena noticia es que no hace falta inventar un esquema propio. OpenTelemetry tiene convenciones semánticas específicas para IA generativa (GenAI) que definen nombres estándar para spans, atributos y métricas. Están en estado Development, es decir, todavía pueden cambiar, y recientemente se mudaron a un repositorio dedicado dentro del proyecto. Aun así, son la mejor base disponible para no quedar atado a un proveedor de observabilidad.
Qué hay que ver en una app con LLM
Además de las señales habituales, una app con LLM necesita responder estas preguntas:
| Pregunta | Señal |
|---|---|
| ¿Cuánto tarda en empezar a responder? | Tiempo hasta el primer chunk (streaming) |
| ¿Cuánto cuesta cada request, feature y cliente? | Tokens de entrada, salida y caché, por modelo |
| ¿Estoy aprovechando la caché de prompts? | Tokens leídos de caché sobre tokens de entrada |
| ¿Qué modelo respondió realmente? | Modelo pedido vs. modelo de la respuesta |
| ¿Por qué terminó la generación? | Finish reasons (longitud máxima, filtro, herramienta) |
| ¿Qué hizo el agente? | Árbol de spans: llamadas al modelo, herramientas y subagentes |
| ¿La respuesta fue buena? | Evals en línea, feedback de usuarios, revisión de muestras |
Las convenciones GenAI de OpenTelemetry
Para una llamada de inferencia, la especificación define que el nombre del span sea {gen_ai.operation.name} {gen_ai.request.model} (por ejemplo, chat gpt-6-luna) y que el tipo de span sea CLIENT. Los atributos más útiles:
| Atributo | Qué registra |
|---|---|
gen_ai.operation.name |
La operación: chat, generate_content, text_completion, etc. (obligatorio) |
gen_ai.provider.name |
El proveedor, por ejemplo openai (obligatorio) |
gen_ai.request.model |
El modelo pedido |
gen_ai.response.model |
El modelo que respondió |
gen_ai.usage.input_tokens / gen_ai.usage.output_tokens |
Tokens consumidos |
gen_ai.usage.cache_read.input_tokens |
Tokens de entrada servidos desde la caché del proveedor |
gen_ai.response.finish_reasons |
Por qué terminó cada generación |
gen_ai.conversation.id |
Identificador de la conversación o sesión |
error.type |
El tipo de error, si lo hubo |
Para métricas, la especificación incluye, entre otras, gen_ai.client.operation.duration y gen_ai.client.operation.time_to_first_chunk del lado del cliente, y métricas para agentes y herramientas como gen_ai.invoke_agent.duration y gen_ai.execute_tool.duration.
Instrumentación manual, paso a paso
Muchas librerías y SDK ya ofrecen instrumentación automática. Si no es tu caso, o querés entender qué se registra, esto es lo mínimo con la API de OpenTelemetry en Python:
from opentelemetry import trace
from opentelemetry.trace import SpanKind, Status, StatusCode
tracer = trace.get_tracer("mi-app.llm")
def llamar_modelo(cliente, modelo: str, prompt: str, feature: str):
with tracer.start_as_current_span(f"chat {modelo}", kind=SpanKind.CLIENT) as span:
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.provider.name", "openai")
span.set_attribute("gen_ai.request.model", modelo)
span.set_attribute("app.feature", feature) # atributo propio para costos por feature
try:
resp = cliente.responses.create(model=modelo, input=prompt)
except Exception as e:
span.set_attribute("error.type", type(e).__name__)
span.set_status(Status(StatusCode.ERROR))
raise
span.set_attribute("gen_ai.response.model", resp.model)
span.set_attribute("gen_ai.usage.input_tokens", resp.usage.input_tokens)
span.set_attribute("gen_ai.usage.output_tokens", resp.usage.output_tokens)
return resp
Dos detalles importantes. Primero, app.feature no es parte del estándar: es un atributo propio, y es el que te va a permitir responder "¿cuánto cuesta el resumen de tickets?". Segundo, registrar gen_ai.response.model además del modelo pedido te permite detectar si un alias o un modelo por defecto empieza a resolver a otra versión.
Agentes: el árbol de spans es la herramienta de debugging
En un agente, una sola tarea puede generar decenas de llamadas al modelo y a herramientas. Si cada paso es un span hijo de la tarea, el trace muestra exactamente dónde se fue el tiempo y el dinero:
invoke_agent soporte-tickets 12,4 s
├── chat gpt-6-luna 0,9 s (clasificación)
├── execute_tool buscar_pedido 0,3 s
├── chat gpt-6-sol 4,1 s
├── execute_tool buscar_pedido 0,3 s <- la misma herramienta otra vez
├── chat gpt-6-sol 5,8 s
└── execute_tool enviar_respuesta 1,0 s
Patrones a vigilar: la misma herramienta llamada varias veces con los mismos argumentos, cantidad de pasos por tarea que crece con el tiempo y tareas que terminan por límite de pasos en lugar de por éxito.
El contenido de los prompts: opt-in y con cuidado
Los atributos que capturan el contenido de los mensajes (gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions) están definidos como Opt-In en la especificación, y la propia documentación advierte que probablemente contengan información sensible, incluidos datos personales. Las instrumentaciones no deberían capturarlos por defecto.
Si decidís registrarlos:
- Redactá datos personales, secretos y tokens antes de exportar.
- Muestreá: no hace falta guardar el contenido de todos los requests.
- Separá la retención: los metadatos (tokens, latencia) pueden guardarse meses; el contenido, lo mínimo necesario.
- Revisá dónde se almacena: muchas herramientas de observabilidad son SaaS, y mandar prompts con datos de clientes a un tercero puede tener implicancias legales y contractuales.
De la telemetría al costo
Con tokens por modelo y por feature, el costo se calcula en tu backend de métricas o con un job simple que multiplique por el precio vigente. Algunas alertas útiles:
- Tasa de aciertos de caché que cae de forma abrupta (suele indicar un cambio en el prefijo del prompt o en las definiciones de herramientas).
- Requests cerca de los umbrales de precio del proveedor, como el de 272.000 tokens de entrada en GPT-6 Sol y Luna.
- Costo diario por feature o por cliente por encima de un presupuesto.
- Cambio en el modelo de respuesta para un mismo modelo pedido.
Qué hacer con esto
- Instrumentar cada llamada a un LLM con las convenciones GenAI de OpenTelemetry (o verificar que tu SDK ya lo haga).
- Agregar atributos propios de negocio: feature, cliente, versión de prompt.
- Modelar los agentes como árboles de spans (tarea, llamadas al modelo, herramientas).
- Mantener la captura de contenido desactivada por defecto; si se activa, redactar y muestrear.
- Armar un dashboard de costo por feature y de tasa de aciertos de caché.
- Conectar la observabilidad con las evals: los casos raros de producción son los mejores casos de prueba.
¿Hoy podés responder cuánto cuesta cada feature con IA de tu producto, o solo conocés la factura total del mes?
Lecturas relacionadas
- Cómo evaluar LLMs en producción: guía práctica de evals para equipos de desarrollo: cómo armar un dataset de evaluación, cuándo usar LLM-as-a-judge y cómo sumarlo al CI.
- GPT-6 Sol y Luna: precios, prompt caching y cómo recalcular el costo de tus agentes: los precios nuevos de Sol y Luna, el prompt caching y la letra chica de los 272K tokens.
Fuentes
- OpenTelemetry, Generative AI semantic conventions (aviso de traslado): https://opentelemetry.io/docs/specs/semconv/gen-ai/
- OpenTelemetry, repositorio de convenciones semánticas GenAI: https://github.com/open-telemetry/semantic-conventions-genai
- OpenTelemetry GenAI, spans de inferencia y atributos: https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md
- OpenTelemetry GenAI, métricas: https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-metrics.md
- OpenAI, "Better prompt caching for GPT-6" (22/09/2026): https://openai.com/index/better-prompt-caching-for-gpt-6/
- OpenAI API docs, GPT-6 Sol (precios y umbral de 272K): https://developers.openai.com/api/docs/models/gpt-6-sol