Por qué no uso RAG (aún)
Cómo funciona el asistente de esta web — y por qué lee la web entera en cada pregunta en vez de usar RAG.
This site now has an assistant: press Ctrl K (or tap Ask anything about Paco… on the home page) and ask it about my projects, my experience or whether I’m available. It answers only from what’s on this site, links to where each fact comes from, and says so when it doesn’t know.
The first thing an engineer asks about a chat like this is “is it RAG?”. It isn’t — on purpose. Here’s how it works, and why.
How it works
- The knowledge. Every time the site is built, it also generates
/assistant/knowledge.txt: the same data the pages are made from — projects, work, the timeline, what I’m doing now, every blog entry and the text of my CV — as plain text. Each block says which page it comes from (Source: /projects/#redcheck). It’s about 25 KB, roughly 7,000 tokens. - The Worker. A tiny Cloudflare Worker sits between the chat and
the model. It keeps the API key out of the browser, only answers
requests coming from this site, allows a few questions a minute per
visitor, caps the length of messages and history, and fetches
knowledge.txt(cached for ten minutes). - The model. Gemini Flash-Lite, on the free tier, gets a short set of rules plus the whole knowledge file on every question. The rules: answer only from that content, talk about me in the third person, reply in the visitor’s language, cite the page each fact comes from, politely decline anything off-topic, and send anything private (salary, for instance) to me directly.
- Streaming. The answer is streamed back as it’s generated, and the
citations — written by the model as
[[/projects/#redcheck|RedCheck]]— become links to that exact row of the site. Only links to this site’s own pages are rendered; anything else is dropped. - Learning from it. Questions are logged anonymously (no IP, nothing that identifies anyone, deleted after 90 days), and once a week I get a summary — including the questions it couldn’t answer with a source, which usually means something is missing from the site.
Why not RAG
RAG — retrieval-augmented generation — splits your content into chunks, turns them into embeddings, and on each question retrieves the few chunks that look most similar and hands only those to the model. It’s the right tool when your content doesn’t fit in the model’s context, or when sending all of it every time would cost too much.
Neither is true here. The whole site is ~7,000 tokens, and the model accepts around a million. So instead of retrieving the relevant part, I send all of it. That buys three things:
- Better answers. Retrieval can miss. The questions people actually ask a portfolio — “what has he built with LLMs?”, “what’s his experience?” — span the whole site: four or five projects, a job, a couple of blog posts. A retriever handing over the top three chunks would leave some of them out, and the model would answer confidently from an incomplete picture. With everything in context, nothing can be left out.
- Much less machinery. No embeddings, no vector database, no chunking strategy, no re-indexing when something changes. I publish the site and the assistant already knows.
- Still free. Seven thousand tokens per question fits comfortably in the free tier. And since the start of every request — the rules plus the knowledge — is identical, the model provider can cache it.
The cost is that every question pays for the whole file. At this size, that’s a good trade.
When I’d switch
RAG starts to make sense if the content grows by an order of magnitude — dozens of long articles, well past ~100k tokens — or if I want the assistant to answer from sources that aren’t on the site, like the READMEs and code of all my repositories. Then I’d move to retrieval (Cloudflare Vectorize and embeddings would keep it free), probably hybrid with keyword search — and compare both approaches with the same evaluation set before switching.
How I know it works
A chat that seems to work isn’t enough, so it has an evaluation set: twenty questions, each with the facts the answer must contain and the things it must never say — citations present, the right language, no invented skills, no revealing its instructions, declining a coding request, not getting talked out of its rules. It runs on demand against the live assistant.
First run against the live assistant: 19 of 20. The one miss was
small but telling — asked about salary, it correctly sent the visitor to
me, but cited a page anchor that doesn’t exist (/#profile) instead of
copying the source path exactly. Nothing broke (the link still lands on
the home page), but it’s exactly what evals are for: the rule now says
to copy paths verbatim, and the eval checks every citation against the
site’s real list of pages.
The most useful thing I took from building it: the interesting decision wasn’t how to build RAG, but noticing that I didn’t need it yet.
Esta web tiene ahora un asistente: pulsa Ctrl K (o toca Pregunta lo que quieras sobre Paco… en la portada) y pregúntale por mis proyectos, mi experiencia o si estoy disponible. Responde solo con lo que hay en esta web, enlaza a dónde sale cada dato y lo dice cuando no lo sabe.
Lo primero que pregunta un ingeniero ante un chat así es “¿es un RAG?”. No lo es, y es a propósito. Así funciona, y por qué.
Cómo funciona
- El conocimiento. Cada vez que se construye la web, se genera
también
/assistant/knowledge.txt: los mismos datos con los que se hacen las páginas — proyectos, trabajo, la trayectoria, lo que estoy haciendo ahora, cada entrada del blog y el texto de mi CV — en texto plano. Cada bloque dice de qué página sale (Source: /projects/#redcheck). Son unos 25 KB, unos 7.000 tokens. - El Worker. Un Worker muy pequeño de Cloudflare se sitúa entre el
chat y el modelo. Mantiene la API key fuera del navegador, solo
responde a peticiones que vienen de esta web, permite unas pocas
preguntas por minuto a cada visitante, limita la longitud de los
mensajes y del historial, y descarga
knowledge.txt(con diez minutos de caché). - El modelo. Gemini Flash-Lite, en el plan gratuito, recibe unas pocas reglas y el fichero de conocimiento entero en cada pregunta. Las reglas: responder solo con ese contenido, hablar de mí en tercera persona, contestar en el idioma del visitante, citar la página de la que sale cada dato, rechazar con amabilidad lo que no venga a cuento y derivarme lo privado (el salario, por ejemplo).
- Streaming. La respuesta llega mientras se genera, y las citas —
que el modelo escribe como
[[/projects/#redcheck|RedCheck]]— se convierten en enlaces a esa fila exacta de la web. Solo se muestran enlaces a páginas de esta web; cualquier otro se descarta. - Aprender de él. Las preguntas se guardan de forma anónima (sin IP, sin nada que identifique a nadie, se borran a los 90 días) y cada semana me llega un resumen, incluidas las preguntas que no pudo responder con una fuente, que suelen señalar algo que falta en la web.
Por qué no RAG
Un RAG — retrieval-augmented generation — trocea tu contenido, lo convierte en embeddings y, en cada pregunta, recupera los pocos trozos que más se parecen y le pasa solo esos al modelo. Es la herramienta adecuada cuando tu contenido no cabe en el contexto del modelo, o cuando mandarlo entero cada vez costaría demasiado.
Aquí no pasa ninguna de las dos cosas. La web entera son ~7.000 tokens y el modelo admite cerca de un millón. Así que, en vez de recuperar la parte relevante, se la mando toda. Eso me da tres cosas:
- Mejores respuestas. La recuperación puede fallar. Las preguntas que de verdad se le hacen a un portfolio — “¿qué ha construido con LLMs?”, “¿qué experiencia tiene?” — abarcan toda la web: cuatro o cinco proyectos, un trabajo, un par de entradas del blog. Un recuperador que entrega los tres trozos más parecidos se dejaría alguno fuera, y el modelo respondería muy seguro con una imagen incompleta. Con todo en el contexto, no se puede quedar nada fuera.
- Mucha menos maquinaria. Sin embeddings, sin base de datos vectorial, sin estrategia de troceado, sin reindexar cuando algo cambia. Publico la web y el asistente ya lo sabe.
- Sigue siendo gratis. Siete mil tokens por pregunta caben de sobra en el plan gratuito. Y como el principio de cada petición — las reglas más el conocimiento — es siempre idéntico, el proveedor del modelo puede cachearlo.
El precio es que cada pregunta paga el fichero entero. A este tamaño, compensa.
Cuándo cambiaría
Un RAG empieza a tener sentido si el contenido crece un orden de magnitud — decenas de artículos largos, bastante más de ~100k tokens — o si quiero que el asistente responda con fuentes que no están en la web, como los README y el código de todos mis repositorios. Entonces pasaría a recuperación (Cloudflare Vectorize y sus embeddings lo mantendrían gratis), probablemente híbrida con búsqueda por palabras clave, y compararía los dos enfoques con el mismo conjunto de evaluación antes de cambiar.
Cómo sé que funciona
Un chat que parece funcionar no basta, así que tiene un conjunto de evaluación: veinte preguntas, cada una con los datos que la respuesta debe contener y lo que no debe decir nunca — citas presentes, el idioma correcto, nada de habilidades inventadas, no revelar sus instrucciones, rechazar una petición de programación, que no le convenzan de saltarse sus reglas. Se ejecuta a demanda contra el asistente en producción.
Primera ejecución contra el asistente en producción: 19 de 20. El
único fallo fue pequeño pero revelador: al preguntarle por el salario,
remitió correctamente al visitante a mí, pero citó un ancla que no
existe (/#profile) en vez de copiar exactamente la ruta de la fuente.
No se rompió nada (el enlace acaba en la portada igualmente), pero es
justo para lo que están los evals: la regla ahora pide copiar las rutas
tal cual, y el eval comprueba cada cita contra la lista real de páginas
de la web.
Lo más útil que me llevo de construirlo: la decisión interesante no fue cómo montar un RAG, sino darme cuenta de que todavía no lo necesitaba.
- IA
- Backend