Pylarion Logo Pylarion Logo Pylarion
DESARROLLO WEB
13 MIN READ

Creación de una aplicación RAG con Nuxt y Gemini File Search

Pylarion Pylarion

Pylarion

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

Creación de una aplicación RAG con Nuxt y Gemini File Search

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

La Generación Aumentada por Recuperación, conocida por sus siglas en inglés como RAG (Retrieval-Augmented Generation), representa uno de los paradigmas más sólidos en el desarrollo moderno de aplicaciones de inteligencia artificial. A diferencia de los modelos de lenguaje convencionales que responden únicamente con base en sus datos de entrenamiento, una arquitectura RAG incorpora un mecanismo de recuperación dinámica que permite al modelo consultar una base de conocimiento propia antes de generar una respuesta. Esta distinción es fundamental cuando se trabaja con documentación técnica, bases de datos corporativas o cualquier tipo de contenido especializado que no forma parte del corpus público de entrenamiento.

En este artículo se detalla el proceso completo para construir una aplicación RAG funcional empleando Nuxt como framework full-stack y la API Gemini File Search de Google como motor de indexación y recuperación semántica. Nuxt, construido sobre Vue.js, ofrece una arquitectura híbrida que facilita la creación simultánea de endpoints de servidor y componentes de interfaz de usuario dentro de un mismo proyecto, lo cual resulta especialmente conveniente para prototipos y aplicaciones de producción de escala media. La combinación con el SDK de Gemini permite aprovechar capacidades avanzadas de embeddings y búsqueda vectorial sin necesidad de infraestructura adicional.

Conceptos fundamentales del patrón RAG

Antes de abordar la implementación, es necesario comprender el flujo de datos que gobierna cualquier sistema RAG. El proceso se divide en dos fases claramente diferenciadas: la fase de indexación y la fase de inferencia. Durante la indexación, los documentos fuente son procesados, divididos en fragmentos o chunks de tamaño controlado, convertidos en vectores de embeddings y almacenados en un índice recuperable. Durante la inferencia, la consulta del usuario se transforma también en un vector, se compara contra el índice para identificar los fragmentos más relevantes por similitud semántica, y esos fragmentos se inyectan como contexto en el prompt enviado al modelo generativo.

La API Gemini File Search abstrae gran parte de esta complejidad, gestionando internamente la vectorización y la búsqueda semántica. Sin embargo, comprender el flujo subyacente permite tomar decisiones arquitectónicas informadas, como el tamaño óptimo de los chunks, la estrategia de solapamiento entre fragmentos o la gestión del límite de tokens en el contexto enviado al modelo. En aplicaciones de producción, estos parámetros tienen un impacto directo en la calidad de las respuestas generadas.

Configuración del entorno y gestión de dependencias

El primer paso consiste en inicializar un proyecto Nuxt en su versión más reciente. Desde la terminal, el comando npx nuxi@latest init rag-nuxt-app genera la estructura base del proyecto. Una vez dentro del directorio, se procede a instalar las dependencias necesarias. El SDK oficial de Google Generative AI para JavaScript es la pieza central, complementado opcionalmente con utilidades para el manejo de archivos y la fragmentación de texto.

# Inicializar el proyecto
npx nuxi@latest init rag-nuxt-app
cd rag-nuxt-app

# Instalar dependencias principales
npm install @google/generative-ai
npm install @google/generative-ai/server

# Dependencias auxiliares para procesamiento de texto
npm install langchain @langchain/textsplitters

La gestión segura de credenciales es un aspecto crítico. La clave de API de Gemini debe almacenarse exclusivamente en variables de entorno y nunca exponerse en el código fuente ni en el cliente. Nuxt proporciona el archivo .env para este propósito, cuyas variables son accesibles en el servidor a través de useRuntimeConfig(). Es imperativo agregar el archivo .env al .gitignore del proyecto para evitar filtraciones accidentales en repositorios de control de versiones.

# Archivo .env en la raíz del proyecto
GEMINI_API_KEY=tu_clave_de_api_aqui
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    geminiApiKey: process.env.GEMINI_API_KEY,
    public: {}
  }
})

Funciones de utilidad en el servidor

Nuxt organiza la lógica de servidor dentro del directorio server/, que incluye subdirectorios para api/ (endpoints HTTP) y utils/ (funciones auxiliares reutilizables). Se recomienda encapsular la inicialización del cliente Gemini y las operaciones de indexación en funciones de utilidad independientes, promoviendo la separación de responsabilidades y facilitando las pruebas unitarias. La función principal de utilidad gestiona la instancia del cliente y expone métodos para subir archivos, listar archivos indexados y realizar consultas.

// server/utils/gemini.ts
import { GoogleGenerativeAI } from '@google/generative-ai'
import { GoogleAIFileManager } from '@google/generative-ai/server'

export const useGeminiClient = () => {
  const config = useRuntimeConfig()
  const genAI = new GoogleGenerativeAI(config.geminiApiKey)
  const fileManager = new GoogleAIFileManager(config.geminiApiKey)
  
  const model = genAI.getGenerativeModel({
    model: 'gemini-1.5-flash',
  })

  return { genAI, fileManager, model }
}

La función de fragmentación de texto merece especial atención. Un tamaño de chunk demasiado pequeño puede perder contexto relevante, mientras que uno excesivamente grande puede saturar la ventana de contexto del modelo. Para la mayoría de los casos de uso con documentación técnica, un tamaño de entre 500 y 1000 tokens con un solapamiento del 10-20% ofrece un equilibrio adecuado. La siguiente función implementa una estrategia de fragmentación básica pero efectiva.

// server/utils/textSplitter.ts
export const splitTextIntoChunks = (
  text: string,
  chunkSize: number = 800,
  overlap: number = 100
): 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 += chunkSize - overlap
  }

  return chunks
}

Endpoints de API para indexación y consulta

El sistema requiere al menos tres endpoints bien diferenciados: uno para cargar e indexar documentos, otro para listar los archivos disponibles en el almacenamiento y un tercero para realizar consultas RAG. Esta separación permite una interfaz de usuario modular y facilita la gestión del ciclo de vida de los documentos indexados. A continuación se muestra la implementación del endpoint de indexación, que recibe texto plano, lo fragmenta y lo sube a la API de Gemini File Search.

// server/api/index-document.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { text, fileName } = body

  if (!text || !fileName) {
    throw createError({
      statusCode: 400,
      message: 'Se requieren los campos text y fileName'
    })
  }

  const { fileManager } = useGeminiClient()
  const chunks = splitTextIntoChunks(text)
  const uploadedFiles = []

  for (const [index, chunk] of chunks.entries()) {
    const blob = new Blob([chunk], { type: 'text/plain' })
    const uploadResult = await fileManager.uploadFile(
      // Convertir Blob a Buffer para la API
      Buffer.from(await blob.arrayBuffer()),
      {
        mimeType: 'text/plain',
        displayName: `${fileName}_chunk_${index}`,
      }
    )
    uploadedFiles.push(uploadResult.file)
  }

  return {
    success: true,
    chunksIndexed: uploadedFiles.length,
    files: uploadedFiles.map(f => ({ name: f.name, displayName: f.displayName }))
  }
})

El endpoint de consulta es el núcleo del sistema RAG. Recibe la pregunta del usuario, recupera todos los archivos indexados disponibles y los inyecta como contexto en el prompt enviado a Gemini. La API File Search maneja internamente la selección de los fragmentos más relevantes mediante búsqueda semántica, lo que simplifica considerablemente la implementación en comparación con soluciones que requieren gestionar manualmente una base de datos vectorial como Pinecone o Qdrant.

// server/api/query.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { question } = body

  if (!question) {
    throw createError({ statusCode: 400, message: 'La pregunta es requerida' })
  }

  const { model, fileManager } = useGeminiClient()
  
  // Recuperar todos los archivos indexados
  const listResponse = await fileManager.listFiles()
  const files = listResponse.files || []

  if (files.length === 0) {
    return { answer: 'No hay documentos indexados. Por favor, sube contenido primero.' }
  }

  // Construir el prompt con referencias a los archivos indexados
  const fileParts = files.map(file => ({
    fileData: {
      mimeType: file.mimeType,
      fileUri: file.uri
    }
  }))

  const result = await model.generateContent([
    ...fileParts,
    {
      text: `Basándote exclusivamente en los documentos proporcionados, responde la siguiente pregunta de forma precisa y detallada. Si la información no está en los documentos, indícalo explícitamente.\n\nPregunta: ${question}`
    }
  ])

  return {
    answer: result.response.text(),
    sourcesCount: files.length
  }
})

Interfaz de usuario con Nuxt y Vue

La capa de presentación se construye como un componente Vue estándar dentro del directorio pages/ de Nuxt. La interfaz debe proporcionar dos funcionalidades principales: un área para cargar o pegar texto que será indexado, y un campo de consulta con visualización de respuestas. El uso de useFetch y $fetch de Nuxt simplifica las llamadas a los endpoints de servidor, manejando automáticamente la serialización y los estados de carga.

<!-- pages/index.vue -->
<template>
  <div class="container">
    <h1>Aplicación RAG con Nuxt + Gemini</h1>
    
    <!-- Sección de indexación -->
    <section class="indexing-section">
      <h2>Indexar Documento</h2>
      <input v-model="fileName" placeholder="Nombre del documento" />
      <textarea v-model="documentText" placeholder="Pega el texto a indexar..." rows="8" />
      <button @click="indexDocument" :disabled="isIndexing">
        {{ isIndexing ? 'Indexando...' : 'Indexar Documento' }}
      </button>
      <p v-if="indexResult">{{ indexResult }}</p>
    </section>

    <!-- Sección de consulta -->
    <section class="query-section">
      <h2>Realizar Consulta</h2>
      <input v-model="userQuestion" placeholder="¿Qué deseas saber?" />
      <button @click="submitQuery" :disabled="isQuerying">
        {{ isQuerying ? 'Consultando...' : 'Enviar Pregunta' }}
      </button>
      <div v-if="answer" class="answer-box">
        <h3>Respuesta:</h3>
        <p>{{ answer }}</p>
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
const fileName = ref('')
const documentText = ref('')
const userQuestion = ref('')
const answer = ref('')
const indexResult = ref('')
const isIndexing = ref(false)
const isQuerying = ref(false)

const indexDocument = async () => {
  isIndexing.value = true
  try {
    const response = await $fetch('/api/index-document', {
      method: 'POST',
      body: { text: documentText.value, fileName: fileName.value }
    })
    indexResult.value = `✓ Documento indexado en ${response.chunksIndexed} fragmentos`
  } catch (error) {
    indexResult.value = 'Error al indexar el documento'
  } finally {
    isIndexing.value = false
  }
}

const submitQuery = async () => {
  isQuerying.value = true
  try {
    const response = await $fetch('/api/query', {
      method: 'POST',
      body: { question: userQuestion.value }
    })
    answer.value = response.answer
  } catch (error) {
    answer.value = 'Error al procesar la consulta'
  } finally {
    isQuerying.value = false
  }
}
</script>

Consideraciones de arquitectura y escalabilidad

La implementación descrita representa un punto de partida sólido, pero existen aspectos críticos a considerar antes de llevarla a producción. La API Gemini File Search impone límites en el número de archivos almacenados y en la frecuencia de las solicitudes, por lo que en escenarios de alta concurrencia es necesario implementar estrategias de caché y cola de procesamiento. Asimismo, los archivos subidos a través de la File API tienen un período de retención limitado, lo que implica la necesidad de implementar lógica de reindexación periódica o utilizar almacenamiento persistente complementario.

Para aplicaciones con grandes volúmenes de documentos, considerar una arquitectura híbrida resulta ventajoso. En lugar de depender exclusivamente de la File API para la recuperación, se puede implementar una capa de pre-filtrado basada en metadatos (categoría, fecha, autor) antes de ejecutar la búsqueda semántica. Esto reduce significativamente el número de fragmentos evaluados y mejora tanto la velocidad como la precisión de las respuestas. A continuación se resumen las principales consideraciones arquitectónicas:

  • Gestión del ciclo de vida de archivos: implementar endpoints para eliminar archivos obsoletos y mantener el índice actualizado.
  • Límites de la API: monitorizar el uso de cuota y configurar reintentos con backoff exponencial.
  • Seguridad: autenticar los endpoints de indexación para evitar la subida no autorizada de contenido.
  • Observabilidad: registrar métricas de latencia, número de fragmentos recuperados y calidad percibida de las respuestas.
  • Estrategia de chunking: evaluar splitters semánticos que respeten límites de párrafo o sección en lugar de cortes arbitrarios por caracteres.

"Una arquitectura RAG bien diseñada no solo mejora la precisión de las respuestas generadas, sino que además proporciona trazabilidad sobre las fuentes utilizadas, un requisito indispensable en aplicaciones empresariales donde la auditoría y la explicabilidad son prioritarias."

Pruebas y validación del flujo completo

La validación de un sistema RAG va más allá de las pruebas unitarias convencionales. Es necesario evaluar la calidad de las respuestas mediante métricas específicas como la fidelidad (¿la respuesta se basa en los documentos proporcionados?), la relevancia de la respuesta (¿responde realmente a la pregunta formulada?) y la relevancia del contexto recuperado (¿los fragmentos seleccionados son pertinentes?). Frameworks como RAGAS ofrecen herramientas automatizadas para medir estas dimensiones, aunque una evaluación humana inicial sigue siendo insustituible para calibrar el sistema.

Para las pruebas de integración del proyecto Nuxt, se recomienda utilizar Vitest junto con utilidades de testing de Nuxt para simular las llamadas a los endpoints de servidor sin necesidad de conectarse a la API real de Gemini. La creación de mocks del cliente Gemini permite verificar que la lógica de fragmentación, el formato de los prompts y el manejo de errores funcionan correctamente de forma aislada. Esta práctica acelera el ciclo de desarrollo y reduce los costes asociados al consumo de la API durante las fases de prueba.

En conclusión, la combinación de Nuxt como framework full-stack y la API Gemini File Search de Google ofrece una ruta de implementación eficiente para sistemas RAG que requieren mínima infraestructura adicional. La arquitectura resultante es extensible, permitiendo incorporar progresivamente características avanzadas como re-ranking de resultados, generación de respuestas con streaming o integración con fuentes de datos externas mediante conectores personalizados. El prototipo descrito en este artículo constituye una base técnica robusta sobre la cual construir soluciones de inteligencia artificial contextual orientadas a dominios de conocimiento específicos.

2
Pylarion Pylarion

Pylarion

Desarrollo Web Expert

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

Blog / Desarrollo Web / Creación de una aplicación RAG 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
6
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
8
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
9
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