Cómo crear un RAG desde cero: chatea con tu propia base de datos usando InsForge
Cuando se habla de que un chat pueda consultar datos propios —de un negocio, de una empresa o de cualquier sistema— por lo general se piensa en RAG (Retrieval Augmented Generation). Suena complicado, pero la idea es simple: hacer que un modelo inteligente sea capaz de leer los datos que están dentro de tu base de datos y responder a partir de ellos, en lugar de inventarse las respuestas.
En este artículo te doy una introducción teórica y práctica: primero entendemos qué es un RAG y por qué existe, y luego construimos uno completo usando InsForge como backend y Cursor como agente de código, con autenticación y despliegue incluidos.
El problema: los modelos solo saben lo que aprendieron en su entrenamiento
Los chats que usamos hoy (Claude, ChatGPT y compañía) funcionan consultando un modelo que ya está entrenado. Eso implica dos limitaciones importantes:
Fecha de corte. El modelo tiene una fecha hasta la cual recibió datos. Si le preguntas algo actual a un modelo instalado en tu máquina, simplemente no lo sabe. Las interfaces comerciales lo resuelven con web search: cuando preguntas algo reciente, buscan en internet y le pasan esa información al modelo.
No conoce tus datos. Este es el caso más interesante. Si tienes una aplicación, un negocio o datos de tu organización (productos, usuarios, facturas), el modelo no tiene forma de saberlos. Y una búsqueda en internet tampoco ayuda: esos datos son tuyos.
Aquí es donde entra el diseño de un RAG. El flujo típico es:
- Tienes un chat (una interfaz web que puedes pedirle a cualquier agente).
- Ese chat consulta a un modelo inteligente (OpenAI, Claude, modelos abiertos, el que sea).
- Se agrega el tercer componente: hacer que el modelo consulte tu base de datos y use esa información extra para mejorar su respuesta.
Ese "añadido" de datos es justamente el augmented del nombre: la parte de generation ya la tenemos con el modelo, y el retrieval es la recuperación de tus datos. RAG, al final, es solo un chat que consulta tus datos antes de responder.
Si has probado NotebookLM de Google, ya viste esta idea en acción a nivel de usuario: cargas tus archivos y chateas con esa información. Lo que veremos aquí es el mismo concepto, pero desde el lado del desarrollo de sistemas: cómo implementarlo tú mismo para un SaaS o para el sistema de una empresa.
Las dos técnicas: text-to-SQL y búsqueda semántica
En una base de datos real tienes tablas de productos, usuarios, categorías, facturas. La pregunta es: ¿cómo hace el modelo para conocer esa información? Hay varias técnicas, pero las dos fundamentales son estas.
1. Text-to-SQL: cuando la pregunta se puede traducir a una consulta
Supongamos que en el chat alguien pregunta: "¿Cuáles son los productos con precio menor a $100?"
Para responder eso no necesitas que el modelo tenga cargados todos tus datos. Es una consulta SQL típica. El modelo recibe tu pregunta en texto, la traduce a SQL, ejecuta la consulta, obtiene los resultados y te responde. Es rápido —incluso más rápido de lo que esperas— porque en el fondo es lo mismo que hacen todos los sistemas: consultas SQL para responder cosas.
2. Búsqueda semántica: cuando la pregunta es por significado
Ahora supongamos que el usuario pregunta: "Me duele la espalda, ¿qué productos me recomiendas?"
Aquí no hay ningún dato SQL al que traducir. Podrías intentar buscar si el término existe dentro de la columna de descripción, pero lo que realmente se necesita es buscar por significado. Eso es una búsqueda semántica, y es el propósito central de crear un RAG.
La idea: además de las columnas típicas de tu tabla de productos (nombre, precio, stock, descripción), agregas una columna en formato vector. Si tu descripción dice "silla ergonómica", su versión vectorial es algo como [0.0012, -0.0431, 0.0088, ...]. A esto se le conoce como embedding: el formato que entiende la IA en lugar del texto. Cuando el modelo necesita buscar productos, hace match contra esa columna vectorial, recupera los datos que coinciden en significado, y con eso construye su respuesta.
Si necesitas ir más allá de una sola columna —por ejemplo, buscar por título, comentarios u otros campos, y no solo en productos sino también en categorías o usuarios— puedes crear una tabla aparte (product_embeddings) relacionada con tus registros. El punto clave: el trabajo pesado no recae sobre el modelo inteligente, sino sobre la base de datos, que es quien guarda y sirve la información.
¿Cómo se generan los embeddings?
Para convertir tus textos en vectores usas un modelo de embeddings. Por lo general tienen costo y dependen del proveedor. En OpenAI, por ejemplo, están text-embedding-3-small y text-embedding-3-large: les pasas un texto y te devuelven su versión vectorial.
Formas de hacer la conversión:
- Un script que convierta ciertas columnas de tus tablas.
- Generación automática al insertar: cada vez que agregas un producto nuevo, la base de datos genera su embedding.
Un detalle importante sobre compatibilidad: los vectores generados con un modelo de embeddings no son compatibles con los de otro modelo distinto. Si generaste tus embeddings con text-embedding-3-small y luego cambias de modelo de embeddings, vas a tener problemas. En cambio, el modelo inteligente que consuma esos embeddings (OpenAI, Claude, el que sea) sí puede trabajar con ellos sin importar cómo los generaste, siempre que la búsqueda se haga con el mismo modelo de embeddings.
Hoy existen modelos de embeddings de OpenAI y de otras empresas, algunos optimizados para multilenguaje, otros para código o documentación técnica.
¿Y la base de datos? No es solo cosa de SQL. Esto funciona con PostgreSQL, MySQL, Oracle, SQLite, y también con bases NoSQL como MongoDB o Redis: prácticamente todas soportan vectores hoy, normalmente vía plugins. En nuestro caso usaremos PostgreSQL con el plugin pgvector.
Manos a la obra: InsForge + Cursor
Para el proyecto demo uso InsForge, una plataforma en la nube que ya nos da el backend completo: base de datos PostgreSQL, base de datos vectorial (pgvector), autenticación y modelos de IA. Nosotros solo construimos el frontend, y para eso uso Cursor —aunque puedes usar Claude Code, OpenCode o el agente que prefieras; lo importante son los conceptos.
Si nunca usaste InsForge: es similar a Supabase, pero muy apoyado en herramientas de IA. Tiene MCPs, CLI y API, lo que significa que puedes darle a tu agente acceso a tu cuenta y que él se encargue de desplegar todo. Además provee el Model Gateway, un proveedor único de modelos basado en OpenRouter, con lo cual tienes acceso a prácticamente todos los modelos de IA desde la misma plataforma. Modelos + base vectorial + PostgreSQL: todo lo que necesita un RAG.
Paso 1: crear el proyecto y la UI
Te registras (GitHub o Google), creas un proyecto —yo le llamé insforge-rag— y dejas el resto por defecto.
En Cursor, el primer prompt es solo para inicializar:
Crea una UI al estilo de ChatGPT con un panel lateral y un chat principal. Solo crea la UI.
La razón de pedir "solo la UI" es que primero se creen los archivos base, y recién después le damos la planificación grande de integrar InsForge.
Paso 2: conectar el agente con el backend
InsForge tiene una página donde eliges qué agente de código conectar: Claude Code, Codex, Cursor y varios más (y una opción de "otros agentes" si el tuyo no aparece). Eliges tu agente, copias el comando de instalación y lo pegas en el chat del agente. Eso instala un par de paquetes en el proyecto, configura el ID del proyecto y deja lista la conexión vía CLI, que es la forma más sencilla de que el agente hable con el backend.
Paso 3: construir el sistema RAG
Ahora sí, el prompt principal. Le pedimos:
- Construir un sistema RAG completo usando InsForge como backend, indicando que ya tiene acceso al proyecto vía CLI.
- Habilitar pgvector para el soporte de datos vectoriales.
- Crear una función
match_documentsque devuelva los chunks más similares con su score de similitud. - Crear edge functions en TypeScript (InsForge usa Deno para esta lógica): una para ingestar datos —generando chunks de ~500 tokens y sus embeddings con
text-embedding-3-smalla través del Model Gateway— y otra para hacer preguntas sobre esos datos, recuperando los chunks más relevantes. Si no encuentra coincidencias, debe responder "no tengo información en mis documentos" en lugar de inventar.
Al terminar, tenemos una migración con la tabla documents, donde cada fila guarda su chunk de texto y su columna embedding (ese arreglo de números que el modelo sí entiende).
Paso 4: probar la búsqueda semántica
Cargué un markdown con reportes inventados de empresas: quién vendió más, en qué año, cuántos empleados tiene cada una. Al subirlo, el sistema divide la información en chunks y genera sus embeddings automáticamente.
Luego pregunté en el chat: "¿Cuántos empleados tiene el grupo Salaris Energía?" — y respondió correctamente (7,594), citando el chunk de donde salió la información, con su porcentaje de similitud visible como tooltip. Ese dato no existe en internet porque es inventado: la única forma de responderlo es consultando nuestra base de datos. Y la respuesta llega rápido, porque no es texto que el modelo genera desde cero, sino un embedding ya guardado.
Este es exactamente el enfoque de NotebookLM: cargas archivos y preguntas sobre ellos. Pero el RAG no acaba ahí.
Datos en vivo: cuando lo estático no alcanza
Cargar un PDF o información estática está bien, pero lo realmente útil es consultar datos que cambian: inventario, precios, facturas. Convertir esos datos a embeddings no tiene mucho sentido cuando se alteran tan frecuentemente; una interfaz web los está actualizando todo el tiempo.
Para el ejemplo, inserté un dataset típico de e-commerce (usuarios, productos, facturas, ítems de factura) pidiéndole al agente en otra pestaña: "inserta estos datos en PostgreSQL usando el InsForge CLI". Si tus datos vienen en PDFs o Excels, puedes pedirle al agente que cree el esquema y normalice los datos en tablas.
Luego, el prompt para la función de consulta:
En mi proyecto de InsForge tengo las tablas users, products, invoices, invoice_items. Crea una función serverless llamada
askque tome un texto, consulte los esquemas de las tablas y convierta mi pregunta en una consulta SQL. Valida que no contenga INSERT, UPDATE, DELETE, DROP ni ALTER. Limita a 50 filas. Pasa los resultados al LLM para que responda con el formato: answer, la consulta SQL y las filas.
Dos puntos de seguridad importantes aquí:
- La validación no es opcional. Sin ella, cualquiera podría pedirle al chat un
DROP DATABASEy tumbarte la base. Es básicamente SQL injection dentro de un prompt. Este enfoque debería ser de solo lectura; si necesitas escritura, mejor exponer funciones específicas (lo que en un MCP serían tools). - Temperatura en 0 + prompt restrictivo. La temperatura en 0 hace que el modelo responda de forma determinista y conservadora, pero lo que realmente evita que invente es la instrucción del prompt: responder únicamente con los datos recuperados y, si no hay coincidencias, decir que no tiene información. La combinación de ambas cosas es clave para que las respuestas sean confiables.
Y una advertencia honesta: sí necesitas entender qué hay dentro de tu base de datos. Si esto se crea mal y tú vas en modo puro vibe coding sin entender nada, es muy fácil que se te pase un fallo de diseño que deje tus tablas expuestas.
Probándolo
- "¿Cuáles son los productos que cuestan menos de 100 USD?" → responde con datos reales de la tabla. La persona que pregunta ni se entera de que por debajo hay SQL.
- Inserté un producto nuevo a mano (un NVIDIA DGX Spark a $4,475) y pregunté "¿cuál es el producto más caro?" → lo encontró al instante, porque la consulta es en vivo.
- "¿Cuántas facturas pendientes hay?" → 5. "¿Productos de la categoría muebles?" → lista correcta y rápida, porque es equivalente a una llamada SQL directa.
¿Cuándo usar cada técnica?
Esta es la parte donde entra el criterio del desarrollador: saber hacia dónde apuntar cada consulta.
| Pregunta del usuario | Técnica |
|---|---|
| "¿Cuál es el producto más caro?" | Text-to-SQL |
| "¿Cuánto gasté en marzo?" | Text-to-SQL (tabla de facturas) |
| "¿Tienes algo para el dolor de espalda al trabajar?" | Búsqueda semántica (embeddings) |
| "Busco algo para trabajar cómodo desde casa" | Búsqueda semántica (embeddings) |
La última fila es un buen ejemplo del límite del SQL: lo mejor que podría hacer el modelo es buscar productos cuyo nombre contenga "casa" o "trabajo". Pero una laptop es perfecta para trabajar desde casa y nunca va a tener esas palabras en su nombre. Ahí el embedding gana, porque busca por significado.
Combinando ambas: embeddings en la tabla de productos
Para cerrar el círculo, actualicé el sistema con un último prompt:
Agrega en la tabla de productos una columna
description(texto) y una columnaembedding(vector). Para cada producto, genera vía Model Gateway una descripción en español y conviértela en embeddings con text-embedding-3-small. Actualiza la funciónaskpara que decida si la pregunta requiere SQL exacto o embeddings con la funciónmatch_products.
Y le di dos pruebas: "¿cuál es el producto más caro?" (SQL) y "busco algo para trabajar desde casa" (semántica). Ambas respondieron correctamente. Ahora cada producto tiene su descripción generada automáticamente y su embedding equivalente. Ojo con un detalle de mantenimiento: si tienes una UI donde se agregan productos, los embeddings deben generarse en el momento de crearlos.
La prueba final: "Dame ideas de regalos para gamers." Como el sistema está configurado para responder solo con datos de nuestras tablas, no puede inventarse productos: tiene que buscarlos en la base de datos. Y responde con escritorio gamer, teclados mecánicos... y un cable HDMI, que como regalo es cuestionable, pero como resultado de búsqueda semántica es correcto.
Autenticación y despliegue
Como InsForge también trae autenticación, el cierre es un solo prompt en modo plan:
Implementa autenticación completa y luego despliégalo en InsForge.
El agente carga los skills de InsForge (instalados desde aquel primer comando), aplica las migraciones de usuarios —cada usuario queda con sus datos separados de los demás— y al final entrega la URL de producción en un dominio de InsForge, con su formulario de registro y verificación por correo.
Un ajuste final de UX: las respuestas del modelo llegan en markdown crudo (asteriscos, guiones). Un prompt de "formatea la respuesta con markdown en el chat" y listo: listas, negritas y respuestas legibles, tanto para las respuestas semánticas como para las que vienen de SQL.
Conclusión y siguientes pasos
Crear un RAG es relativamente sencillo una vez que entiendes las dos piezas: SQL para datos exactos y en vivo, embeddings para búsqueda por significado. Si lo implementas sobre una base de datos real, significa que ahora puedes consultar de forma natural los datos que ya tiene una empresa.
Esto es solo la base. A partir de aquí hay más conceptos que vale la pena explorar:
- Caché de respuestas del modelo, para ahorrar tokens y responder más rápido.
- Enrutado de modelos (model routing): derivar cada pregunta al modelo más adecuado según su tipo. Incluso hay modelos específicos para esto.
- Extender el sistema con capacidades como interpretación de imágenes.
Al final del día construimos una aplicación completa —chat, RAG híbrido, autenticación, usuarios y despliegue— gracias a que el backend ya venía resuelto con InsForge y a que el código lo escribió un agente mientras nosotros nos concentramos en el diseño del sistema. Que es, justamente, donde está el verdadero trabajo.
Si tienes dudas o quieres profundizar más en RAGs, déjalo en los comentarios. Encuentra más contenido en fazt.dev.