Retrieval / Trust  ·  4 min read  ·  2025.04

Why your RAG agent should cite, not summarize

A RAG agent that only summarizes is asking you to trust it. A RAG agent that cites is giving you a way to check it. One of those is easy to fake. The other isn’t, and the difference is where the engineering happens.

The failure mode nobody shows you in the demo

Normal RAG setup: embed the question, pull the top-k chunks, stuff them into the prompt, ask the model to answer “using the provided context and citing your sources.” Works beautifully in the demo. Then it ships, and one day it says something like:

The battery degradation threshold is 80% of nominal capacity [Source: internal_spec_v3.pdf, §4.2].

Clean answer. Confident citation. Except internal_spec_v3.pdf was never retrieved for that query, and §4.2 doesn’t exist. The model wrote the citation the same way it wrote the sentence: as plausible text. A reference is just a string, and a language model is very good at producing strings that look like references. This is the quiet version of hallucination, and it’s worse than a wrong answer, because it comes wrapped in the exact signal you’d use to catch it.

I spend my days on battery fault prediction, so that example isn’t random: this is precisely where a made-up “§4.2” gets a real decision wrong.

You can’t prompt your way out of this

“Only cite real sources” is a request the model can ignore the same way it ignores “don’t hallucinate.” The fix isn’t a better instruction, it’s making invented citations structurally impossible. Three moves:

  1. Retrieval returns chunks with stable IDs. Every chunk pulled from pgvector comes back tied to its source document and position. Nothing enters the context window without that ID attached.

  2. The model cites by ID, not by name. It references chunks as [3], never as a filename. It can’t invent the source string because it never writes one, it only picks from IDs it was handed.

  3. The server resolves IDs back to sources. Before a citation reaches you, a server-side step maps each ID to the real document and section. Reference an ID that wasn’t in the retrieved set and it’s dropped there, never rendered.

The result: the model can only point at documents that genuinely entered its context. No path exists for it to cite something it never saw, because citations resolve against the retrieval set, not against the model’s imagination.

How it runs here

On this site, that resolve step happens inside the same FastAPI endpoint that streams the answer. As tokens come back from the model over SSE, cited IDs get matched against the exact chunks retrieved for that request before the client renders them. Click any citation and you land on the real source. If the agent can’t ground a claim in something it retrieved, it doesn’t fabricate a source to cover the gap, it just has no citation to show, which tells you something too.

Summarizing asks for trust. Citing this way earns it, because verification is built into the mechanism instead of promised by the prompt.

Recuperación / Confianza  ·  4 min de lectura  ·  2025.04

Por qué tu agente RAG debería citar, no resumir

Un agente RAG que solo resume te está pidiendo confianza. Un agente RAG que cita te está dando una forma de comprobarlo. Una de las dos cosas es fácil de fingir. La otra no, y en esa diferencia es donde está la ingeniería.

El modo de fallo que nadie enseña en la demo

Montaje RAG normal: embedding de la pregunta, recuperar los top-k chunks, meterlos en el prompt y pedirle al modelo que responda “usando el contexto proporcionado y citando tus fuentes”. Funciona de maravilla en la demo. Luego llega a producción, y un día suelta algo como:

El umbral de degradación de la batería es el 80% de la capacidad nominal [Fuente: internal_spec_v3.pdf, §4.2].

Respuesta limpia. Cita segura. Salvo que internal_spec_v3.pdf nunca se recuperó para esa consulta, y el §4.2 no existe. El modelo escribió la cita igual que escribió la frase: como texto plausible. Una referencia es solo un string, y un modelo de lenguaje es muy bueno produciendo strings que parecen referencias. Esta es la versión silenciosa de la alucinación, y es peor que una respuesta errónea, porque viene envuelta justo en la señal que usarías para cazarla.

Me paso los días con predicción de fallos en baterías, así que el ejemplo no es casual: es exactamente ahí donde un “§4.2” inventado tuerce una decisión real.

De esto no te salva un prompt

“Cita solo fuentes reales” es una petición que el modelo puede ignorar igual que ignora “no alucines”. El arreglo no es una instrucción mejor, es hacer que las citas inventadas sean estructuralmente imposibles. Tres movimientos:

  1. El retrieval devuelve chunks con IDs estables. Cada chunk que sale de pgvector vuelve atado a su documento de origen y a su posición. Nada entra en la ventana de contexto sin ese ID.

  2. El modelo cita por ID, no por nombre. Referencia los chunks como [3], nunca como un nombre de archivo. No puede inventarse el string de la fuente porque nunca lo escribe: solo elige entre los IDs que le han pasado.

  3. El servidor resuelve los IDs de vuelta a las fuentes. Antes de que una cita te llegue, un paso en el servidor mapea cada ID al documento y la sección reales. Si referencia un ID que no estaba en el conjunto recuperado, se descarta ahí y nunca se muestra.

El resultado: el modelo solo puede señalar documentos que de verdad entraron en su contexto. No existe camino para citar algo que nunca vio, porque las citas se resuelven contra el conjunto recuperado, no contra la imaginación del modelo.

Cómo funciona en esta web

Aquí, ese paso de resolución ocurre dentro del mismo endpoint de FastAPI que emite la respuesta en streaming. Mientras los tokens vuelven del modelo por SSE, los IDs citados se comprueban contra los chunks exactos recuperados para esa petición antes de que el cliente los pinte. Haz clic en cualquier cita y aterrizas en la fuente real. Si el agente no puede anclar una afirmación en algo que recuperó, no se fabrica una fuente para tapar el hueco: simplemente no hay cita que mostrar, y eso también te dice algo.

Resumir pide confianza. Citar así se la gana, porque la verificación va incorporada en el mecanismo en vez de prometida en el prompt.