Aulafy
Courses/Claude Code + Local AI/Lexia-style web app with RAG

Lexia-style web app with RAG

In this lesson you will build the Lexia technical pattern: a web application that goes beyond chat—it queries its own library, retrieves relevant sources, and generates cited answers with a local OpenAI-compatible model.

  • Separate interface, API, corpus, embeddings, and generative model.
  • Create a reusable local RAG architecture for different domains.
  • Design prompts that require answers with sources and clear boundaries.
  • Prepare the app for a local demo and for deploying the web part on Vercel.

The application map

Before coding, view the system as a chain of small pieces:

TerminalCode
usuario -> interfaz web -> API -> recuperador RAG -> fuentes -> prompt con reglas -> LLM -> respuesta citada

What we are going to build

The final project will have these pieces:

  • A web interface with a query box, answer, sources, and history.
  • A Node.js backend with endpoints to query, search, and check index status.
  • A corpus folder with chunked documents.
  • Local embeddings generated with LM Studio, Ollama, or an OpenAI-compatible server.
  • Semantic retrieval and keyword search.
  • A system prompt that requires citations and abstention when sources are missing.

Step 1: define your domain

Don't start with code. Start with the product. Fill in this brief:

TerminalCode
Nombre de la app:
Usuario:
Problema:
Fuentes:
Tono:
Formato de respuesta:
Límites:

Example for an educational version:

TerminalCode
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

Step 2: create the project foundation

To learn the pattern from Lexia, clone the repository and run it locally:

TerminalCode
git clone https://github.com/raym33/lexia.git
cd lexia
cp .env.example .env
npm start

Key configuration variables:

TerminalCode
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

Step 3: connect a local model runtime

In LM Studio, load a chat model and an embeddings model. Enable the OpenAI-compatible local server at http://127.0.0.1:1234/v1.

TerminalCode
curl http://127.0.0.1:1234/v1/models

Test embeddings:

TerminalCode
curl -s http://127.0.0.1:1234/v1/embeddings \
  -H 'content-type: application/json' \
  -d '{"model":"bge-m3","input":["prueba de embeddings"]}'

Step 4: prepare your document library

For a first prototype, use a small JSON corpus. The important thing is that each chunk has text and a citation.

TerminalCode
corpus/
  documentos.json
TerminalCode
[
  {
    "id": "doc-001#p1",
    "titulo": "Manual del curso",
    "cita": "Manual del curso, apartado 1",
    "texto": "Contenido del fragmento...",
    "materia": "curso"
  }
]

Step 5: generate the embeddings index

TerminalCode
npm run embed

Expected files:

TerminalCode
data/embeddings.bin
data/embeddings.ids.txt
data/embeddings.meta.json

Step 6: build the query API

The core route of a Lexia-style app is a RAG query:

TerminalCode
POST /api/consulta
TerminalCode
async function consulta(pregunta) {
  const fuentes = await retrieve(pregunta, 6);
  const prompt = buildPrompt(pregunta, fuentes);
  const respuesta = await chat(prompt);
  return { respuesta, fuentes };
}

Step 7: add hybrid retrieval

Lexia combines semantic search and keyword search. This is an important decision because technical domains have precise vocabulary.

  • Embeddings: capture meaning.
  • BM25 or lexical search: captures exact words.
  • Authority weights: prioritize canonical sources.
  • Optional reranker: reorders candidates when you need more precision.

Step 8: design the interface

The minimum interface should show:

  • Query box.
  • Mode selector: answer, draft, or search.
  • Main answer.
  • Retrieved sources with snippets.
  • Local history.
  • Index status and helpful error messages.
TerminalCode
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.

Step 9: responsible guardrails

Step 10: evaluate before publishing

Create five known questions and note which source should appear. Don't evaluate only whether the answer sounds good.

TerminalCode
[
  {
    "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"
  }
]

Simple metrics:

  • Hit@3: the expected source appears among the top three.
  • Hit@6: it appears among the top six.
  • Cited answer: the answer includes references.
  • Correct abstention: if there is no source, the app acknowledges it.

Step 11: prepare for Vercel

Vercel is perfect for publishing the web part and educational content. For a RAG app with local models, remember this separation:

  • Static or Next.js web can be deployed on Vercel.
  • The local model and embeddings live on your machine or private server.
  • If you need a public demo, use sample data and a controlled remote backend.
TerminalCode
npm run build
vercel deploy

Final challenge

Create a mini Lexia for your own domain:

  • Choose a domain.
  • Prepare 15 corpus chunks without sensitive data.
  • Configure local model and embeddings.
  • Create five evaluation questions.
  • Customize the prompt.