Crea una web app tipo Lexia con RAG local
En esta lección vas a construir el patrón técnico de Lexia: una aplicación web que no se limita a chatear, sino que consulta una biblioteca propia, recupera fuentes relevantes y genera respuestas citadas con un modelo local compatible con OpenAI.
- Separar interfaz, API, corpus, embeddings y modelo generativo.
- Crear una arquitectura RAG local reutilizable para distintos dominios.
- Diseñar prompts que obliguen a responder con fuentes y límites claros.
- Preparar la app para una demo local y para publicar la parte web en Vercel.
El mapa de la aplicación
Antes de programar, mira el sistema como una cadena de piezas pequeñas:
usuario -> interfaz web -> API -> recuperador RAG -> fuentes -> prompt con reglas -> LLM -> respuesta citada
Qué vamos a construir
El proyecto final tendrá estas piezas:
- Una interfaz web con caja de consulta, respuesta, fuentes e historial.
- Un backend Node.js con endpoints para consultar, buscar y ver el estado del índice.
- Una carpeta de corpus con documentos fragmentados.
- Embeddings locales generados con LM Studio, Ollama o un servidor OpenAI-compatible.
- Recuperación semántica y búsqueda por términos.
- Un prompt de sistema que exige citas y abstención cuando faltan fuentes.
public/ para la interfaz, server.mjs para la API, ingest.mjs para preparar corpus y embed.mjs para generar embeddings.Paso 1: define tu dominio
No empieces por el código. Empieza por el producto. Rellena esta ficha:
Nombre de la app: Usuario: Problema: Fuentes: Tono: Formato de respuesta: Límites:
Ejemplo para una versión educativa:
Nombre: EduLexia Usuario: profesores y alumnos de FP Problema: resolver dudas sobre materiales del curso Fuentes: apuntes, rubricas, programaciones y ejercicios Tono: claro, práctico y pedagógico Formato: respuesta breve, pasos, fuentes y tarea sugerida Límites: no inventar temario ni calificaciones
Paso 2: crea la base del proyecto
Para aprender el patrón desde Lexia, clona el repositorio y arráncalo en local:
git clone https://github.com/raym33/lexia.git cd lexia cp .env.example .env npm start
Variables clave de configuración:
PORT=5174 LM_BASE=http://127.0.0.1:1234/v1 CHAT_MODEL=gemma-3-12b-it EMBED_MODEL=bge-m3 USE_RERANK=0 TOP_K=6
http://localhost:5174. Si la app carga pero el índice aparece vacío, no pasa nada: todavía falta corpus y embeddings.Paso 3: conecta un runtime local de modelos
En LM Studio, carga un modelo de chat y un modelo de embeddings. Activa el servidor local compatible con OpenAI en http://127.0.0.1:1234/v1.
curl http://127.0.0.1:1234/v1/models
Prueba embeddings:
curl -s http://127.0.0.1:1234/v1/embeddings \
-H 'content-type: application/json' \
-d '{"model":"bge-m3","input":["prueba de embeddings"]}'Paso 4: prepara tu biblioteca documental
Para un primer prototipo, usa un corpus pequeño en JSON. Lo importante es que cada fragmento tenga texto y cita.
corpus/ documentos.json
[
{
"id": "doc-001#p1",
"titulo": "Manual del curso",
"cita": "Manual del curso, apartado 1",
"texto": "Contenido del fragmento...",
"materia": "curso"
}
]Paso 5: genera el índice de embeddings
npm run embed
Ficheros esperados:
data/embeddings.bin data/embeddings.ids.txt data/embeddings.meta.json
Paso 6: construye la API de consulta
La ruta central de una app tipo Lexia es una consulta RAG:
POST /api/consulta
async function consulta(pregunta) {
const fuentes = await retrieve(pregunta, 6);
const prompt = buildPrompt(pregunta, fuentes);
const respuesta = await chat(prompt);
return { respuesta, fuentes };
}Paso 7: añade recuperación híbrida
Lexia combina búsqueda semántica y búsqueda por términos. Es una decisión importante porque los dominios técnicos tienen vocabulario exacto.
- Embeddings: captan significado.
- BM25 o búsqueda léxica: captura palabras exactas.
- Pesos de autoridad: priorizan fuentes canónicas.
- Reranker opcional: reordena candidatos cuando necesitas más precisión.
Paso 8: diseña la interfaz
La interfaz mínima debe mostrar:
- Caja de consulta.
- Selector de modo: responder, redactar o buscar.
- Respuesta principal.
- Fuentes recuperadas con snippet.
- Historial local.
- Estado del índice y mensajes de error útiles.
Recuperando fuentes... Generando respuesta citada... No hay indice disponible. Ejecuta npm run ingest y npm run embed. El modelo local no responde. Revisa LM Studio.
Paso 9: guardrails responsables
data/users.json, data/embeddings.*, .env ni documentos privados.Paso 10: evalúa antes de publicar
Crea cinco preguntas conocidas y anota qué fuente debería aparecer. No evalúes solo si la respuesta suena bien.
[
{
"pregunta": "Que plazo tengo para entregar la actividad?",
"fuente_esperada": "rubrica-entrega#p2"
},
{
"pregunta": "Como se califica el proyecto final?",
"fuente_esperada": "rubrica-final#p1"
}
]Métricas sencillas:
- Hit@3: la fuente esperada aparece entre las tres primeras.
- Hit@6: aparece entre las seis primeras.
- Respuesta citada: la respuesta incluye referencias.
- Abstención correcta: si no hay fuente, la app lo reconoce.
Paso 11: preparar para Vercel
Vercel es perfecto para publicar la parte web y el contenido educativo. Para una app RAG con modelos locales, recuerda esta separación:
- La web estática o Next.js puede desplegarse en Vercel.
- El modelo local y los embeddings viven en tu máquina o servidor privado.
- Si necesitas una demo pública, usa datos de ejemplo y un backend remoto controlado.
npm run build vercel deploy
Reto final
Crea una mini Lexia para un dominio propio:
- Elige un dominio.
- Prepara 15 fragmentos de corpus sin datos sensibles.
- Configura modelo local y embeddings.
- Crea cinco preguntas de evaluación.
- Personaliza el prompt.
- Graba una demo de dos minutos mostrando respuesta y fuentes.