QMD — поиск по Markdown

Периодически AI-комьюнити (в самом широком смысле) подвергается нашествию моды. В какой-то момент весь твиттер начинает писать про одно и то же решение или функцию, все советуют одно и то же и все от этого в восторге. Я не имею в виду типичный мусор вида “Recently {smth} released new feature and this changes everything…” — нет, речь о вполне интересных вещах, как, например, OpenClaw.

Вот так в очередной раз донеслась мода на qmd. Это проект CEO Shopify Тоби Люке, который выпустил его несколько месяцев назад — минипоисковик по локальным файлам (в основном markdown, не проверял, что он еще поддерживает), который работает в командной строке и может использоваться AI-агентами. Уже есть и плагины, например, к Obsidian. У меня много документов в Markdown, да и Obsidian использую, поэтому решил попробовать наконец.

Технически это RAG, только без чат-обертки. Остальные компоненты присутствуют — файлы режутся на чанки, индексируются для поиска по ключевым словам, преобразуются в вектор, всё это хранится в SQLite базе и дальше в зависимости от оператора запроса либо делается поиск по ключевым словам, либо по эмбеддингам, либо гибридный поиск, то есть собираются результаты по ключевым словам и эмбеддингам, переранжируются совместно и выдается результат.

Движок при первом использовании скачивает необходимые модели для эмбеддингов, переранжирования и расширения ключевых слов, это порядка 2-2,5 гигабайт.

В общем, всё знакомо и понятно, но, если честно, пользоваться этим сложно.

Поиск по ключевым словам быстрый и хороший. Правда, он не уникальный — даже для Obsidian есть несколько плагинов, решающих эту задачу и решающих хорошо. Но для CLI это хорошая штука, агенты смогут использовать.

Поиск по эмбеддингам меня начал удивлять — во-первых, для того, чтобы эти эмбеддинги создать, движок загрузил модель и распух в памяти до 28 гигабайт. Если в первой итерации я ему подсунул около 10 тысяч документов, то потом решил тестировать более щадяще и дал только 300 — не помогло, свои 28 гигабайт он кушал в любом случае. Я честно не понимаю, зачем ему столько — для эмбеддингов используется модель с 600М параметров, ей даже с контекстом не нужно больше пары гигабайт памяти. 28 гигабайт — это больше половины вообще всей памяти на моем MacBook Pro и системе становится тяжело, то есть в фоне эту индексацию не проделаешь. Я решил потерпеть, все же индексация делается один раз. Но при поиске он опять распух до 28 гигабайт, впрочем, после ответа память освободил.

Во-вторых, поиск по эмбеддингам — это поиск по похожести. Он ловит переформулировки, но я знаком с главной проблемой такого поиска. Дело в том, что если вы задаете вопросы другими терминами, чем те, которые использованы в релевантных документах, то шансы что-то найти невелики. Я сталкивался с этим, когда запустил первую версию чатбота для бухгалтеров — вопросы, заданные естественным языком, получали ответы на основании готовых консультаций, а очень релевантные результаты из законов и кодексов в результаты поиска не попадали.

ОК, я запустил гибридный поиск. Движок привычно занял 28 гигабайт оперативной памяти и начал работу. Да, это классический RAG, я и сам так подошел в разработке — взять запрос пользователя, выделить ключевые слова, добавить синонимы, поискать по нескольким запросам, преобразовать их в векторы, поискать все векторы, все это переранжировать и выдать результат.

55 секунд на один небольшой запрос по очень небольшой коллекции. И, поскольку я такое делал, тут ничего особо не ускоришь — много операций, много транзакционных расходов (времени). Плюс к тому это все сделано на Typescript и node.js, что скорости не добавляет. К примеру, мой бот прокручивает на порядок больше информации — ищет в обоих поисках по 50 документов, результат (100 документов) отдает во внешнюю LLM для переранжирования, забирает Top25, отдает с большим промптом во внешнюю LLM для формулирования ответа и 95% ответов укладываются в 53 секунды.

Вполне допускаю, что без холодного старта скорость ответа будет лучше, но вряд ли кардинально. Также допускаю, что при наличии необходимости искать по очень большой базе и делать это программно — то есть дать такой поиск агенту, — результат будет лучше, чем просто поиск по вхождению, а увеличение времени не так критично. Агент и так работает долго, много времени занимает генерация, какая разница, если это время немного увеличится.

Но для не такой уж большой базы документов, пожалуй, это излишне. Оставлю пока как замену grep, но только в режиме поиска по ключевым словам — эмбеддинги и переранжирования подождут.