Pylarion Logo Pylarion Logo Pylarion
DESARROLLO WEB
14 MIN READ

Creando una aplicación RAG completa con Nuxt y Gemini File Search

Pylarion Pylarion

Pylarion

Equipo de Pylarion. Construimos software confiable donde la tecnología no puede fallar. • 29 de junio de 2026

Creando una aplicación RAG completa con Nuxt y Gemini File Search

Construyendo una Aplicación RAG Completa con Nuxt y la API Gemini File Search de Google

La inteligencia artificial generativa ha transformado radicalmente la manera en que los desarrolladores conciben las aplicaciones modernas. Sin embargo, uno de los desafíos más persistentes en el despliegue de modelos de lenguaje de gran escala (LLMs) es su incapacidad nativa para acceder a información actualizada o específica de un dominio determinado. Es precisamente aquí donde la arquitectura RAG —Retrieval-Augmented Generation— emerge como una solución técnicamente sólida y pragmáticamente poderosa. En este tutorial, exploraremos cómo construir una aplicación RAG completa desde cero, combinando el framework Nuxt con la API Gemini File Search de Google para lograr respuestas precisas y contextualmente enriquecidas.

Antes de escribir una sola línea de código, es fundamental comprender el ecosistema conceptual en el que nos moveremos. RAG es un patrón arquitectónico que combina la recuperación de información desde una base de datos vectorial o un almacén de documentos indexados, con la capacidad generativa de un LLM. En lugar de depender únicamente del conocimiento preentrenado del modelo, el sistema recupera fragmentos de texto relevantes en tiempo real y los suministra como contexto adicional en el prompt. El resultado es una IA que responde con mayor precisión, reduce las alucinaciones y puede operar sobre documentación propietaria sin necesidad de reentrenamiento. La API Gemini File Search de Google encapsula gran parte de esta complejidad, ofreciendo un mecanismo de indexación y consulta directamente integrado en su ecosistema de modelos.

Definición de Términos Clave del Ecosistema RAG

Para abordar este proyecto con rigor técnico, es imprescindible dominar la terminología específica del paradigma RAG. A continuación se detallan los conceptos fundamentales que estructuran toda la arquitectura:

  • Embedding: Representación vectorial densa de un fragmento de texto en un espacio matemático de alta dimensión, que permite calcular similitud semántica entre documentos y consultas.
  • Vector Store (Almacén Vectorial): Base de datos especializada en almacenar y recuperar eficientemente vectores de alta dimensionalidad mediante búsquedas por similitud coseno o distancia euclidiana.
  • Chunking: Proceso de segmentación de documentos extensos en fragmentos manejables, optimizando tanto la precisión del embedding como el rendimiento de la recuperación.
  • Grounding: Técnica que ancla las respuestas del LLM a fuentes de información verificables, mitigando las alucinaciones del modelo.
  • Retriever: Componente responsable de identificar y extraer los fragmentos más relevantes del almacén ante una consulta dada.
  • Augmented Prompt: El prompt final enviado al LLM, que incluye tanto la consulta original del usuario como el contexto recuperado por el retriever.
  • File Search API: Servicio de Google que abstrae las operaciones de indexación, almacenamiento y recuperación semántica de documentos dentro del ecosistema Gemini.

Configuración Inicial del Entorno de Desarrollo

El primer paso consiste en inicializar un proyecto Nuxt con la configuración adecuada para soportar operaciones de servidor. Nuxt es un framework full-stack basado en Vue.js que ofrece un sistema de rutas de servidor nativo mediante su directorio server/, lo cual resulta idóneo para gestionar llamadas a APIs externas sin exponer credenciales al cliente. La versión recomendada es Nuxt 3, que incorpora soporte nativo para TypeScript, composables reactivos y un motor de renderizado optimizado.

# Crear el proyecto Nuxt
npx nuxi@latest init rag-gemini-app
cd rag-gemini-app

# Instalar el SDK oficial de Google Generative AI
npm install @google/generative-ai

# Instalar dependencias adicionales de utilidad
npm install @nuxtjs/tailwindcss

Una vez creado el scaffold del proyecto, es imperativo configurar la clave API de Gemini de forma segura. Nunca debe exponerse esta credencial en el código fuente del cliente. En Nuxt 3, la gestión de variables de entorno se realiza mediante el fichero .env en la raíz del proyecto, y su acceso en el lado del servidor se realiza a través del módulo useRuntimeConfig. Esta separación garantiza que la clave permanezca exclusivamente en el contexto del servidor Node.js, inaccesible desde el navegador.

# .env
GEMINI_API_KEY=tu_clave_api_de_google_ai_studio
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    geminiApiKey: process.env.GEMINI_API_KEY, // Solo accesible en servidor
    public: {
      // Variables públicas aquí
    }
  }
})

Arquitectura del Servidor: Endpoints para la Gestión del Flujo RAG

El núcleo de la aplicación reside en el directorio server/api/, donde definiremos múltiples endpoints que orquestan las distintas fases del pipeline RAG. Esta separación en rutas discretas sigue el principio de responsabilidad única y facilita el mantenimiento, las pruebas unitarias y la escalabilidad futura. El flujo completo contempla tres operaciones fundamentales: inicializar el vector store, cargar e indexar documentos y ejecutar consultas semánticas contra el almacén.

El primer endpoint que debemos implementar es el de inicialización del almacén de datos. Este endpoint se encarga de instanciar el cliente de la API Gemini y preparar el espacio de almacenamiento donde se indexarán los documentos. Internamente, la File Search API de Google maneja la creación del índice vectorial de forma transparente, abstrayendo la complejidad del proceso de embedding. La respuesta incluye un identificador único del almacén que será referenciado en operaciones posteriores.

// server/api/store/initialize.post.ts
import { GoogleGenerativeAI } from '@google/generative-ai'

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  
  try {
    const genAI = new GoogleGenerativeAI(config.geminiApiKey)
    
    // Inicializar el File Manager para gestión de documentos
    const fileManager = genAI.getFileManager()
    
    return {
      success: true,
      message: 'Vector store inicializado correctamente',
      timestamp: new Date().toISOString()
    }
  } catch (error) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Error al inicializar el almacén vectorial'
    })
  }
})

El segundo endpoint crítico gestiona la carga e indexación de documentos. Este proceso recibe archivos desde el cliente, los transmite a la API de Gemini File Search y aguarda la confirmación de que el proceso de indexación ha concluido satisfactoriamente. Es importante implementar un mecanismo de polling o espera activa, ya que la indexación es una operación asíncrona que puede tardar varios segundos dependiendo del tamaño del documento. Una vez completada la indexación, el documento queda disponible para consultas semánticas.

// server/api/documents/upload.post.ts
import { GoogleGenerativeAI } from '@google/generative-ai'

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const formData = await readFormData(event)
  const file = formData.get('document') as File
  
  if (!file) {
    throw createError({ statusCode: 400, statusMessage: 'No se proporcionó archivo' })
  }

  const genAI = new GoogleGenerativeAI(config.geminiApiKey)
  const fileManager = genAI.getFileManager()
  
  // Convertir File a Buffer para la transmisión
  const arrayBuffer = await file.arrayBuffer()
  const buffer = Buffer.from(arrayBuffer)
  
  // Cargar archivo a Gemini File API
  const uploadResponse = await fileManager.uploadFile(buffer, {
    mimeType: file.type,
    displayName: file.name
  })
  
  // Polling hasta que el estado sea ACTIVE
  let fileInfo = await fileManager.getFile(uploadResponse.file.name)
  while (fileInfo.state === 'PROCESSING') {
    await new Promise(resolve => setTimeout(resolve, 2000))
    fileInfo = await fileManager.getFile(uploadResponse.file.name)
  }
  
  if (fileInfo.state === 'FAILED') {
    throw createError({ statusCode: 500, statusMessage: 'Error en el procesamiento del archivo' })
  }
  
  return {
    success: true,
    fileUri: fileInfo.uri,
    displayName: fileInfo.displayName,
    mimeType: fileInfo.mimeType
  }
})

El Endpoint de Consulta: Generación Aumentada por Recuperación

El endpoint de consulta representa el corazón funcional de toda la arquitectura RAG. En él convergen la recuperación semántica y la generación del LLM en una única operación coordinada. La lógica consiste en recibir la pregunta del usuario junto con las referencias URI de los documentos previamente indexados, construir un prompt aumentado que incluya tanto la consulta como el contexto documental, y enviarlo al modelo Gemini para obtener una respuesta fundamentada en el contenido específico de los archivos cargados. Este enfoque garantiza que el modelo no fabrique información, sino que la extraiga directamente de las fuentes suministradas.

// server/api/query/index.post.ts
import { GoogleGenerativeAI } from '@google/generative-ai'

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const body = await readBody(event)
  const { question, fileUris } = body
  
  if (!question || !fileUris?.length) {
    throw createError({ statusCode: 400, statusMessage: 'Parámetros insuficientes' })
  }
  
  const genAI = new GoogleGenerativeAI(config.geminiApiKey)
  const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' })
  
  // Construir las partes del mensaje con referencias a archivos indexados
  const fileParts = fileUris.map((uri: string) => ({
    fileData: { fileUri: uri, mimeType: 'application/pdf' }
  }))
  
  const result = await model.generateContent([
    ...fileParts,
    {
      text: `Basándote ÚNICAMENTE en los documentos proporcionados, responde la siguiente pregunta de manera precisa y detallada: ${question}`
    }
  ])
  
  return {
    answer: result.response.text(),
    sources: fileUris,
    timestamp: new Date().toISOString()
  }
})

Desarrollo de la Interfaz de Usuario con Nuxt y Vue

La capa de presentación debe reflejar fielmente el estado del pipeline RAG, proporcionando al usuario retroalimentación visual en cada etapa del proceso. La interfaz se estructura en tres paneles principales: un área de carga de documentos con indicadores de progreso de indexación, una sección de consulta con historial de conversación y un panel de gestión de los textos indexados que permite visualizar, referenciar y eliminar documentos del almacén. La reactividad nativa de Vue 3 junto con los composables de Nuxt facilita enormemente la sincronización entre el estado del servidor y la representación visual.

<!-- pages/index.vue -->
<template>
  <div class="rag-app-container">
    <section class="upload-section">
      <h3>Cargar Documentos</h3>
      <input type="file" @change="handleFileUpload" accept=".pdf,.txt,.docx" />
      <div v-if="uploadStatus" class="status-indicator">
        <span :class="statusClass">{{ uploadStatus }}</span>
      </div>
    </section>

    <section class="documents-section">
      <h3>Documentos Indexados</h3>
      <ul>
        <li v-for="doc in indexedDocuments" :key="doc.uri">
          {{ doc.displayName }}
          <button @click="removeDocument(doc.uri)">Eliminar</button>
        </li>
      </ul>
    </section>

    <section class="query-section">
      <h3>Consultar Base de Conocimiento</h3>
      <textarea v-model="userQuery" placeholder="Introduce tu pregunta..." />
      <button @click="executeQuery" :disabled="!userQuery || isLoading">
        {{ isLoading ? 'Procesando...' : 'Consultar' }}
      </button>
      
      <div v-if="queryResult" class="result-container">
        <blockquote>
          <p>{{ queryResult }}</p>
        </blockquote>
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
const uploadStatus = ref('')
const indexedDocuments = ref<Array<{uri: string, displayName: string}>>([])
const userQuery = ref('')
const queryResult = ref('')
const isLoading = ref(false)

const statusClass = computed(() => ({
  'text-green-500': uploadStatus.value.includes('completado'),
  'text-yellow-500': uploadStatus.value.includes('procesando'),
  'text-red-500': uploadStatus.value.includes('error')
}))

const handleFileUpload = async (event: Event) => {
  const input = event.target as HTMLInputElement
  const file = input.files?.[0]
  if (!file) return

  uploadStatus.value = 'Cargando e indexando documento...'
  const formData = new FormData()
  formData.append('document', file)

  try {
    const response = await $fetch('/api/documents/upload', {
      method: 'POST',
      body: formData
    })
    indexedDocuments.value.push({
      uri: response.fileUri,
      displayName: response.displayName
    })
    uploadStatus.value = 'Indexación completada exitosamente'
  } catch {
    uploadStatus.value = 'Error durante la indexación'
  }
}

const executeQuery = async () => {
  if (!userQuery.value || !indexedDocuments.value.length) return
  isLoading.value = true
  
  try {
    const response = await $fetch('/api/query', {
      method: 'POST',
      body: {
        question: userQuery.value,
        fileUris: indexedDocuments.value.map(d => d.uri)
      }
    })
    queryResult.value = response.answer
  } finally {
    isLoading.value = false
  }
}

const removeDocument = (uri: string) => {
  indexedDocuments.value = indexedDocuments.value.filter(d => d.uri !== uri)
}
</script>

Consideraciones de Seguridad y Optimización en Producción

Antes de desplegar la aplicación en un entorno productivo, es fundamental revisar una serie de aspectos críticos que determinan tanto la seguridad como el rendimiento del sistema. La gestión de la clave API ya ha sido abordada mediante variables de entorno, pero existen otras superficies de ataque que merecen atención. En particular, la validación y sanitización de los archivos cargados es un vector de riesgo que no debe subestimarse: deben implementarse restricciones estrictas sobre los tipos MIME aceptados, el tamaño máximo de archivo y la frecuencia de carga por usuario mediante mecanismos de rate limiting.

Desde la perspectiva del rendimiento, el proceso de polling durante la indexación puede convertirse en un cuello de botella si múltiples usuarios cargan documentos simultáneamente. Una estrategia recomendada es implementar una cola de trabajos asíncronos con almacenamiento de estado en Redis, combinada con Server-Sent Events (SSE) para notificar al cliente cuando la indexación ha completado, eliminando así la necesidad de polling activo desde el servidor.

  1. Validación de archivos: Verificar el tipo MIME real del archivo, no solo la extensión declarada por el cliente.
  2. Rate limiting: Implementar limitación de peticiones por IP y por usuario autenticado para prevenir abusos de la API.
  3. Gestión de errores granular: Distinguir entre errores transitorios de red y errores permanentes de la API para implementar lógicas de reintento apropiadas.
  4. Caché de respuestas: Almacenar en caché las respuestas a consultas frecuentes para reducir latencia y costos de API.
  5. Monitorización: Integrar herramientas de observabilidad como OpenTelemetry para trazar el pipeline completo de principio a fin.
  6. Limpieza de archivos: Implementar políticas de retención para eliminar automáticamente archivos indexados que ya no son necesarios, controlando así el costo del almacenamiento en la API de Gemini.

Conclusiones y Próximos Pasos

La combinación de Nuxt y la API Gemini File Search demuestra ser una plataforma técnicamente coherente y productivamente eficiente para construir aplicaciones RAG en el mundo real. La arquitectura descrita en este tutorial cubre el ciclo completo desde la ingesta de documentos hasta la generación de respuestas fundamentadas, todo ello con una clara separación entre la lógica de servidor y la capa de presentación. El resultado es una aplicación mantenible, segura y extensible que puede adaptarse a una amplia variedad de casos de uso: desde sistemas de soporte documental hasta asistentes técnicos internos o plataformas de consulta legal y médica.

"RAG no es simplemente una técnica de optimización de prompts; es un cambio de paradigma que permite a los LLMs operar como sistemas de conocimiento dinámicos, actualizables y verificables sin necesidad de costosos procesos de fine-tuning."

Como extensiones naturales de este proyecto, resulta especialmente interesante explorar la implementación de un sistema de reranking que mejore la calidad de los fragmentos recuperados antes de su inclusión en el prompt aumentado. Asimismo, la integración de mecanismos de memoria conversacional permitiría mantener el contexto a lo largo de múltiples turnos de diálogo, transformando la aplicación en un sistema de consulta verdaderamente conversacional. Finalmente, la evaluación sistemática de la calidad de las respuestas mediante métricas como RAGAS —que mide la fidelidad, relevancia y completitud de las respuestas generadas— representa un paso imprescindible para cualquier despliegue en producción que aspire a estándares de calidad empresarial.

0
Pylarion Pylarion

Pylarion

Desarrollo Web Expert

Equipo de Pylarion. Construimos software confiable donde la tecnología no puede fallar.

Blog / Desarrollo Web / Creando una aplicación RAG completa con Nuxt y Gemini File Search

Comentarios

(0)
Categoría: Desarrollo Web

Artículos Recomendados

Creando un editor colaborativo con Nuxt UI, Jazz e IA
Desarrollo Web
3
0
0

Creando un editor colaborativo con Nuxt UI, Jazz e IA

El autor comparte su experiencia desarrollando un editor de texto enriquecido colaborativo en tiempo real utilizando Nuxt UI y Jazz, asistido en gran medida por inteligencia artificial. Inicialmente planeado como un tutorial paso a paso, el artículo evoluciona hacia una reflexión sobre cómo la IA facilita la programación, una práctica conocida como vibe coding. Se detalla la motivación de construir una solución propia dentro del ecosistema Nuxt para mantener el control completo del código, superando así las limitaciones de varias opciones prefabricadas. El proyecto resultante incluye características clave como soporte multiusuario, comentarios contextuales estilo Notion, carga de imágenes y resolución de respuestas. Además de proporcionar el código base de código abierto para que la comunidad lo clone, el autor ofrece importantes lecciones sobre el desarrollo guiado por agentes de IA, destacando la necesidad de forzarlos a verificar iterativamente su trabajo mediante el navegador.

Leer artículo
Construyendo una aplicación RAG con Nuxt y Gemini File Search
Desarrollo Web
5
0
0

Construyendo una aplicación RAG con Nuxt y Gemini File Search

Este artículo es un tutorial técnico paso a paso para construir una aplicación de Generación Aumentada por Recuperación (RAG) utilizando el framework Nuxt y la API Gemini File Search de Google. Explica cómo indexar documentos propios, recuperar el contexto más relevante en fragmentos y generar respuestas precisas y fundamentadas por inteligencia artificial. El proyecto abarca la creación de un backend en Nuxt con utilidades y endpoints para interactuar con Gemini, gestionar los procesos de indexación y solicitar información. También describe una interfaz de usuario completa para probar el flujo integrado, permitiendo cargar textos, visualizar almacenes de datos y leer los estados del sistema. Además, el contenido define conceptos esenciales sobre la tecnología RAG y detalla exactamente la configuración inicial del proyecto, la gestión de dependencias y el manejo de variables de entorno.

Leer artículo
Gestión de Estado en Vue: Composables, Provide/Inject y Pinia
Desarrollo Web
2
0
0

Gestión de Estado en Vue: Composables, Provide/Inject y Pinia

Esta guía práctica explica cómo elegir la herramienta adecuada para compartir estado y lógica en aplicaciones Vue.js, evitando arquitecturas complejas. Destaca tres enfoques clave: Los Composables, que utilizan la Composition API para encapsular y reutilizar comportamiento donde cada consumidor obtiene su propio estado independiente, ideales para lógica local como validaciones o fetch de datos. Provide/Inject resulta útil para pasar o inyectar estado a lo largo de un árbol de componentes descendente sin necesidad de recurrir al tedioso paso de props múltiples ('prop drilling'). Por último, Pinia es la solución estándar recomendada para gestionar el estado global y centralizado de la aplicación (como sesiones de usuario o datos del carrito de compras), ya que vive fuera de la jerarquía de los componentes y ofrece integración completa con Devtools.

Leer artículo