Ir al contenido

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.

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 query con hijos rewrite, 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.

packages/01-vercel-ai-sdk/src/ops/tracing.ts — wrapper minimal sobre el SDK:

tracing.ts: API
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:

  1. Si OTel está deshabilitado (env var OTEL_ENABLED no es 1), simplemente llama a fn y devuelve. Cero overhead cuando no estás traceando.
  2. 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.
Ventana de terminal
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)

Ventana de terminal
pnpm phoenix:up

Internamente: 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.

Ventana de terminal
OTEL_ENABLED=1 pnpm vercel:query:adv "¿Qué propone Clean Architecture?" --rerank --cite

La primera línea del output va a ser:

[otel] tracing enabled → http://localhost:6006/v1/traces

Cuando 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.

query.adv.ts ya tiene los wraps puestos en este nivel. Cinco lugares:

query.adv.ts: tracing
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.

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”:

  1. Buscás el trace por timestamp o por query.
  2. Mirás el timeline: ¿qué stage fue el más largo?
  3. Para ese stage, leés los attributes: ¿el rag.k era razonable? ¿rag.query tenía algo raro?

Esto en producción es la diferencia entre 15 minutos de debugging y una tarde de adivinanza.

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).

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.