Citations: respuestas con fuentes inline
La diferencia entre confiable y defendible
Sección titulada «La diferencia entre confiable y defendible»Tu RAG dice:
“La regla de dependencia establece que las dependencias del código fuente solo pueden apuntar hacia adentro.”
¿Cómo sabe el usuario si eso está en tus documentos o el LLM lo inventó? No tiene cómo. Tiene que confiar.
Y un buen RAG nunca pide eso. Citation explícita transforma la respuesta en algo defendible:
“La regla de dependencia establece que las dependencias del código fuente solo pueden apuntar hacia adentro [1]. Las capas internas no deben conocer las externas [1][2].”
Donde [1] y [2] mapean a chunks específicos del corpus. Ahora el usuario puede:
- Verificar cada afirmación contra la fuente original.
- Detectar afirmaciones sin fuente (alucinaciones).
- Confiar porque sabe de dónde sale cada cosa.
El patrón
Sección titulada «El patrón»Dos cosas:
- Numerar los chunks en el prompt:
[1],[2], … con la metadata. - Instruir al LLM a citar cada afirmación con
[N]después.
El LLM lo aprende sin fine-tuning — viene en el comportamiento base de la mayoría de modelos modernos. Solo hay que pedirlo bien.
Después: parsear la respuesta para extraer las marcas, validar que mapean a chunks reales, y mostrar las fuentes.
El archivo
Sección titulada «El archivo»packages/01-vercel-ai-sdk/src/format/citations.ts — dos exports:
export function buildCitedPrompt(question: string, contexts: RAGContext[]): string;export function parseCitations(text: string, contexts: RAGContext[]): CitedAnswer;buildCitedPrompt arma el prompt con los chunks numerados. parseCitations extrae las marcas [N] y las mapea a fuentes.
El prompt
Sección titulada «El prompt»return `Sos un asistente. Respondé la PREGUNTA usando ÚNICAMENTE el CONTEXTO numerado de abajo.
Después de cada afirmación factual de tu respuesta, citá la fuente con el formato [N], donde N es el número del pasaje del contexto que respalda esa afirmación. Si la información no está en el contexto, decí "no tengo suficiente información para responder" — no inventes.
CONTEXTO:${numbered}
PREGUNTA:${question}
RESPUESTA (con citas inline):`;Cuatro detalles:
ÚNICAMENTEmantiene el ancla anti-alucinación que ya viene del Nivel 1.Después de cada afirmación factual, citá la fuente con el formato [N]: instrucción explícita y formato cerrado.Si la información no está en el contexto, decí "no tengo suficiente información": salida honorable. Sin esto, el LLM puede sentirse forzado a citar aunque no tenga base, y mete[1]random.(con citas inline)al final: empuja el formato esperado.
El parser
Sección titulada «El parser»Buscamos todas las marcas [N] en el texto y las clasificamos:
const re = /\[(\d+)\]/g;const seen = new Map<number, ParsedCitation>();const unmatched = new Set<number>();
let match: RegExpExecArray | null = re.exec(text);while (match !== null) { const n = parseInt(match[1]!, 10); if (n >= 1 && n <= contexts.length) { if (!seen.has(n)) { seen.set(n, { index: n, filename: contexts[n - 1]!.filename }); } } else { unmatched.add(n); } match = re.exec(text);}
return { text, citations: Array.from(seen.values()).sort((a, b) => a.index - b.index), unmatched: Array.from(unmatched).sort((a, b) => a - b),};El regex captura cada [N]. Las clasificamos:
- Match in-range (
1 ≤ N ≤ contexts.length): la cita es válida, la mapeamos a fuente. - Match out-of-range: el LLM citó un chunk que no existe (ej.
[7]con solo 4 chunks). Esto es una señal de alucinación — el LLM inventó la cita.
unmatched es la métrica clave para detectar problemas. Si tu pipeline arroja unmatched > 0 con frecuencia, algo está mal con el modelo o con el prompt.
El comando
Sección titulada «El comando»pnpm vercel:query:adv "<pregunta>" --citeOutput enriquecido:
=== Answer ===La regla de dependencia establece que las dependencias del código fuentesólo pueden apuntar hacia adentro [1]. Las capas internas no pueden conocerlas externas [1][2], lo que permite que las reglas de negocio sean testablessin necesidad de UI ni base de datos [2].
=== Citations === [1] clean-architecture.md [2] clean-architecture.md
=== Sources (top-K) === [1] clean-architecture.md (score: 0.7234) [2] clean-architecture.md (score: 0.6118) [3] hexagonal-architecture.md (score: 0.5891) [4] solid-principles.md (score: 0.5102)Mirá la diferencia entre Citations y Sources:
- Sources son los top-K que llegaron al prompt (4 chunks).
- Citations son las que el LLM efectivamente usó en la respuesta. En este caso, solo
[1]y[2]— los chunks 3 y 4 no fueron citados, lo cual es información: el retriever los trajo pero el LLM no los necesitó.
Esa diferencia es feedback útil para tunear TOP_K — si todos los chunks sobre todo top-3 y top-4 nunca aparecen en citations, podés bajar TOP_K a 2 sin perder nada.
Combinando con todo el nivel
Sección titulada «Combinando con todo el nivel»Citation se compone con todas las flags anteriores:
pnpm vercel:query:adv "<pregunta>" \ --rewrite --multi-query 3 --hybrid --rerank --citePipeline completo del Nivel 3:
- Rewrite la pregunta.
- Multi-query descompone en 3 sub-preguntas.
- Para cada sub-pregunta, hybrid retrieve (dense + BM25 + RRF) con
RETRIEVE_K=20. - Merge + dedupe candidatos.
- LLM rerank a top-4.
- Cited prompt con los 4 chunks numerados.
- Generate + parse citations.
- Print answer + citations + sources.
5+ LLM calls + 3 retrieves. Pesado pero defendible.
Probá la siguiente para ver citations en acción:
pnpm vercel:query:adv \ "Comparame Clean Architecture con Hexagonal Architecture en términos de testabilidad" \ --multi-query 2 --rerank --citeMirá:
- Las sub-queries que generó multi-query.
- La respuesta con
[N]inline. - La sección
=== Citations ===para ver qué chunks usó realmente. - Si
WARNING: unmatched citation marksaparece, contame.
La conexión con Nivel 2
Sección titulada «La conexión con Nivel 2»Citation explícita y faithfulness alta van juntas. Si tu RAG cita y unmatched = 0, es porque cada afirmación está soportada — y esa es la definición operativa de faithfulness.
Si tu eval del Nivel 2 te da faithfulness alto pero el usuario no ve las citations, estás dejando valor en la mesa. La citation es la materialización visible de la métrica que tu harness ya está midiendo.
Cerraste Nivel 3
Sección titulada «Cerraste Nivel 3»Si llegaste hasta acá:
- Tenés hybrid retrieval (dense + BM25 con RRF).
- Tenés reranking (LLM-as-reranker con path para cross-encoder real).
- Sabés multi-query — descomposición de preguntas multi-tema.
- Sabés query rewriting — limpieza de queries crudas.
- Tenés citations explícitas con validación de unmatched.
- Sabés componer las 5 técnicas en un solo pipeline.
Tu RAG pasó de “demo” a “producto que aguanta usuarios reales”.
Lo que viene
Sección titulada «Lo que viene»Tu RAG es bueno. Pero ¿qué pasa cuando lo ponés en producción? Sin observabilidad, no sabés qué falla. Sin gestión de costos, te explota la cuenta. Sin un plan de refresh del índice, tu corpus se desactualiza. Sin deploy edge-ready, tu latencia para usuarios fuera de tu región es horrible.
Eso es Nivel 4 — Operación. Lo que rara vez se enseña pero siempre se necesita.