Главная / Статьи / RAG на Spring AI с векторным хранилищем внутри JVM: без Qdrant и Docker

RAG на Spring AI с векторным хранилищем внутри JVM: без Qdrant и Docker

Как заменить Qdrant на встроенную библиотеку Vectors в RAG-приложении на Spring AI: зависимость, флаг JVM, настройка сохранения на диск и компромиссы работы внутри JVM.

1003 слов

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, аргумент нужно добавить ещё и в конфигурацию запуска:

  1. Откройте Run → Edit Configurations.
  2. Выберите конфигурацию запуска вашего приложения.
  3. Найдите поле VM options. Если его не видно, нажмите Modify options и включите Add VM options.
  4. Впишите в поле --add-modules=jdk.incubator.vector.
  5. Нажмите Apply, затем OK.
  6. Запустите приложение как обычно.

При старте в логах должны появиться сообщения о том, что чанки документа загружены в векторное хранилище.

Почему больше ничего менять не нужно

Основную работу делает абстракция. При загрузке приложение режет свод правил на чанки и передаёт их тому бину 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 по-прежнему лучше. Но для помощника по документации, внутреннего бота по регламентам или прототипа убрать из развёртывания целый сервис часто очень выгодная сделка.