RAG на Spring AI з векторним сховищем усередині JVM: без Qdrant і Docker
Як замінити Qdrant на вбудовану бібліотеку Vectors у RAG-застосунку на Spring AI: залежність, прапорець JVM, налаштування збереження на диск і компроміси роботи всередині JVM.
Retrieval-Augmented Generation зовсім не обов'язково означає окрему векторну базу, яку треба запускати й обслуговувати. Застосунок на Spring AI може тримати векторний індекс усередині JVM, у тому самому процесі, що й усе інше. Розберімо, як це налаштувати.
Чого бракує варіанту в пам'яті
Spring AI працює з багатьма векторними сховищами, серед них Chroma, Pinecone, PostgreSQL з pgVector і Qdrant. У ньому є й вбудований клас SimpleVectorStore. Він годиться для демо й тестів, але не для продакшену. Справжнього векторного пошуку він не виконує: на кожен запит перебирає всі документи по одному. Метадані документів він теж не підтримує, тож відфільтрувати результати за метаданими поверх векторної схожості не вийде.
Цю прогалину закриває нова бібліотека Vectors. Це повноцінний векторний індекс, що цілком живе всередині JVM, підтримує метадані й уміє ефективно шукати за схожістю. Семантичний пошук у вас є, а розгортати й обслуговувати ще одну базу не потрібно.
План такий: взяти RAG-застосунок, який зараз використовує Qdrant, і замінити сховище на Vectors, щоб увесь застосунок працював без зовнішніх сервісів.
Змінюємо залежність
Спершу додайте стартер Vectors:
implementation 'com.integrallis:vectors-spring-boot-starter:0.1.4'
Версія має бути 0.1.4 або новіша. У ранніх релізах не було автоконфігурації, і бін VectorStore доводилося створювати самому, разом із біном VectorCollection. Починаючи з 0.1.4 автоконфігурація створює обидва сама.
Далі приберіть три речі: залежність Qdrant, залежність підтримки Docker Compose і сам compose.yaml. Нічого з цього більше не потрібно: Vectors працює всередині застосунку, і жоден сервер запускати чи контролювати не треба.
Решта застосунку лишається як була. Spring AI ховає сховище за абстракцією VectorStore, тому код завантаження в load() працює так само, а QuestionAnswerAdvisor, налаштований на ChatClient, редагувати не потрібно.
Один прапорець JVM перед першим запуском
Vectors спирається на Java Vector API, який у JDK досі має статус інкубатора, тому цей модуль треба ввімкнути. Для Gradle додайте до build.gradle таке:
tasks.named('bootRun') {
jvmArgs('--add-modules', 'jdk.incubator.vector')
}
Це покриває запуски через Gradle, наприклад ./gradlew bootRun.
Якщо ви запускаєте з IntelliJ IDEA, аргумент треба додати ще й у конфігурацію запуску:
- Відкрийте Run → Edit Configurations.
- Оберіть конфігурацію запуску вашого застосунку.
- Знайдіть поле VM options. Якщо його не видно, натисніть Modify options і ввімкніть Add VM options.
- Впишіть у поле
--add-modules=jdk.incubator.vector. - Натисніть Apply, потім OK.
- Запустіть застосунок як зазвичай.
Під час старту в логах мають з'явитися повідомлення про те, що чанки документа завантажено у векторне сховище.
Чому більше нічого змінювати не треба
Основну роботу робить абстракція. Під час завантаження застосунок ріже звід правил на чанки й передає їх тому біну VectorStore, який є. Під час запитання QuestionAnswerAdvisor запитує в того самого біна чанки, найближчі до питання користувача, і додає їх до промпту, перш ніж відповість модель. Жоден із кроків не знає і не переймається тим, чи ходить бін мережею до контейнера Qdrant, чи до індексу, що лежить за кілька об'єктів від нього в тій самій купі. Саме тому вся заміна зводиться до зміни залежності й одного прапорця JVM.
Поставте кілька запитань
Візьміть як вихідний документ правила настільної гри Sagrada (ті самі, що використовувалися з Qdrant) і запитайте, наприклад:
- Як називається гра?
- Скільки гравців може брати участь?
- Скільки кубиків кидають у кожному раунді?
Відповіді мають спиратися на правила, як і з Qdrant. Різниця лише в тому, що тепер усе відбувається всередині JVM.
Збереження індексу між запусками
Vectors має більше можливостей, ніж задіює базове налаштування вище, і документацію варто прочитати. Найбільше застосункам знадобиться збереження на диск: вектори переживають перезапуск, і щоразу завантажувати їх заново не потрібно. Вмикається це однією властивістю:
java-vectors.storage-path=/tmp/vectors/rag-recipe
Шлях до сховища можна обрати будь-який. Після цього сховище зберігає вміст від запуску до запуску, і документи не треба завантажувати під час кожного старту. Один наслідок: після першого запуску приберіть метод load() з RagIngestionConfig, інакше кожен перезапуск додаватиме дублікати того самого документа.
Коли це підходить
Для застосунків, яким не потрібна окрема векторна база, Vectors — зручна золота середина. Справжній векторний пошук, підтримка метаданих і збереження на диск за бажанням, і все це всередині власної JVM застосунку. Якщо вам потрібен RAG у застосунку на Spring AI без ще одного сервісу, який треба розгортати й підтримувати, на неї варто подивитися.
Компроміси, які варто зважити
Індекс усередині процесу має витрати, про які легко забути. Вектори займають пам'ять у тій самій JVM, що обслуговує запити, тож великий корпус конкурує за купу з рештою застосунку. З увімкненим збереженням дані лежать на диску конкретної машини й прив'язані до одного екземпляра застосунку. Якщо кільком екземплярам потрібен спільний індекс або корпус значно більший, ніж варто тримати в одній JVM, окрема база на кшталт Qdrant і далі краща. Але для помічника з документації, внутрішнього бота з регламентів чи прототипу прибрати з розгортання цілий сервіс часто дуже вигідна угода.