RAG con Spring AI y un vector store dentro de la JVM: sin Qdrant ni Docker
Sustituya Qdrant por la librería Vectors, que se ejecuta en el mismo proceso, en una app RAG con Spring AI: dependencia, flag de la JVM, persistencia y los compromisos de trabajar dentro de la JVM.
Retrieval-Augmented Generation no significa necesariamente tener que ejecutar y mantener una base de datos vectorial aparte. Una aplicación con Spring AI puede guardar su índice vectorial dentro de la JVM, en el mismo proceso que todo lo demás. Veamos cómo configurarlo.
Dónde se queda corta la opción en memoria
Spring AI funciona con muchos vector stores, entre ellos Chroma, Pinecone, PostgreSQL con pgVector y Qdrant. También incluye una clase propia, SimpleVectorStore. Esa clase sirve para demos y pruebas, pero no para producción. No hace una búsqueda vectorial real: en cada consulta recorre todos los documentos uno por uno. Tampoco admite metadatos de documentos, así que no hay forma de acotar los resultados por metadatos además de por similitud vectorial.
Una librería más reciente llamada Vectors cubre ese hueco. Es un índice vectorial de verdad que vive por completo dentro de la JVM, con soporte de metadatos y búsqueda por similitud eficiente. Obtiene búsqueda semántica sin desplegar ni operar otra base de datos.
El plan es tomar una aplicación RAG que ahora usa Qdrant y cambiar el almacén por Vectors, de modo que toda la app funcione sin ningún servicio externo.
Cambiar la dependencia
Empiece por añadir el starter de Vectors:
implementation 'com.integrallis:vectors-spring-boot-starter:0.1.4'
La versión tiene que ser 0.1.4 o posterior. Las versiones anteriores no tenían autoconfiguración, así que había que crear uno mismo el bean VectorStore, junto con un bean VectorCollection. A partir de la 0.1.4, la autoconfiguración crea ambos por usted.
Después, elimine tres cosas: la dependencia de Qdrant, la dependencia de soporte para Docker Compose y el propio compose.yaml. Ya no hace falta nada de eso, porque Vectors se ejecuta dentro de la aplicación y no hay ningún servidor que arrancar ni vigilar.
El resto de la app se queda como estaba. Como Spring AI oculta el almacén detrás de la abstracción VectorStore, el código de ingesta en load() funciona exactamente igual que antes, y el QuestionAnswerAdvisor configurado en el ChatClient no necesita cambios.
Un flag de la JVM antes de la primera ejecución
Vectors se apoya en la Java Vector API, que sigue siendo una funcionalidad en incubación del JDK, así que hay que activar ese módulo. En Gradle, añada esto a build.gradle:
tasks.named('bootRun') {
jvmArgs('--add-modules', 'jdk.incubator.vector')
}
Eso cubre las ejecuciones lanzadas con Gradle, como ./gradlew bootRun.
Si lanza la aplicación desde IntelliJ IDEA, también tendrá que añadir el argumento a la configuración de ejecución:
- Vaya a Run → Edit Configurations.
- Elija la configuración de ejecución de su aplicación.
- Busque el campo VM options. Si no aparece, haga clic en Modify options y active Add VM options.
- Escriba
--add-modules=jdk.incubator.vectoren el campo. - Haga clic en Apply y luego en OK.
- Arranque la aplicación como siempre.
Al arrancar, los mensajes del log deberían confirmar que los chunks del documento se han cargado en el vector store.
Por qué no hay que cambiar nada más
La abstracción hace el trabajo pesado. Durante la ingesta, la app divide el reglamento en chunks y se los pasa al bean VectorStore que exista. Cuando llega una pregunta, el QuestionAnswerAdvisor le pide a ese mismo bean los chunks más cercanos a la pregunta del usuario y los añade al prompt antes de que responda el modelo. Ninguno de los dos pasos sabe ni le importa si el bean habla por red con un contenedor de Qdrant o con un índice que está a unos pocos objetos de distancia en el mismo heap. Por eso todo el cambio se reduce a una dependencia nueva más un flag de la JVM.
Pruebe algunas preguntas
Con el reglamento del juego de mesa Sagrada como documento fuente (el mismo que se usó con Qdrant), pregunte cosas como estas:
- ¿Cómo se llama el juego?
- ¿Cuántos jugadores pueden participar?
- ¿Cuántos dados se tiran en cada ronda?
Las respuestas deberían basarse en el reglamento, igual que con Qdrant. La diferencia es que ahora todo ocurre dentro de la JVM.
Guardar el índice entre ejecuciones
Vectors tiene más funciones de las que usa la configuración básica de arriba, y merece la pena leer su documentación. La persistencia es la que querrán la mayoría de las apps, para que los vectores sobrevivan a un reinicio y no haya que volver a cargarlos cada vez. Activarla requiere una sola propiedad:
java-vectors.storage-path=/tmp/vectors/rag-recipe
Puede elegir cualquier directorio como ruta de almacenamiento. A partir de ahí, el almacén conserva su contenido de una ejecución a otra y no hace falta volver a ingerir los documentos en cada arranque. Una consecuencia: después de la primera ejecución, quite el método load() de RagIngestionConfig, o cada reinicio añadirá copias duplicadas del mismo documento.
Cuándo encaja
Para aplicaciones que no necesitan una base de datos vectorial independiente, Vectors ocupa un término medio muy práctico. Ofrece búsqueda vectorial real, soporte de metadatos y persistencia opcional, todo dentro de la propia JVM de la aplicación. Quien quiera RAG en una app con Spring AI sin añadir otro servicio más que desplegar y mantener debería echarle un vistazo.
Compromisos que conviene sopesar
Tener el índice en el mismo proceso tiene costes fáciles de olvidar. Los vectores ocupan memoria en la misma JVM que atiende las peticiones, así que un corpus grande compite por el heap con el resto de la aplicación. Con la persistencia activada, los datos quedan en el disco de esa máquina, lo que los ata a una sola instancia de la aplicación. Si varias instancias necesitan compartir un índice, o el corpus es mucho mayor de lo que debería guardar una sola JVM, una base de datos independiente como Qdrant sigue siendo mejor opción. Pero para un asistente de documentación, un bot interno de normativas o un prototipo, quitar un servicio entero del despliegue suele ser un intercambio muy rentable.