← SCRAM AI Lab
Domina el tracing de pipelines LLM con OpenTelemetry y GenAI semantic conventions. Optimiza latencias en producción y evita sobrecostos con tail sampling.
May 21, 2026
471 lecturas

Un chatbot LLM en producción es un sistema distribuido escondido: el usuario manda un mensaje, tu router decide el tier, llama a un provider (Anthropic, OpenAI, Google), parsea la respuesta, ejecuta side-effects (escribir al CRM, llamar a GLPI, sincronizar con Mautic), y devuelve. Si solo tienes logs, vas a poder reconstruir el camino — pero no vas a poder ver de un vistazo dónde se fueron los 6 segundos en un request lento. Para eso existe OpenTelemetry, y para LLMs las semantic conventions gen_ai son el estándar.
POST /v1/chat/sessions/:id/messages [root span: 4.2s]
├── sanitize_message [12ms]
├── classify_intent [180ms]
├── ai_router.select_tier [3ms]
├── llm_call (tier=2, anthropic) [2.8s]
│ ├── prompt_assembly [45ms]
│ ├── anthropic.messages.create [2.7s]
│ └── response_parsing [38ms]
├── side_effects [1.1s]
│ ├── crm.capture_lead [320ms]
│ ├── glpi.create_ticket [580ms]
│ └── mautic.sync_contact [200ms]
└── persist_message [85ms]
Esa visualización en Grafana Tempo te dice en 3 segundos: "el side-effect de GLPI tardó 580ms y bloquea el response al usuario". Decisión: paralelizar side-effects no críticos, mover GLPI a background queue.
// tracing-init.ts (debe ser el PRIMER import en main.ts)
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'chat-api',
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.VERSION,
}),
traceExporter: new OTLPTraceExporter({
url: 'http://tempo:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false },
})],
});
sdk.start();
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('chat-api');
async function callLLM(provider: string, model: string, messages: Message[]) {
return tracer.startActiveSpan('llm_call', {
attributes: {
'gen_ai.system': provider, // anthropic | openai | google
'gen_ai.request.model': model, // claude-opus-4-7
'gen_ai.request.max_tokens': 4096,
'gen_ai.request.temperature': 0.7,
},
}, async (span) => {
try {
const response = await providers[provider].call(model, messages);
span.setAttributes({
'gen_ai.response.model': response.model,
'gen_ai.response.id': response.id,
'gen_ai.usage.input_tokens': response.usage.input_tokens,
'gen_ai.usage.output_tokens': response.usage.output_tokens,
'gen_ai.response.finish_reasons': response.stop_reason,
});
return response;
} catch (err) {
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
throw err;
} finally {
span.end();
}
});
}
Con logs estructurados puedes calcular latencia total de un request. No puedes ver:
Logs y traces son complementarios. Loki para "qué pasó a lo largo del tiempo", Tempo para "qué pasó en este request específico". Linkéalos con trace_id en el JSON payload de tus logs.
Tempo soporta tail-based sampling — recolectas todo y decides al final del trace si lo persistes. Costo de storage baja 70% sin perder los traces que importan.
La pregunta de la siguiente iteración: ¿cómo correlacionas un trace con el embedding del prompt para detectar que ciertos topics consistentemente disparan latencia alta? Estamos probando un sidecar que extrae features del prompt y los manda como atributos del span.
OpenTelemetry te protege del acoplamiento con proveedores propietarios al estandarizar la telemetría bajo las especificaciones de la Cloud Native Computing Foundation (CNCF). Permite unificar en una sola vista distribuida el rastreo de tus llamadas a modelos fundacionales con tus bases de datos relacionales, colas de mensajería y microservicios auxiliares sin duplicar agentes ni SDKs.
Las plataformas especializadas en evaluación de LLM son excelentes para calcular métricas de alucinación o gestionar datasets de fine-tuning. Sin embargo, cuando el problema operativo radica en que una consulta a Redis bloqueó el hilo antes de contactar a la API de inferencia, las herramientas puramente orientadas a IA fallan al no registrar el contexto de la infraestructura subyacente.
Para determinar qué stack implementar según el estado del proyecto, evaluamos las alternativas según criterios de adopción corporativa:
| Criterio | OpenTelemetry Nativo + Tempo | Langfuse / Arize Phoenix | APM Propietario (Datadog / New Relic) |
|---|---|---|---|
| Correlación con backend tradicional | Nativa en un solo trace context | Limitada; requiere puentes manuales | Nativa mediante agentes propietarios |
| Vendor Lock-in | Nulo (estándar abierto CNCF) | Bajo (código abierto, esquema propio) | Alto (formatos y agentes cerrados) |
| Métricas especializadas (evals, feedback) | Requiere instrumentación manual | Integradas out-of-the-box | Módulos LLM dedicados con costo extra |
| Costo de almacenamiento y licencia | Solo infraestructura de hosting (S3/GCS) | Gratuito en auto-hospedaje o por consumo | Tarificación por millón de eventos / spans |
| Soporte GenAI Semantic Conventions | Oficial y directo en especificación | Adaptadores OTLP parciales | Mapeo interno a sus propios modelos |
Desplegar aplicaciones de IA generativa desde México, Colombia, Chile o Brasil añade un reto físico que ningún ajuste de código puede desaparecer: la distancia a los centros de datos de inferencia. Los principales proveedores (OpenAI, Anthropic y los clusters de Bedrock) concentran su cómputo en regiones como us-east-1 (Norte de Virginia) o us-west-2 (Oregón).
De acuerdo con mediciones de latencia reportadas por Cloudflare Radar y reportes de red de AWS, un paquete de red entre Ciudad de México y Virginia toma un Round Trip Time (RTT) base de entre 55ms y 75ms en condiciones óptimas. Si el tráfico se origina en São Paulo o Santiago, el RTT supera fácilmente los 130ms a 160ms por salto.
Si tu arquitectura comete el error de encadenar llamadas sincrónicas (por ejemplo, validar moderación, luego recuperar contexto de una base vectorial remota, luego invocar el modelo y finalmente parsear con una función externa), estás pagando ese castigo de RTT cuatro o cinco veces consecutivas antes de mandar el primer token al usuario. OpenTelemetry permite identificar con precisión milimétrica si los 3 segundos de espera provienen de la generación de tokens en el silicio (GPU/TPU) o del handshaking TLS acumulado en conexiones HTTP secuenciales desde infraestructura local.
La adopción de tracing distribuido introduce puntos ciegos si no se toman medidas preventivas de diseño:
La convención semántica permite agregar prompts completos y completions dentro de los atributos del span. En México, la Ley Federal de Protección de Datos Personales en Posesión de los Particulares (LFPDPPP), así como normativas equivalentes en la región (LGPD en Brasil), prohíben el almacenamiento de datos sensibles sin consentimiento explícito y cifrado riguroso. Almacenar identificadores tributarios (RFC, RUT), números de tarjeta o expedientes médicos en texto plano dentro de Grafana Tempo o Jaeger constituye una brecha de seguridad. Regla: sanitiza o aplica hash a los payloads de texto antes de adjuntarlos al span, limitando los atributos en producción exclusivamente a tokens consumidos, IDs de modelos, latencias y códigos de parada.
Cuando utilizas streaming mediante Server-Sent Events (SSE), emitir un span o un span event por cada token devuelto satura el bucle de eventos de NodeJS y desborda el recolector de OTLP. Para flujos continuos, instrumenta únicamente dos hitos: el span general de la llamada y un evento temporal interno correspondiente al Time to First Token (TTFT). El resto de la transmisión debe medirse como una métrica agregada al cierre del stream.
Una API de chat con 200 requests concurrentes que registre spans anidados de cada sub-llamada (embeddings, re-ranking, base vectorial, llamadas a herramientas) generará gigabytes de telemetría por hora. Si envías todos los datos hacia un SaaS sin configurar un OpenTelemetry Collector intermedio con tail-based sampling, la factura de observabilidad superará con rapidez el costo de la propia API de inferencia. Despliega un Collector local en tu clúster de Kubernetes o instancia de cómputo para filtrar el 100% de traces correctos dentro del percentil 50 antes de salir hacia el backend final.
Artículos relacionados
¿Quién autoriza a un agente? Permisos automáticos, aprobación humana y dos turnos
Claude, OpenAI y Microsoft ya traen controles para decidir qué ejecuta un agente. Los comparamos con el nuestro: la llave identifica y el código autoriza.
Traefik v2.10 con auto-renewal certs para 94 containers
Wildcard *.scram2k.com cubre la mayoría, certs individuales para el resto. acme.json shared, DNS-01 para wildcards, HTTP-01 para subdomains. Anti-patrón: cert por container.
Loki + Grafana para logs de chatbot: query patterns
Aprende a estructurar logs de chatbots en Loki y Grafana sin provocar explosión de cardinalidad: labels correctos, patrones LogQL y control de costos.