28 октября 2026 года Роберт Силен выложил на mariadb.org разбор учебного приложения: Laravel 13 ищет ответы по своим markdown-файлам в MariaDB. Туториал написал Мохамед Тухами Райма, участник сообщества, который доводил векторные запросы MariaDB до ядра Laravel. На странице есть и видео на семь минут, шаги те же.
В том же месяце, 7 октября, на mariadb.org уже зафиксировали, с какой версии это лежит в самом фреймворке. С Laravel 13.28 колонка VECTOR, векторный индекс и поиск похожих строк работают на MariaDB 11.7 и новее, без второй базы и без отдельного пакета. В той заметке рекомендуют 11.8 LTS или 12.3 LTS. В документации Laravel AI SDK, на которую ссылается эта заметка, векторные запросы описаны для PostgreSQL с расширением pgvector и для MariaDB 11.7 и новее, у которого тип VECTOR встроенный.
Учебный репозиторий читает markdown, режет файлы на куски, превращает каждый кусок в числовой вектор (эмбеддинг), кладёт его в MariaDB и отвечает на вопрос. Сначала находит ближайшие куски, потом отдаёт их агенту, который пишет текст ответа. В конце есть команда ask и небольшой HTTP-эндпоинт. У ответа указан файл, из которого взят кусок. Код, на который ссылается туториал: github.com/Rhaima96/laravel-mariadb-vector.
На Laravel 12 этого в ядре нет. Там остаётся пакет сообщества devilsberg/laravel-mariadb-vector Эрика Роса. Имена методов похожи, но не совпадают, так что на 12 смотрят его README, а не миграцию из туториала для 13. В заметке от 7 октября у пакета около 2 000 установок: спрос был ещё до того, как поддержку влили в фреймворк.
Драйвер mariadb и колонка на 1536
Для прогона нужны PHP 8.3 или новее, Composer, Docker и ключ провайдера эмбеддингов. Проверяли на чистой установке: PHP 8.4, Composer 2.10, Docker 29. Файл compose поднимает MariaDB 12.3 и публикует порт 3308, чтобы не задеть сервер, который уже слушает 3306. На VPS, где сайт на WordPress ходит в свою MariaDB, учебное приложение как раз про отдельный экземпляр. База в примере vectordemo, пользователь vector, пароль secret. Это пароль из учебного compose. Файл .env с ключом API в git не кладут.
Дальше одна настройка, из-за которой векторный поиск либо есть, либо его нет. Laravel подключается к MariaDB и драйвером mysql, и драйвером mariadb. Соединение при драйвере mysql выглядит живым. Векторный индекс при этом не создаётся, запросы дистанции отключены, а uuid() становится char(36), а не родным типом MariaDB. В примере стоит DB_CONNECTION=mariadb, хост 127.0.0.1, порт 3308.
Таблица в миграции одна, documents. В строке имя файла, текст куска и вектор embedding на 1536 чисел. 1536 это длина выхода OpenAI text-embedding-3-small. MariaDB отвергает вектор другой длины уже на вставке. Сменили модель, сменили число в vector('embedding', 1536) и прогнали миграцию заново.
Каст AsVector на модели Document читает байты вектора MariaDB как обычный массив float в PHP и пишет массив обратно через vec_fromtext(). Сырой SQL для упаковки не нужен. В миграции колонка и индекс это $table->vector() и $table->vectorIndex(). В запросе дистанция и фильтр это selectVectorDistance() и whereVectorSimilarTo(). Текст в вектор превращает Str::of($text)->toEmbeddings() из Laravel AI SDK.
В пример миграции в документации AI SDK попал вызов Schema::ensureVectorExtensionExists(). На MariaDB он падает с текстом Extensions are only supported by Postgres. Это помощник для pgvector. У MariaDB расширение не нужно, строку из миграции убирают.
Индекс cosine и четыре ближайших куска
vectorIndex() в этом приложении строит индекс HNSW с косинусной дистанцией. В комментарии к миграции указаны M=6 и DISTANCE=cosine. Меньшее число в колонке distance значит более близкий кусок: на MariaDB запрос идёт через VEC_DISTANCE_COSINE. В заметке от 7 октября отдельно сказано, что настраиваемый M и евклидова дистанция лежат в пакете rhaima96/laravel-vector-metrics, а в ядре Laravel для MariaDB остаётся cosine. Индекс MariaDB берётся только у запроса с той же функцией дистанции, что у индекса, по возрастанию и с LIMIT.
Три ограничения туториал называет прямо. Колонка под индексом должна быть NOT NULL, nullable() к ней не добавляют. Векторный индекс у таблицы один. И индекс отдаёт строки только в том порядке, под который он построен.
В методе search лимит по умолчанию 4, порог minSimilarity равен 0,25. selectVectorDistance() добавляет колонку distance. whereVectorSimilarTo() оставляет достаточно похожие куски и сортирует от ближнего к дальнему. Следствие лимита такое: индекс сначала возвращает не больше четырёх ближайших, и порог режет уже эту четвёрку. Метод может вернуть меньше четырёх кусков и не возвращает больше.
Вопрос в вектор считают один раз и тот же массив подставляют и в дистанцию, и в фильтр. Если отдать в whereVectorSimilarTo обычную строку, SDK посчитает эмбеддинг сам, но колонке distance вектор тоже нужен, и вопрос уйдёт провайдеру дважды.
Команды ingest и ask, эндпоинт /api/ask
В каталоге content/ три коротких markdown-файла: про эмбеддинги, про MariaDB Vector и про драйвер mariadb. Команда php artisan ingest читает файлы, режет текст и пишет строки. Режут по пустым строкам и склеивают абзацы примерно до 600 символов, чтобы кусок редко обрывал предложение посередине. Перед стартом таблица очищается: файлы можно поправить и прогнать команду снова. Каждый прогон зовёт провайдера эмбеддингов по разу на кусок. На трёх коротких файлах это дёшево. В конце команда печатает, сколько кусков легло в таблицу.
Режут не для красоты. Вопрос обычно закрывает один абзац, не весь файл. Короткий кусок и для поиска точнее, и в запрос к модели уходит меньше лишнего текста.
Команда ask сначала печатает совпадения, потом ответ. Учебный вопрос, почему не стоит ходить в MariaDB драйвером mysql, даёт четыре ближайших куска: три из laravel-driver.md и один из mariadb-vector.md, дистанции от 0,33 до 0,60.
php artisan ask "why should I not use the mysql driver with MariaDB?"
Дальше класс DocsAgent. В инструкцию ему кладут найденные куски, каждый под именем файла, и просят отвечать только из этого текста, называть файл и прямо сказать, если ответа в кусках нет. Вызов один: prompt($question)->text. На том же вопросе агент объясняет, что драйвер mysql с MariaDB может выглядеть рабочим и ломается на векторных запросах и индексах, и заканчивает строкой Source: laravel-driver.md. Дословная формулировка от прогона к прогону меняется.
Документы остаются в вашей базе. В чат-модель уходят вопрос и только совпавшие куски. Текст, который считают при ingest, всё равно получает провайдер эмбеддингов. База находит фрагменты, модель пишет ответ по ним.
Тот же поиск можно выставить как POST /api/ask. В bootstrap/app.php включают api-маршруты, в routes/api.php вешают маршрут с throttle:10,1, десять запросов в минуту. Контроллер ждёт поле question, строку не длиннее 500 символов. Пустое поле даёт 422. Если ничего не похоже достаточно, ответ 404. Иначе JSON: поле answer и список sources, у каждого источника имя файла и distance, округлённая до четырёх знаков.
Эндпоинт в примере без аутентификации. Каждый вызов тратит токены эмбеддинга и чата. Десять запросов в минуту режут частоту, счёт при этом не обнуляется. На публичный адрес такое вешают уже с проверкой входа. В .env.example стоит APP_DEBUG=true. Страница ошибки в этом режиме показывает полный стек. На рабочем приложении ставят false.
Отдельно про базу, в которую это поедет. php artisan migrate:fresh из раздела про Ollama удаляет все таблицы подключённой базы. Для vectordemo в учебном compose это ожидаемо. Для базы, где лежит WordPress, это снос сайта. Compose как раз отделяет контуры: docker compose up -d поднимает MariaDB 12.3 на порту 3308 и не трогает экземпляр на 3306.
Своя MariaDB, которой пользуется сайт, может быть старше 11.7. Тогда колонки VECTOR на ней нет, и миграция с vector() на этом сервере не заведётся. Туториал ставит нужную ветку контейнером, а не пакетом дистрибутива. Версию того сервера, который уже обслуживает сайт, смотрят до любых миграций.
mariadb --version
Ollama: 768 чисел и порог 0,45
Эмбеддинги можно посчитать локально, чтобы текст документов при ingest не уходил с машины. В туториале это Ollama и модель nomic-embed-text: 768 чисел, проверено на свежем клоне с MariaDB 12.3. Локальным становится только эмбеддинг. Агент, который пишет ответ, по-прежнему берёт чат-провайдера из config/ai.php. Пока его не сменили, это OpenAI, и в момент вопроса совпавшие куски вместе с вопросом уходят туда. Локальные чат-модели туториал не разбирает.
Команды установки в тексте даны для macOS через Homebrew. Для остальных систем автор отсылает на страницу установки ollama.com. Проверка, которую он приводит, печатает длину вектора: запрос к localhost:11434 с моделью nomic-embed-text должен дать 768. Если Ollama на другой машине, адрес задают переменной OLLAMA_URL.
Дальше три правки в проекте. После php artisan vendor:publish --tag=ai-config в config/ai.php ставят default_for_embeddings в ollama. SDK по умолчанию ходит на http://localhost:11434 и берёт nomic-embed-text. В миграции 1536 меняют на 768. Потом php artisan migrate:fresh и снова ingest. Векторы разных моделей сравнивать нельзя: в таблице должны лежать числа одной модели. Чужую длину MariaDB не примет на вставке. Одну и ту же длину от двух моделей тоже нельзя мешать, дистанция между такими векторами ни о чём не говорит. Таблицу пересобирают и заливают заново.
Порог при смене модели тоже другой. У OpenAI text-embedding-3-small подходящие куски на учебном вопросе лежали между 0,33 и 0,60. minSimilarity 0,25 означает максимальную дистанцию 0,75 и отделяет их от постороннего текста. У nomic-embed-text тот же вопрос попал в 0,23-0,38, а посторонний вопрос про рецепт шоколадного торта всё равно нашёл куски с дистанцией 0,64-0,66. При старом пороге эти куски проходят, и API не отвечает 404.
В Document::search() порог по умолчанию поднимают до 0,45. Это максимальная дистанция 0,55. Команда ask принимает то же значение флагом --similarity=0.45. После этого учебный вопрос возвращает четыре куска с дистанциями от 0,23 до 0,38, вопрос про торт получает 404. Число 0,45 подобрано под эту маленькую выборку и эту модель. Туториал предлагает прогнать несколько вопросов, на которые документы точно отвечают и точно не отвечают, посмотреть дистанции, которые печатает ask и поставить порог между двумя группами.
В свой проект, не в клон репозитория, SDK ставят командой composer require laravel/ai. Качество поиска сильнее зависит от модели эмбеддингов, чем от самой колонки. Заметка от 7 октября отсылает к июньскому разбору Эрика Роса: на 2 942 названиях должностей доля верных первых результатов гуляла от 14% почти до 60% из-за модели и того, как её спрашивают. Число в vector() это контракт с этой моделью. Пока Laravel на сервере 12-й, ядро вектор MariaDB не отдаст. Пока MariaDB старше 11.7, сначала сервер из веток, которые туториал называет рабочими, и только потом миграция. Демо /api/ask без входа на тот же хост, где открыт сайт, публиковать не стоит.