¿Base de conocimiento con IA en 20 minutos? Tutorial paso a paso de RAG con Workers AI + Vectorize (código completo)

Quería montar un servicio de atención inteligente para la empresa y revisé tutoriales de RAG: unos solo teoría, otros te mandan a alquilar una GPU y montar el entorno. Solo configurar LangChain y la base de datos vectorial puede llevarte dos días, sin garantía de que funcione.
Luego descubrí que Cloudflare lanzó un conjunto completo de herramientas de IA: Workers AI + Vectorize + D1, todo gestionado, con una cuota gratuita generosa. Probé armar una app de Q&A sobre notas y, de cero a operativa, tardé menos de 20 minutos, con unas cien líneas de código.
Este artículo te guía paso a paso por el flujo completo:
- Explicar con claridad qué es RAG (sin jerga innecesaria)
- Construir en la práctica una app de Q&A sobre base de conocimiento (con código completo)
- Optimizar para mejor recuperación y menor costo
- Desplegar y usarla de verdad
Solo necesitas algo de JavaScript, una cuenta de Cloudflare (gratuita) y seguir los pasos.
¿Qué es RAG? Entiende el funcionamiento en 5 minutos
RAG explicado con un examen
Una analogía directa. En un examen a libro cerrado solo cuentas lo que recuerdas; si olvidas algo, improvisas. Con libro abierto, cuando dudas consultas el material y la respuesta mejora mucho.
RAG (Retrieval-Augmented Generation, generación aumentada por recuperación) le da a la IA permiso de «examen con apuntes».
Un LLM tradicional es como examen a libro cerrado: solo responde con lo que vio en el entrenamiento. El problema:
- Los datos de entrenamiento caducan; no conoce lo más reciente
- No ha visto los documentos internos de tu empresa
- No retiene todos los detalles y tiende a inventar (en la jerga, «alucinar»)
RAG hace esto: primero busca material relevante en tu base de conocimiento y se lo pasa a la IA para que responda con ese contenido. Así las respuestas son más fiables y pueden incorporar información actualizada.
Los tres pasos centrales de RAG
El flujo completo son tres pasos:
Paso 1: convertir el conocimiento en vectores y guardarlo
Tienes un montón de documentos. RAG convierte cada fragmento de texto en una secuencia de números (en terminología técnica, «vector» o «embedding») que representa el significado del texto.
Por ejemplo, «los gatos son adorables» y «los gatitos son tiernos» usan palabras distintas pero significan algo parecido; sus vectores quedarán cercanos. Esos vectores se almacenan en una base de datos vectorial como Vectorize.
Paso 2: cuando el usuario pregunta, encontrar los fragmentos más relevantes
Si alguien pregunta «¿cómo entrenar a un gato?», el sistema convierte la pregunta en vector y busca en la base los fragmentos «más cercanos», es decir, los más relevantes semánticamente.
Eso se llama búsqueda por similitud; es muy rápida y en milisegundos puede sacar las 3-5 coincidencias mejores entre decenas de miles de entradas.
Paso 3: pasar lo recuperado al LLM para generar la respuesta
Con el contenido relevante, se arma un prompt para la IA:
A continuación, material de referencia:
[contenido recuperado 1]
[contenido recuperado 2]
...
Pregunta del usuario: ¿cómo entrenar a un gato?
Responde basándote en el material anterior.
La IA, con esas «referencias», puede dar una respuesta precisa y fundamentada.
¿Por qué elegir el stack completo de Cloudflare?
Hay muchas formas de hacer RAG: LangChain, LlamaIndex, etc. Pero hay que configurar entorno, elegir base vectorial y gestionar GPUs; es bastante trabajo.
Las ventajas de la propuesta de Cloudflare:
Workers AI — Incluye más de una docena de modelos open source (Llama 3, Claude, etc.). Los usas vía API, sin alquilar GPU. La capa gratuita tiene una cuota diaria fija de Neurons; para proyectos personales suele bastar.
Vectorize — Base vectorial gestionada; no montas Milvus, Pinecone ni similares. Crear índice, insertar vectores y buscar por similitud se resuelve con pocas líneas de código.
D1 — La base SQLite de Cloudflare para el texto original. La base vectorial guarda vectores; el contenido textual sigue viniendo de aquí.
Todo gestionado — Lo mejor: sin preocuparte por servidores, escalado ni backups; te concentras en el código. Además, la red perimetral de Cloudflare acelera el acceso global.
"En 2025 Cloudflare lanzó AutoRAG: subes documentos a R2 y el troceo, vectorización, recuperación y generación se hacen solos"
En 2025 Cloudflare también lanzó AutoRAG, que simplifica aún más el flujo: subes documentos a R2 y el resto (troceo, vectorización, recuperación, generación) es automático. En este artículo montamos todo a mano para entender la base.
Dicho esto, vamos a construir uno.
Práctica: monta tu primera aplicación RAG
Haremos una app de Q&A sobre notas: el usuario añade notas, hace preguntas y el sistema busca contenido relevante en todas las notas para responder.
Inicialización del proyecto y preparación del entorno
Primero instala Wrangler (CLI de Cloudflare):
npm install -g wrangler
wrangler login # Inicia sesión en tu cuenta de Cloudflare
Crea el proyecto:
npm create cloudflare@latest rag-notes-app
# Elige "Hello World" worker
# Elige TypeScript
cd rag-notes-app
Instala Hono como router (más cómodo que la API nativa de Workers):
npm install hono
Crea la base D1 y el índice Vectorize:
# Crear base D1 para las notas originales
wrangler d1 create notes-db
# Crear índice Vectorize (768 dimensiones, con el modelo bge-base-en-v1.5)
wrangler vectorize create notes-index --dimensions=768 --metric=cosine
Configura wrangler.jsonc (o wrangler.toml):
{
"name": "rag-notes-app",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
"node_compat": true,
// Enlace de IA
"ai": {
"binding": "AI"
},
// Enlace de base D1
"d1_databases": [
{
"binding": "DB",
"database_name": "notes-db",
"database_id": "TU_ID_DE_BASE_DE_DATOS" // Copia del comando de creación anterior
}
],
// Enlace de índice Vectorize
"vectorize": [
{
"binding": "VECTORIZE",
"index_name": "notes-index"
}
],
// Enlace de Workflow (tareas de vectorización asíncronas)
"workflows": [
{
"binding": "RAG_WORKFLOW",
"name": "rag-workflow",
"class_name": "RAGWorkflow"
}
]
}
Inicializa las tablas:
-- schema.sql
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Ejecuta:
wrangler d1 execute notes-db --file=./schema.sql
Implementar la carga de la base de conocimiento
El núcleo del RAG: convertir las notas del usuario en vectores y guardarlos.
Crea src/workflow.ts (Workflow para tareas asíncronas):
import { WorkflowEntrypoint, WorkflowStep } from 'cloudflare:workers';
type Env = {
AI: Ai;
DB: D1Database;
VECTORIZE: VectorizeIndex;
};
type Params = {
noteId: number;
text: string;
};
export class RAGWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const { noteId, text } = event.payload;
// Paso 1: confirmar que el registro en D1 ya existe (lo hace la ruta principal)
// Paso 2: generar el vector
const embeddings = await step.do('generate embeddings', async () => {
const response = await this.env.AI.run(
'@cf/baai/bge-base-en-v1.5', // Modelo de embedding de 768 dimensiones
{ text: [text] }
);
return response.data[0]; // Devuelve el array del vector
});
// Paso 3: insertar en Vectorize
await step.do('insert vector', async () => {
await this.env.VECTORIZE.insert([
{
id: noteId.toString(),
values: embeddings,
metadata: { text } // Guarda una copia del texto para depuración
}
]);
});
}
}
Ruta principal src/index.ts (añadir notas):
import { Hono } from 'hono';
import { RAGWorkflow } from './workflow';
type Bindings = {
AI: Ai;
DB: D1Database;
VECTORIZE: VectorizeIndex;
RAG_WORKFLOW: Workflow;
};
const app = new Hono<{ Bindings: Bindings }>();
// Añadir nota
app.post('/notes', async (c) => {
const { text } = await c.req.json<{ text: string }>();
if (!text?.trim()) {
return c.json({ error: 'Text is required' }, 400);
}
// Insertar en D1
const result = await c.env.DB.prepare(
'INSERT INTO notes (text) VALUES (?) RETURNING id'
).bind(text).first<{ id: number }>();
if (!result) {
return c.json({ error: 'Failed to create note' }, 500);
}
// Disparar Workflow para generar el vector de forma asíncrona
await c.env.RAG_WORKFLOW.create({
params: { noteId: result.id, text }
});
return c.json({
id: result.id,
message: 'Note created, vectorization in progress'
});
});
export default app;
export { RAGWorkflow };
Cuando el usuario envía un POST para añadir una nota:
- El texto se guarda de inmediato en D1
- En segundo plano, el Workflow genera el vector e inserta en Vectorize
- Aunque la vectorización tarde unos segundos, no bloquea la petición del usuario
Implementar el Q&A inteligente
Ya puedes guardar notas; ahora la consulta.
Añade en src/index.ts:
// Consulta y respuesta
app.get('/', async (c) => {
const query = c.req.query('q');
if (!query) {
return c.json({ error: 'Query parameter "q" is required' }, 400);
}
// Paso 1: convertir la pregunta en vector
const queryEmbedding = await c.env.AI.run(
'@cf/baai/bge-base-en-v1.5',
{ text: [query] }
);
// Paso 2: buscar las 3 notas más similares en Vectorize
const matches = await c.env.VECTORIZE.query(
queryEmbedding.data[0],
{ topK: 3, returnMetadata: true }
);
if (matches.count === 0) {
return c.json({ answer: 'No se encontraron notas relacionadas' });
}
// Paso 3: obtener el texto completo desde D1 (si hace falta)
const noteIds = matches.matches.map(m => m.id);
const notes = await c.env.DB.prepare(
`SELECT text FROM notes WHERE id IN (${noteIds.map(() => '?').join(',')})`
).bind(...noteIds).all();
// Paso 4: armar el prompt y llamar al LLM
const context = notes.results.map((n: any) => n.text).join('\n\n---\n\n');
const prompt = `A continuación, contenido relevante de las notas:
${context}
Pregunta del usuario: ${query}
Responde basándote en las notas anteriores. Si no hay información suficiente, indícalo.`;
const aiResponse = await c.env.AI.run(
'@cf/meta/llama-3-8b-instruct', // O usa claude-3-5-sonnet-latest
{
messages: [
{ role: 'system', content: 'Eres un asistente inteligente de notas' },
{ role: 'user', content: prompt }
]
}
);
return c.json({
answer: aiResponse.response,
sources: matches.matches.map(m => ({
id: m.id,
score: m.score,
text: m.metadata?.text
}))
});
});
Prueba:
# Ejecución local
wrangler dev
# Añadir notas
curl -X POST http://localhost:8787/notes \
-H "Content-Type: application/json" \
-d '{"text": "Cloudflare Workers AI soporta modelos Llama 3 y Claude"}'
curl -X POST http://localhost:8787/notes \
-H "Content-Type: application/json" \
-d '{"text": "Vectorize usa similitud coseno para la recuperación vectorial"}'
# Espera unos segundos a que el Workflow termine la vectorización
# Preguntar
curl "http://localhost:8787/?q=Workers%20AI%20%E2%80%94%20qu%C3%A9%20modelos%20hay"
Si todo va bien, recibirás una respuesta basada en el contenido de las notas.
Eliminar y actualizar
Al borrar una nota, elimina también los datos en D1 y Vectorize:
app.delete('/notes/:id', async (c) => {
const id = c.req.param('id');
// Borrar de D1
await c.env.DB.prepare('DELETE FROM notes WHERE id = ?').bind(id).run();
// Borrar de Vectorize
await c.env.VECTORIZE.deleteByIds([id]);
return c.json({ message: 'Note deleted' });
});
Para actualizar, lo más simple es borrar y volver a crear (regenerar el vector).
El código completo está en el ejemplo oficial de Cloudflare.
Optimización avanzada: un RAG más inteligente
Con lo básico funcionando, para usarlo en producción conviene pulir algunos detalles.
Estrategia de fragmentación de texto
Ahora guardamos cada nota entera como una unidad. Si la nota es larga (p. ej. un documento técnico), hay problemas:
- Al recuperar, la similitud del documento completo puede ser baja (solo parte del texto es relevante)
- Un prompt demasiado largo puede superar la ventana de contexto del LLM
Mejor trocear el texto largo (chunk): cada fragmento genera su propio vector.
Método simple de troceo:
function splitText(text: string, chunkSize: number = 500, overlap: number = 50): string[] {
const chunks: string[] = [];
let start = 0;
while (start < text.length) {
const end = Math.min(start + chunkSize, text.length);
chunks.push(text.slice(start, end));
start = end - overlap; // Solapamiento para no cortar frases a medias
}
return chunks;
}
Una forma más inteligente es dividir por párrafos o por significado (p. ej. RecursiveCharacterTextSplitter de LangChain); para la mayoría de casos, longitud fija + solapamiento basta.
Modifica el Workflow y asigna un ID único a cada chunk:
const chunks = splitText(text);
for (let i = 0; i < chunks.length; i++) {
const chunkId = `${noteId}-${i}`;
const embeddings = await this.env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: [chunks[i]]
});
await this.env.VECTORIZE.insert([{
id: chunkId,
values: embeddings.data[0],
metadata: { noteId, chunkIndex: i, text: chunks[i] }
}]);
}
Mejorar la precisión de recuperación
Ajustar topK y umbral de similitud
Devolver solo top 3 puede quedarse corto o ser de más. Prueba con 5 y filtra similitudes bajas:
const matches = await c.env.VECTORIZE.query(queryEmbedding.data[0], {
topK: 5,
returnMetadata: true
});
// Conservar solo resultados con similitud > 0.7
const relevantMatches = matches.matches.filter(m => m.score > 0.7);
La puntuación va de 0 a 1 (similitud coseno); por encima de 0.7 suele considerarse bastante relevante.
Optimizar el prompt
No limites a tirar el contenido recuperado a la IA; indica cómo usarlo:
const prompt = `Eres un asistente inteligente de notas. A continuación, contenido recuperado de la biblioteca de notas (ordenado por relevancia):
${context}
Responde estrictamente con base en ese contenido. Si no alcanza para responder, di claramente que «no se encontró información en las notas» y no inventes.
Pregunta del usuario: ${query}`;
Puntos clave:
- Dejar claro que es material recuperado
- Exigir respuestas solo con ese contenido
- Permitir decir «no lo sé»
Así reduces las alucinaciones.
Control de costos y limitación de tasa
La capa gratuita de Workers AI tiene un límite diario de Neurons (el valor concreto cambia; consulta la página de precios).
Monitorear uso:
En Cloudflare Dashboard → Workers AI ves el consumo diario. Cada modelo consume distinto: los de embedding son baratos; la generación con LLM, más cara.
Estrategia de degradación:
Si temes pasarte del límite:
- Limita la frecuencia por usuario (contador en KV o Durable Objects)
- Al superar la cuota, usa un modelo más pequeño o devuelve resultados en caché
- Para peticiones no críticas, devuelve solo el texto recuperado sin llamar al LLM
// Ejemplo simple de limitación de tasa
const userKey = c.req.header('X-User-ID') || 'anonymous';
const requestCount = await c.env.KV.get(`rate:${userKey}`) || 0;
if (requestCount > 100) {
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.env.KV.put(`rate:${userKey}`, requestCount + 1, { expirationTtl: 86400 });
Cambiar a un modelo más potente
Llama 3 8B ya rinde bien; si quieres mejor comprensión, prueba Claude:
// Primero vincula la API key de Anthropic en el Dashboard
const aiResponse = await c.env.AI.run('claude-3-5-sonnet-latest', {
messages: [
{ role: 'system', content: 'Eres un asistente inteligente de notas' },
{ role: 'user', content: prompt }
]
});
Claude entiende y escribe mejor, pero gasta más Neurons. Elige según tu caso.
En mi experiencia:
- Preguntas simples: Llama 3 basta
- Razonamiento o resúmenes: Claude va notablemente mejor
- Presupuesto ajustado: prueba primero con Llama y sube de modelo cuando lo necesites
Despliegue y escenarios de uso real
Flujo de despliegue
Tras probar en local, desplegar es muy simple:
wrangler deploy
Con eso Cloudflare:
- Empaqueta tu código
- Lo despliega en nodos perimetrales globales
- Te da un dominio
.workers.dev
Verás algo como:
Published rag-notes-app
https://rag-notes-app.your-account.workers.dev
Esa es la URL de tu API.
Dominio personalizado (opcional):
Si tu dominio está en Cloudflare:
wrangler domains add api.yourdomain.com
O en Dashboard → Workers & Pages → tu Worker → Settings → Domains.
Variables de entorno y secrets:
Si usas API key de Anthropic u otros datos sensibles:
wrangler secret put ANTHROPIC_API_KEY
# Introduce tu key
En código:
const apiKey = c.env.ANTHROPIC_API_KEY;
Escenarios de aplicación reales
Esta arquitectura RAG sirve para mucho. Algunos casos:
1. Q&A de base de conocimiento empresarial
Escenario: cientos de páginas de manual, documentación técnica y FAQ; a los nuevos les cuesta encontrar información.
Enfoque:
- Subir todos los documentos, trocear por capítulo y guardar en Vectorize
- Interfaz web simple o bot en el chat corporativo
- El empleado pregunta «¿cuál es el proceso de reembolso?» y el sistema recupera el capítulo y responde
Ventaja: disponible 24/7, mucho más rápido que buscar en PDFs.
2. Atención al cliente inteligente
Escenario: e-commerce con mucha información de productos y políticas de posventa; el equipo repite las mismas respuestas.
Enfoque:
- Guardar FAQ, descripciones y políticas de devolución
- Ante una consulta, primero responde el sistema RAG
- Si no puede, pasa a un humano
Resultado: un desarrollador con este esquema redujo la carga del equipo de soporte en más del 60 %.
3. Asistente de notas personales
Escenario: años de notas en Notion u Obsidian y necesitas un dato concreto al momento.
Enfoque:
- Exportar notas periódicamente y cargarlas vía API al sistema RAG
- Preguntar «¿cuál era ese truco de TypeScript que vi?»
- El sistema devuelve los fragmentos relevantes
Yo uso algo parecido; encontrar material es mucho más rápido.
4. Herramienta «Chat with PDF»
Escenario: el usuario sube un PDF (paper, contrato, informe) y quiere extraer datos sin leerlo entero.
Enfoque (ver caso de Rohit Patil):
- Subida del PDF a R2
- El Worker extrae texto, trocea y vectoriza
- El usuario pregunta «¿cuáles son las condiciones de pago de este contrato?»
Muy útil en legal, consultoría y similares.
Resolución de problemas frecuentes
Problema 1: dimensiones del vector no coinciden
Error: dimension mismatch: expected 768, got 512
Causa: las dimensiones del índice Vectorize (768) no coinciden con las del modelo.
Solución: alinea índice y modelo. bge-base-en-v1.5 usa 768 dimensiones; no mezcles modelos.
Problema 2: datos inconsistentes entre D1 y Vectorize
Síntoma: un ID de nota devuelto en la consulta no existe en D1.
Causa: borraste en D1 y olvidaste Vectorize, o el Workflow falló.
Solución: envuelve el borrado en una transacción o usa Workflow para limpiar ambos lados.
Problema 3: timeout del Workflow
Error: workflow execution timeout
Causa: vectorizar mucho texto supera el límite de tiempo del Workflow.
Solución: divide el documento en varias tareas Workflow o procésalo por lotes.
// Procesamiento por lotes
const batchSize = 10;
for (let i = 0; i < chunks.length; i += batchSize) {
const batch = chunks.slice(i, i + batchSize);
await c.env.RAG_WORKFLOW.create({
params: { noteId, chunks: batch, offset: i }
});
}
Conclusión
Repasemos lo que hicimos:
- Entender RAG: generación aumentada por recuperación, «examen con apuntes» para la IA: buscar primero, responder después
- Montar una app funcional: sistema de Q&A sobre notas, de entorno a código
- Optimizar: troceo, ajuste de recuperación y control de costos para uso real
- Ver escenarios: base empresarial, soporte, asistente personal, chat con PDF
La gran ventaja del stack de Cloudflare es la baja barrera de entrada: sin GPU, sin montar bases de datos ni operaciones; la cuota gratuita alcanza para proyectos personales. En producción, los planes de pago suelen salir más baratos que montarlo tú mismo.
Siguientes pasos:
- Probar ya: clona el ejemplo oficial,
wrangler dev, y en 5 minutos ves resultados - Conectar datos reales: importa notas, documentos o FAQ y evalúa la calidad de recuperación
- Añadir interfaz: chat simple con React/Vue o despliega con Cloudflare Pages
- Explorar más: RAG multimodal (imágenes, tablas), GraphRAG (grafos de conocimiento), etc.
RAG es una de las arquitecturas más prácticas en aplicaciones de IA hoy; dominarla abre muchas posibilidades. En la práctica, el stack de Cloudflare resuelve bastantes problemas reales.
Si te atascas, pregunta en Cloudflare Discord o en el foro de la comunidad; la comunidad es activa.
FAQ
¿Qué diferencia hay entre RAG y un motor de búsqueda tradicional?
• Devuelve enlaces a documentos según coincidencia de palabras clave
RAG:
• Recupera contenido relevante por comprensión semántica y genera respuestas en lenguaje natural
• Entiende que «los gatos son adorables» y «los gatitos son tiernos» significan algo similar
• La búsqueda tradicional solo coincide con las mismas palabras clave
• RAG da la respuesta directamente, sin que el usuario revise varios documentos
¿Qué tamaño de base de conocimiento soporta el plan gratuito de Cloudflare?
• D1 gratuito: 10 GB de almacenamiento
• Vectorize gratuito: 5 millones de vectores (aprox. 5 GB de texto)
Suficiente para proyectos personales y bases de conocimiento de pymes.
Si necesitas más, sube al plan de pago o usa varios índices en fragmentos.
¿Cómo mejorar la precisión de recuperación en RAG?
1) Fragmentación adecuada:
• chunk size de 500-1000 caracteres
• overlap de 50-100 caracteres
2) Ajuste de parámetros:
• topK (normalmente 3-5 resultados)
• umbral de similitud (>0.7)
3) Optimizar el prompt para que la IA responda solo con el contenido recuperado
4) Usar un modelo de embedding mejor (p. ej. text-embedding-3 de OpenAI)
5) Cachear respuestas de preguntas frecuentes
¿Cómo controlar el costo de una aplicación RAG?
1) Usar la capa gratuita de Workers AI (cuota diaria fija de Neurons)
2) Limitar la frecuencia de solicitudes por usuario (contador en KV)
3) Cachear respuestas de preguntas frecuentes
4) Llama 3 para preguntas simples; Claude para tareas complejas
5) Monitorear el consumo diario y, cerca del límite, degradar a solo devolver resultados de recuperación sin llamar al LLM
¿Para qué escenarios reales sirve RAG?
1) Q&A de base de conocimiento empresarial (manuales, documentación técnica, políticas)
2) Atención al cliente inteligente (consultas de productos, políticas de posventa, FAQ)
3) Asistente de notas personales (recuperar años de notas acumuladas)
4) Herramienta de Q&A sobre documentos (subir PDF/Word y extraer información)
Cualquier escenario donde haya que responder con conocimiento existente.
15 min de lectura · Publicado el: 1 dic 2025 · Actualizado el: 21 ago 2026
Guía Cloudflare AI Stack
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
¿Las bases de datos vectoriales son demasiado caras? La versión gratuita de Vectorize te permite implementar búsqueda semántica en 30 minutos
Tutorial de Cloudflare Vectorize sin coste inicial: implementa búsqueda semántica en 30 minutos, ahorra 50 USD/mes frente a Pinecone. Código completo y guía de errores comunes para proyectos personales y validación rápida de MVP, con cuota gratuita de 5 millones de vectores.
Parte 4 de 5
Siguiente
Este es el artículo más reciente de la serie por ahora.



Comentarios
Inicia sesión con GitHub para dejar un comentario