Русский
Расширение запроса можно отдать агенту
Что сделали: разобрали, как qmd расширяет запрос — сначала я понял это неправильно. Предложили отдать эту работу агенту, который и так подключён по MCP: он передаёт три необязательных поля вместе с запросом. Подобрали имена полей на трёх моделях и замерили прирост на 236 запросах: с сильным агентом nDCG@5 вырос на всех четырёх наборах, уверенно — на русском основном, с 0,603 до 0,702. Со слабым агентом (gpt-5.4-mini) прирост почти пропадает, и больше всего на него влияет описание одного поля. Читать, если у вас MCP-поиск и вы думаете, не добавить ли переписывание запросов.
Для статьи Как ищут другие я разбирал qmd — локальный поисковик по markdown-заметкам. Главная его особенность — расширение запроса: собственная модель Qwen3-1.7B, дообученная переписывать запрос в несколько вариантов. Я прочитал это и решил, что модель работает при индексации: заранее придумывает к каждой заметке, как её могли бы искать. Так устроены другие системы из той же статьи. Cerebras превращает треды Slack в карточки при записи. Hindsight при записи разбирает текст на факты.
У qmd всё наоборот. При индексации модели ничего не генерируют: заметки режутся на куски, для кусков считаются эмбеддинги. Модель работает при поиске, на каждый новый запрос.
Как это устроено
Вы вводите qmd query "как сделать внутренние ссылки между заметками". Дальше происходит вот что.
- Проба. Обычный полнотекстовый поиск по исходному запросу. Если первый результат явно лидирует, расширение пропускается: ответ и так очевиден.
- Расширение. Иначе модель получает промпт
Expand this search query: …и генерирует строки трёх типов:lex:— ключевые слова для полнотекстового поиска;vec:— перефразировка для векторного поиска;hyde:— выдуманный абзац, как мог бы выглядеть ответ в документе.
- Раздача. Каждый вариант идёт только в свой поиск, исходный запрос — в оба. Получается пять-восемь списков.
- Слияние через RRF. Списки исходного запроса весят вдвое больше.
- Реранкер Qwen3-Reranker-0.6B оценивает 40 лучших кандидатов.
- Кеш. Расширение и оценки реранкера сохраняются, так что повторный такой же запрос отвечает за доли секунды.
Вот что модель выдала в нашем прогоне. Английский запрос «how to create internal links between notes»:
vec: tips for linking notes within a document
vec: methods for integrating internal links in notes
hyde: The process of create internal links between notes involves several steps.
First, guide to adding links within note content. ...
Прилично. А вот русский «как упорядочить заметки с помощью тегов»:
lex: метод упорядочивания
lex: советы по
hyde: The topic of how to sort notes with tag ordering covers steps ... is the most
effective way to provide. is the most effective way to provide. is the most
effective way to provide. ...
Ключевое слово «советы по» найдёт что угодно. Выдуманный ответ написан по-английски на русский вопрос и к тому же зациклился. Это не случайность: модель дообучали на трёх с половиной тысячах примеров, и среди них нет ни одной строки на кириллице.
Сколько это стоит
Шаг 2 — генерация, токен за токеном, до 600 токенов. Шаг 5 — сорок полных проходов модели по паре «запрос плюс кусок текста до 900 токенов». Всё это повторяется на каждый новый запрос. На нашем сервере без видеокарты, который к тому же делили с другими задачами, один запрос qmd занимал от трёх до семи минут. На MacBook с M3 Max и Metal — около десяти секунд. В README obsidian-hybrid-search для qmd на Mac указано меньше секунды, но у нас так не получилось. А trip2g — сервер, и видеокарты у него обычно нет.
Где тут агент
Когда к базе знаний подключаются по MCP, по ту сторону сидит агент: Claude, GPT или другая большая модель. Она и так читает вопрос пользователя, знает контекст разговора и знает язык. Переписать запрос в два-три варианта для неё — пара строк ответа, которые ничего не стоят нашему серверу.
Сам qmd это предусмотрел. Его MCP-инструмент query принимает и строку, и готовый список:
{
"searches": [
{"type": "lex", "query": "теги упорядочивание заметок"},
{"type": "vec", "query": "как сортировать и группировать заметки по тегам"},
{"type": "hyde", "query": "Теги помогают находить и группировать заметки: добавьте #тег в текст или в свойство tags."}
],
"intent": "пользователь хочет организовать заметки тегами"
}
Если агент прислал searches, своя модель qmd не запускается вообще. Инструкция MCP-сервера прямо учит агента заполнять эти поля: что такое lex, что такое vec, зачем hyde, и всегда передавать intent. У obsidian-hybrid-search то же попроще: параметр queries[], несколько формулировок ищутся по отдельности и сливаются через RRF.
Получается, что маленькая модель в qmd — запасной вариант для тех, кто ищет из командной строки без агента. Когда агент есть, расширение лучше отдать ему.
Перекладывая расширение запроса на вызывающего агента, мы решаем сразу две задачи. Первая: trip2g не нужно тащить в себя генеративную модель, держать её в памяти и гонять на каждый запрос — на сервере без видеокарты это минуты. Вторая: варианты пишет модель, которая во много раз больше Qwen3-1.7B, понимает русский и знает, о чём идёт разговор. Дешевле — всегда. Лучше — если агент сильный: со слабым прирост почти пропадает, об этом ниже.
Как назвать поля
У qmd поля называются lex, vec и hyde — по технике поиска, а не по смыслу. Модель видит имя поля всегда, а описание — не всегда, поэтому мы проверили три набора имён на трёх моделях: gpt-5.4-mini, gpt-5.4-nano и claude-haiku-4.5. Каждой дали двадцать вопросов из нашего набора и смотрели на первый вызов поиска: заполнены ли поля и что в них лежит.
| Имена | mini | nano | haiku |
|---|---|---|---|
short_query, rephrased_query, expected_answer |
всё заполнено, 1 ошибка языка | всё заполнено, 2 ошибки языка | поиск вызван 18 раз из 20, третье поле — 1 раз |
short_query, rephrased_query, answer_excerpt |
всё заполнено, 1 ошибка языка | третье поле 18 из 20, 7 ошибок языка | третье поле — ни разу |
lex_query, vec_query, hyde_passage |
всё заполнено, 4 ошибки языка | третье поле 18 из 20, 2 ошибки языка | третье поле — 9 раз из 18 вызовов |
Выиграли понятные имена: short_query, rephrased_query, expected_answer. Жаргон qmd помогает только Haiku — она узнаёт HyDE по названию.
Первая версия описаний провалилась в неожиданном месте. Я написал для третьего поля «опиши, каким может быть ответ», и mini исправно выдавала «Документация должна объяснять, что такое Bases…». Для векторного поиска это бесполезно: в справке так не пишут. Помогло переписать описание наоборот: «одно-два предложения, которые могли бы стоять в самой заметке, утверждением, как пишется документация». Ещё одно: поле short_query сначала хотелось назвать keywords, но по такому имени модель пришлёт список через запятую, а полнотекстовому поиску нужны два-три слова, не больше.
Сколько это дало
Мы прогнали все 236 запросов из набора по справке Obsidian: основной и хитрый наборы, английский и русский. Варианты для каждого запроса писал отдельный агент на Claude Sonnet: ему дали только тексты вопросов и описания полей и запретили открывать справку и правильные ответы. Проверить это по файлам нельзя, но косвенно видно: на неочевидных запросах его догадки часто расходятся с правильными ответами — так пишет тот, кто ответа не знает. short_query шёл только в полнотекстовый поиск, два других поля — только в векторный. nDCG@5:
| Набор | trip2g | + только short_query |
+ все три поля |
|---|---|---|---|
| английский основной | 0,742 | 0,794 | 0,798 |
| английский хитрый | 0,767 | 0,820 | 0,815 |
| русский основной | 0,603 | 0,653 | 0,702 |
| русский хитрый | 0,815 | 0,824 | 0,866 |
Больше всего выросло там, где мы были слабее всего: на русском основном наборе — на десять пунктов. Это единственный прирост, который уверенно выше шума: бутстрап даёт 95-процентный интервал от +0,04 до +0,16. На английском основном и русском хитром интервал едва не задевает ноль, на английском хитром — задевает. На английском почти весь прирост даёт одно поле short_query: полнотекстовый поиск требует, чтобы совпали все слова, и два точных слова вместо длинной фразы снимают эту проблему. Recall@10 почти везде дошёл до 0,98–1,0: нужная страница оказывается в первой десятке почти всегда.
Для сравнения, qmd со своим расширением на тех же запросах дал 0,717, 0,733, 0,608 и 0,670, а с мультиязычными эмбеддингами Qwen3 — 0,731, 0,764, 0,668 и 0,771. На русском основном наборе этот вариант qmd в пределах шума от trip2g с полями. Но его запрос на M3 Max с Metal шёл около десяти секунд. Наши три поля стоят серверу два лишних эмбеддинга коротких фраз и один полнотекстовый запрос — миллисекунды.
А если агент слабее
Варианты выше писала сильная модель. Чтобы проверить, как поведёт себя агент попроще, мы дали gpt-5.4-mini тот же инструмент поиска и все 236 вопросов и записали, что она передаёт в первом вызове. Описания полей пробовали в трёх вариантах:
- v1 — описания из раздела выше;
- v2 — плюс правила, которыми qmd учил свою модель: не терять названия и технические токены, без служебных слов, не повторять вопрос, у каждого поля хороший и плохой пример;
- v3 —
short_queryпросит не «два-три слова из вопроса», а название функции или настройки, как в заголовке страницы справки: «Теги», «Холст», «Горячие клавиши».
| Набор | без полей | mini v1 | mini v2 | mini v3 | сильная модель |
|---|---|---|---|---|---|
| английский основной | 0,742 | 0,688 | 0,751 | 0,794 | 0,798 |
| английский хитрый | 0,767 | 0,823 | 0,801 | 0,777 | 0,815 |
| русский основной | 0,603 | 0,604 | 0,598 | 0,634 | 0,702 |
| русский хитрый | 0,815 | 0,810 | 0,818 | 0,796 | 0,866 |
Mini заполняла поля почти всегда, дело не в этом. Разница в том, что она туда кладёт. Сильная модель писала в short_query в среднем 1,7 слова, и почти всегда это было каноническое название функции — по сути, заголовок страницы: «теги», «publish», «встраивание файлов». Mini брала 2,3–2,9 слова из самого вопроса: «граф знаний», «встроить вики-ссылка». Полнотекстовый поиск требует, чтобы совпали все слова, и «встроить вики-ссылка» не находит ничего.
Правила qmd (v2) ничего не дали: правило «главный термин плюс уточнение» только удлинило short_query. Описание v3 подтянуло mini до сильной модели на английском основном наборе и дало три пункта на русском основном, зато на хитрых наборах, где много перефразировок и вопросов про глубокие разделы, оно проиграло v1.
Итог честный: со слабым агентом прирост от полей маленький и неустойчивый — от минус пяти до плюс пяти пунктов в зависимости от описания и набора, а это на границе шума. Уверенный прирост даёт сильный агент. Описание short_query важнее любых общих правил: одна фраза «название функции, а не слова из вопроса» сдвигает результат сильнее, чем весь набор правил из qmd.
Чего это не решает
- Поиск на сайте. Там запрос вводит человек, агента нет. Это улучшение только для MCP.
- Слабые агенты. Haiku почти не пишет третье поле, nano иногда пишет не на языке базы. Поля необязательные: без них поиск работает как раньше, с ними — лучше. Язык базы сервер подставляет прямо в описание поля.
- Часть запросов поля портят. На английском хитром наборе запросы, совпадающие с алиасом, просели с 0,81 до 0,72, запросы про глубокие разделы длинных страниц — с 0,97 до 0,89. На синтаксисе в английском основном — с 0,83 до 0,73, на длинных вопросах в русском хитром — с 0,87 до 0,70. Выигрыш сосредоточен в коротких запросах, перефразировках и вопросах «как сделать».
- Цена на нашей стороне. Каждый вариант — отдельный эмбеддинг. Дёшево, но не бесплатно.
- Слабый агент почти ничего не добавляет. С gpt-5.4-mini прирост колеблется от минус пяти до плюс пяти пунктов, см. раздел выше.
Мы уже видели, как сильно поведение дешёвых моделей зависит от инструкции: инструкции MCP ускоряют дешёвые модели. Здесь то же самое: имя поля и одна фраза в описании решают, получит ли поиск полезный вариант или пересказ того, «что должна объяснять документация».