Tutorial completo para construir una aplicación RAG con Nuxt y la API Gemini File Search de Google
La inteligencia artificial generativa ha transformado radicalmente la forma en que las aplicaciones interactúan con el conocimiento. Sin embargo, uno de sus límites más evidentes es la dependencia de datos de entrenamiento estáticos y genéricos, lo que dificulta obtener respuestas precisas sobre documentación interna, bases de conocimiento propietarias o información actualizada. Para superar esta barrera, surge la técnica conocida como RAG (Retrieval-Augmented Generation), que combina la recuperación semántica de información con la capacidad generativa de modelos como Gemini. En este tutorial, construiremos una aplicación RAG de extremo a extremo utilizando el framework Nuxt y la API Gemini File Search de Google.
A diferencia de los enfoques tradicionales donde el modelo responde exclusivamente con lo aprendido durante su entrenamiento, una arquitectura RAG permite indexar documentos propios en un almacén vectorial y, en tiempo de consulta, recuperar únicamente los fragmentos más relevantes para enriquecer el contexto que recibe el modelo. El resultado es una IA que responde con precisión quirúrgica sobre el corpus de información que tú mismo defines, reduciendo drásticamente las alucinaciones y aumentando la trazabilidad de las respuestas. Este paradigma resulta especialmente valioso en entornos empresariales donde la confidencialidad y la exactitud son requisitos no negociables.
Nuxt, el meta-framework basado en Vue.js, ofrece un entorno ideal para este tipo de aplicaciones gracias a su capacidad de manejar tanto el frontend como el backend dentro de un único proyecto cohesionado. Su sistema de server routes permite exponer endpoints HTTP con mínima configuración, mientras que su arquitectura basada en módulos facilita la integración con servicios externos. Combinado con la robustez de la API de Gemini, que incluye funcionalidades nativas de búsqueda semántica sobre archivos indexados, el resultado es una solución poderosa, escalable y sorprendentemente accesible para equipos de desarrollo web.
A lo largo de este artículo se detallará cada capa de la solución: la configuración inicial del proyecto, el diseño de los endpoints del backend, la lógica de indexación de documentos, el mecanismo de consulta al modelo y la interfaz de usuario que permite ejercitar el flujo completo. El objetivo no es únicamente mostrar código funcional, sino explicar las decisiones de diseño detrás de cada componente para que el lector pueda adaptar la arquitectura a sus propios casos de uso.
Antes de entrar en el código, es importante establecer un glosario conceptual claro, ya que muchos de los términos involucrados provienen de disciplinas distintas y suelen emplearse con imprecisión. Comprender con exactitud qué es un almacén de búsqueda, qué implica indexar un documento o cómo funciona la recuperación semántica es fundamental para tomar decisiones arquitectónicas sólidas durante el desarrollo.
Conceptos clave: RAG, almacenes vectoriales e indexación semántica
RAG (Retrieval-Augmented Generation) es una arquitectura de IA que divide el proceso de respuesta en dos fases distintas: una fase de recuperación (retrieval), donde se buscan los fragmentos de texto más relevantes para la consulta del usuario dentro de un corpus indexado, y una fase de generación (generation), donde el modelo de lenguaje recibe esos fragmentos como contexto adicional y produce una respuesta coherente y fundamentada. Esta separación de responsabilidades permite actualizar el conocimiento del sistema sin necesidad de reentrenar el modelo.
Un almacén de búsqueda (search store o vector store) es la estructura de datos que persiste los fragmentos de texto junto con sus representaciones vectoriales, también llamadas embeddings. Estos vectores codifican el significado semántico del texto en un espacio matemático de alta dimensión, de forma que dos fragmentos conceptualmente similares tendrán vectores próximos entre sí. La API Gemini File Search abstrae la complejidad de gestionar esta infraestructura, exponiendo operaciones de alto nivel para crear almacenes, añadir documentos y ejecutar búsquedas semánticas.
La indexación es el proceso mediante el cual un texto es fragmentado en unidades manejables (chunks), transformado en embeddings y almacenado en el vector store junto con sus metadatos originales. La calidad de la indexación tiene un impacto directo en la precisión de las respuestas finales: una fragmentación demasiado granular puede perder contexto, mientras que fragmentos excesivamente grandes pueden introducir ruido irrelevante en el prompt del modelo.
Configuración inicial del proyecto Nuxt
El primer paso es crear un nuevo proyecto Nuxt e instalar las dependencias necesarias. Asegúrate de tener Node.js 18 o superior instalado en tu entorno de desarrollo.
# Crear el proyecto Nuxt
npx nuxi@latest init nuxt-rag-app
cd nuxt-rag-app
# Instalar el SDK oficial de Google Generative AI
npm install @google/generative-ai
# Instalar dependencias adicionales
npm install @google/genai
Una vez creado el proyecto, es necesario configurar la clave de API de Gemini como variable de entorno. Crea un archivo .env en la raíz del proyecto con el siguiente contenido:
GEMINI_API_KEY=tu_clave_de_api_aqui
NUXT_SECRET_KEY=clave_secreta_para_el_servidor
A continuación, configura el archivo nuxt.config.ts para exponer las variables de entorno al runtime del servidor y habilitar las funcionalidades necesarias:
// nuxt.config.ts
export default defineNuxtConfig({
devtools: { enabled: true },
runtimeConfig: {
geminiApiKey: process.env.GEMINI_API_KEY,
public: {}
},
nitro: {
experimental: {
asyncContext: true
}
}
})
Arquitectura del backend: endpoints para gestionar el ciclo RAG completo
El backend de la aplicación se estructura en torno a cuatro endpoints principales, cada uno responsable de una operación específica dentro del ciclo RAG. Nuxt utiliza el directorio server/api/ para definir rutas HTTP que se ejecutan exclusivamente en el servidor, garantizando que la clave de API nunca quede expuesta al cliente.
- POST /api/stores: Crea un nuevo almacén de búsqueda con un nombre identificador.
- POST /api/stores/[id]/documents: Carga e indexa uno o varios documentos de texto en un almacén existente.
- GET /api/stores/[id]/status: Verifica el estado de indexación de los documentos cargados.
- POST /api/stores/[id]/query: Ejecuta una consulta semántica contra el almacén y obtiene una respuesta generada por Gemini.
El endpoint de creación de almacenes interactúa directamente con la API de Gemini para instanciar un nuevo corpus de búsqueda. La implementación en Nuxt es la siguiente:
// server/api/stores.post.ts
import { GoogleGenAI } from '@google/genai'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const body = await readBody(event)
const genAI = new GoogleGenAI({ apiKey: config.geminiApiKey })
try {
const corpus = await genAI.files.createCorpus({
displayName: body.name,
description: body.description || ''
})
return {
success: true,
storeId: corpus.name,
displayName: corpus.displayName,
createTime: corpus.createTime
}
} catch (error: any) {
throw createError({
statusCode: 500,
statusMessage: `Error al crear el almacén: ${error.message}`
})
}
})
El endpoint de carga de documentos recibe el texto en el cuerpo de la petición, lo fragmenta en chunks manejables y los indexa dentro del corpus indicado. La API de Gemini File Search gestiona internamente la generación de embeddings, por lo que el desarrollador únicamente necesita proporcionar el texto y los metadatos asociados:
// server/api/stores/[id]/documents.post.ts
import { GoogleGenAI } from '@google/genai'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const storeId = getRouterParam(event, 'id')
const body = await readBody(event)
const genAI = new GoogleGenAI({ apiKey: config.geminiApiKey })
// Fragmentación del documento en chunks de 1000 caracteres con solapamiento
const chunkSize = 1000
const overlap = 200
const chunks: string[] = []
for (let i = 0; i < body.content.length; i += chunkSize - overlap) {
chunks.push(body.content.slice(i, i + chunkSize))
}
const documents = await Promise.all(
chunks.map((chunk, index) =>
genAI.files.addDocument({
corpusName: storeId,
document: {
displayName: `${body.filename}-chunk-${index}`,
documentPassages: [{ content: { parts: [{ text: chunk }] } }]
}
})
)
)
return {
success: true,
chunksIndexed: documents.length,
documentIds: documents.map(d => d.name)
}
})
El endpoint de consulta: orquestando recuperación y generación
El endpoint de consulta es el núcleo de la arquitectura RAG y donde ocurre la magia real del sistema. Su funcionamiento puede dividirse en tres fases secuenciales: primero realiza una búsqueda semántica en el corpus para recuperar los fragmentos más relevantes para la pregunta del usuario; segundo, construye un prompt enriquecido que incluye esos fragmentos como contexto; y tercero, invoca al modelo Gemini para generar una respuesta fundamentada exclusivamente en la información recuperada.
// server/api/stores/[id]/query.post.ts
import { GoogleGenAI } from '@google/genai'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const storeId = getRouterParam(event, 'id')
const body = await readBody(event)
const genAI = new GoogleGenAI({ apiKey: config.geminiApiKey })
// Fase 1: Recuperación semántica
const retrievalResult = await genAI.files.queryCorpus({
corpusName: storeId,
query: body.question,
metadataFilters: [],
pageSize: 5
})
const relevantPassages = retrievalResult.relevantChunks
?.map(chunk => chunk.chunk?.data?.chunkData?.stringValue || '')
.filter(Boolean)
.join('\n\n---\n\n') || ''
// Fase 2: Construcción del prompt aumentado
const augmentedPrompt = `
Eres un asistente especializado. Responde la siguiente pregunta
basándote ÚNICAMENTE en el contexto proporcionado. Si la información
no está en el contexto, indícalo explícitamente.
CONTEXTO RECUPERADO:
${relevantPassages}
PREGUNTA DEL USUARIO:
${body.question}
RESPUESTA:
`
// Fase 3: Generación con Gemini
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-pro' })
const result = await model.generateContent(augmentedPrompt)
return {
answer: result.response.text(),
sourcesCount: retrievalResult.relevantChunks?.length || 0,
retrievedContext: relevantPassages
}
})
Un aspecto crítico de este endpoint es el diseño del prompt de sistema. La instrucción explícita de responder únicamente con el contexto recuperado es fundamental para evitar que el modelo mezcle su conocimiento de entrenamiento con la información indexada, lo cual podría producir respuestas híbridas difíciles de auditar. En entornos productivos, es recomendable añadir también instrucciones sobre el formato de la respuesta y el nivel de detalle esperado según el dominio de aplicación.
Interfaz de usuario: diseño del flujo de experiencia completo
La interfaz de usuario de la aplicación debe permitir ejercitar el ciclo RAG de forma secuencial e intuitiva. Se estructura en cuatro bloques funcionales que se corresponden exactamente con los cuatro endpoints del backend: creación de almacén, carga de documentos, verificación de estado y consulta interactiva.
<!-- pages/index.vue -->
<template>
<div class="rag-app">
<!-- Sección 1: Crear almacén -->
<section class="store-creator">
<h2>1. Crear Almacén de Búsqueda</h2>
<input v-model="storeName" placeholder="Nombre del almacén" />
<button @click="createStore" :disabled="isCreating">
{{ isCreating ? 'Creando...' : 'Crear Almacén' }}
</button>
<p v-if="currentStoreId" class="success">
✓ Almacén creado: {{ currentStoreId }}
</p>
</section>
<!-- Sección 2: Cargar documentos -->
<section class="document-loader" v-if="currentStoreId">
<h2>2. Cargar Documentos</h2>
<textarea
v-model="documentContent"
placeholder="Pega aquí el contenido del documento..."
rows="10"
/>
<input v-model="documentName" placeholder="Nombre del documento" />
<button @click="uploadDocument" :disabled="isUploading">
{{ isUploading ? 'Indexando...' : 'Cargar e Indexar' }}
</button>
</section>
<!-- Sección 3: Consulta -->
<section class="query-interface" v-if="currentStoreId">
<h2>3. Consultar al Modelo</h2>
<input v-model="userQuestion" placeholder="¿Qué deseas saber?" />
<button @click="executeQuery" :disabled="isQuerying">
{{ isQuerying ? 'Consultando...' : 'Consultar' }}
</button>
<div v-if="queryResult" class="result-display">
<h3>Respuesta:</h3>
<p>{{ queryResult.answer }}</p>
<details>
<summary>Contexto recuperado ({{ queryResult.sourcesCount }} fragmentos)</summary>
<pre>{{ queryResult.retrievedContext }}</pre>
</details>
</div>
</section>
</div>
</template>
La sección de visualización de contexto recuperado, implementada mediante el elemento <details>, es especialmente valiosa desde el punto de vista del debugging y la auditoría. Al permitir al usuario comparar la respuesta generada con los fragmentos exactos que el modelo recibió como contexto, se facilita la identificación de problemas de fragmentación, indexación deficiente o consultas mal formuladas. Esta transparencia en el flujo de datos es una práctica recomendada en cualquier sistema RAG destinado a entornos productivos.
Consideraciones de rendimiento y mejoras arquitectónicas
Una vez validado el flujo básico, existen varias optimizaciones que marcan la diferencia entre un prototipo funcional y una aplicación lista para producción. A continuación se enumeran las más relevantes:
- Caché de resultados de recuperación: Preguntas similares pueden generar las mismas consultas al vector store. Implementar una capa de caché con Redis o una solución en memoria puede reducir significativamente la latencia y el coste de las llamadas a la API.
- Estrategia de chunking adaptativa: En lugar de fragmentar por número fijo de caracteres, considera estrategias semánticas que respeten párrafos, secciones o unidades de información lógicas del documento original.
- Reranking de resultados: Los fragmentos recuperados en la fase de búsqueda semántica pueden reordenarse mediante un modelo de reranking secundario para maximizar la relevancia antes de construir el prompt final.
- Gestión de múltiples formatos: Ampliar el soporte de ingesta para procesar PDF, DOCX o Markdown, extrayendo el texto limpio antes de la indexación.
- Monitorización de métricas RAG: Implementar evaluaciones automáticas de faithfulness (fidelidad al contexto) y answer relevancy para detectar degradaciones en la calidad de las respuestas a lo largo del tiempo.
"La arquitectura RAG no es simplemente una técnica de optimización de prompts; es un cambio de paradigma que separa el conocimiento del razonamiento, permitiendo que los modelos de lenguaje sean herramientas de inferencia aplicables a cualquier corpus de información sin necesidad de reentrenamiento."
La combinación de Nuxt y la API Gemini File Search demuestra que implementar una arquitectura RAG completa no requiere infraestructura compleja ni conocimientos profundos en aprendizaje automático. El ecosistema de herramientas disponible en 2024 permite a equipos de desarrollo web abordar este tipo de soluciones con las mismas habilidades y patrones que ya utilizan en sus proyectos cotidianos. La clave está en comprender el flujo de datos, diseñar cuidadosamente la estrategia de indexación y construir una capa de evaluación que permita iterar sobre la calidad del sistema de forma continua y sistemática.


