Tracing: ¿qué pasó en esta query?
Cuando tu RAG falle, los logs no van a alcanzar
Sección titulada «Cuando tu RAG falle, los logs no van a alcanzar»Estás en producción. Un usuario te dice: “tu RAG me dio una respuesta mal”. ¿Qué hacés?
Vas a console.log. Mirás los logs del request. Te das cuenta de que no tenés timestamps por stage. No tenés latencias. No tenés los chunks recuperados. No tenés los scores. No sabés si el LLM falló o si el retriever falló. Estás a ciegas.
Necesitás un trace: una línea de tiempo de la request con un span por stage, attributes en cada uno (query, k, latencia, scores), y un visualizador que te deje filtrar y comparar.
OpenTelemetry es el estándar. Phoenix (Arize) es un visualizador local, gratis, sin auth, perfecto para curso y prototipos.
OpenTelemetry en 5 minutos
Sección titulada «OpenTelemetry en 5 minutos»Cinco conceptos bastan:
- Trace: una request completa. Tiene un id único.
- Span: un stage dentro de la request. Tiene start/end time, attributes, status. Los spans pueden anidarse — un span padre
querycon hijosrewrite,retrieve,rerank,generate. - Attribute: un par clave-valor en un span (
rag.k = 4,rag.query = "...",model = "llama3.2:3b"). - Status: OK o ERROR (con mensaje).
- Exporter: hacia dónde se mandan los traces. Acá usamos OTLP/HTTP apuntando a Phoenix local.
Eso es todo. La librería tiene un montón más, pero no necesitás más para empezar.
El módulo del curso
Sección titulada «El módulo del curso»packages/01-vercel-ai-sdk/src/ops/tracing.ts — wrapper minimal sobre el SDK:
export async function traced<T>( name: string, fn: () => Promise<T>, attrs?: TracedAttrs,): Promise<T>;Uso:
const result = await traced('retrieve', async () => { return await qdrant.query(COLLECTION, { ... });}, { 'rag.k': 4 });El wrapper:
- Si OTel está deshabilitado (env var
OTEL_ENABLEDno es1), simplemente llama afny devuelve. Cero overhead cuando no estás traceando. - Si está habilitado, crea un span con el nombre, agrega los attributes, llama a
fn, mide la duración, captura excepciones, y cierra el span.
Setup en 3 pasos
Sección titulada «Setup en 3 pasos»1. Instalar las deps de OTel
Sección titulada «1. Instalar las deps de OTel»pnpm install(las deps @opentelemetry/api, @opentelemetry/sdk-node y @opentelemetry/exporter-trace-otlp-http ya están en packages/01-vercel-ai-sdk/package.json desde este nivel)
2. Levantar Phoenix
Sección titulada «2. Levantar Phoenix»pnpm phoenix:upInternamente: docker compose up -d phoenix. La definición está en docker-compose.yml con el profile observability (no se levanta con qdrant solo).
Phoenix queda escuchando en http://localhost:6006. Abrí esa URL en el browser — UI vacía, esperando traces.
3. Correr tu pipeline con tracing
Sección titulada «3. Correr tu pipeline con tracing»OTEL_ENABLED=1 pnpm vercel:query:adv "¿Qué propone Clean Architecture?" --rerank --citeLa primera línea del output va a ser:
[otel] tracing enabled → http://localhost:6006/v1/tracesCuando termine, recargá Phoenix y vas a ver el trace con todos los spans: rewrite, multi-query, retrieve.dense, rerank, generate. Cada uno con su latencia.
Wrappers en el orquestador
Sección titulada «Wrappers en el orquestador»query.adv.ts ya tiene los wraps puestos en este nivel. Cinco lugares:
const rewritten = await traced('rewrite', () => rewriteQuery(args.question));// ...const queries = await traced('multi-query', () => decomposeQuery(q, n));// ...const dense = await traced('retrieve.dense', () => denseRetrieve(sq, wide), { 'rag.query': sq, 'rag.k': wide,});// ...const reranked = await traced('rerank', () => llmRerank(q, asCandidates(wideSet), TOP_K));// ...const { text } = await traced('generate', () => generateText({ ... }));Naming convention: stage en lowercase con dot-notation cuando hay sub-stages (retrieve.dense, retrieve.sparse). Phoenix los agrupa bien.
Lo que ves en Phoenix
Sección titulada «Lo que ves en Phoenix»Cada trace en la UI tiene:
- Timeline horizontal: spans en barras, por orden cronológico, anchas por duración.
- Hierarchy: spans hijos anidados bajo padres.
- Attributes panel: al clickear un span, ves todos los attrs que pasaste.
- Errors highlighted: spans con status ERROR aparecen en rojo.
Para diagnosticar “por qué este request fue lento”:
- Buscás el trace por timestamp o por query.
- Mirás el timeline: ¿qué stage fue el más largo?
- Para ese stage, leés los attributes: ¿el
rag.kera razonable? ¿rag.querytenía algo raro?
Esto en producción es la diferencia entre 15 minutos de debugging y una tarde de adivinanza.
Alternativa: Langfuse
Sección titulada «Alternativa: Langfuse»Phoenix es zero-config y minimal. Si querés más features (cost tracking nativo, prompt versioning, A/B testing built-in, dashboards persistentes), Langfuse es la otra opción open-source local. Misma idea, más superficie. Para el curso usamos Phoenix por simplicidad — el código de instrumentación no cambia entre uno y otro (ambos hablan OTLP/HTTP).
Lo que viene
Sección titulada «Lo que viene»Tracing te dice qué tarda. El siguiente capítulo te dice qué cuesta: cómo medir tokens, cachear embeddings, y proyectar el bill mensual antes de salir a producción.