Read in:
Русский

Три месяца бенчмарков, а модель так и не прочитала ни одной заметки

Что это: в октябре мы задали моделям Claude вопросы по базе документации так, как их задают её пользователи: через Claude Code и к базе в том виде, в каком она развёрнута. Сломано оказалось пять вещей. Каждую мог поймать один из бенчмарков, которые мы уже опубликовали. Здесь — что было сломано, почему ни один из них этого не увидел и что изменилось после починки.

Опубликованное число остаётся верным ровно для того пути, который им мерили. Это мы писали в перемере нагрузки про скорость. Для ответов это тоже верно.

Прогон

База — документация продукта на русском, около 85 заметок, по MCP. Клиент — Claude Code CLI, подключён только этот сервер, модели haiku и sonnet. Одиннадцать вопросов: четыре с прямым ответом в базе, три о том, чего в базе нет (правильный ответ — «не описано», а не «нет»), один, на который база прямо говорит «нет», одно сообщение с тремя вопросами, один технический вопрос и одна задача на код, к базе отношения не имеющая. Каждый вопрос по два раза на вариант, каждый ответ вслепую оценивал отдельный судья на Opus: опирается ли на базу, честно ли про отсутствие, сохранены ли оговорки, сначала ли короткий ответ, простой ли язык, отвечено ли на каждый подвопрос.

Начинали с маленького вопроса: как базе передать модели правила ответа? Чтобы на него ответить, пришлось читать, что модели делали на самом деле, вызов за вызовом. Там дефекты и нашлись.

Что было сломано

Модель не прочитала ни одной заметки. note_html отдавал заметку в текстовом блоке, а с появлением вложений, за день до прогона, — ещё и структурированный результат, где были только путь и список вложений. Claude Code передаёт модели структурированный результат вместо текстового блока. Модель, открывшая заметку в Claude Code, получала {"note_path": "AGENTS.md", "assets": []}. Тело пришло в 0 вызовах из 30. Модели говорили пользователю, что заметка пустая, и отвечали по сниппетам поиска. Все тесты инструментов проходили: каждый проверял текстовый блок.

Инструкции пропадали на новом протоколе. Claude Code теперь договаривается по MCP 2026-07-28, где вместо initialize идёт server/discover. trip2g собирал имя сервера и инструкции только для initialize, и текущий клиент получал пустое имя и никаких инструкций. Наша сентябрьская статья про инструкции тоже шла через Claude Code, но до того, как клиент сменил протокол. Её числа верны для того клиента, который она мерила.

Каждое подключение шло 25 секунд и потом обрывалось. Сервер объявляет, что список его инструментов может меняться, и клиент сразу открывает subscriptions/listen. Эндпоинт собирал весь ответ целиком, прежде чем его отдать, а подписка не кончается, поэтому запрос висел, пока клиент не сдавался через 25 секунд. Потом клиент отменял его запросом без заголовка Mcp-Method, сервер такой отклонял, и клиент записывал обрыв соединения. После переподключения инструменты работали, поэтому снаружи ничего не выглядело сломанным.

Полнотекстовый поиск на большинство запросов отвечал пустотой. Текстовый поиск trip2g требует, чтобы совпали все слова запроса. В июле мы пробовали ослабить это до ИЛИ и откатили: при включённом векторном поиске полноту даёт векторная часть, у текстовой работа — точность, и ИЛИ забивало её случайными совпадениями. Это по-прежнему так. Но эта база была развёрнута без embedder, и, кроме текстового поиска, у неё ничего не было. Модели пишут длинные запросы, и 58% из 344 разных запросов вернулись пустыми. Нужная страница попадала в первые шесть у 34% тех вопросов, на которые в базе есть ответ.

Поля запроса так и не выпустили. Сентябрьская статья измерила три необязательных поля, которые агент передаёт вместе с поиском, — short_query, rephrased_query и expected_answer — и получила +0.10 nDCG@5 на русском наборе. Дополнительные списки в том замере сводил скрипт снаружи сервера. У инструмента search на сервере остались query, limit и detail_limit. Статья читается так, будто поля есть. Их не было.

До и после

Средняя оценка из 12, слепой судья, 22 ответа на ячейку. «Правила» — это короткий initialize, описанный ниже, плюс правила ответа в заметке, которую модель открывает первой.

haiku sonnet
без правил 2.9 4.4
все правила в initialize, до починок 6.8 10.6
короткий initialize + заметка с правилами, до починок 5.2 8.2
короткий initialize + заметка с правилами, после починок 10.3 11.5
до после
подключение 25 с, потом обрыв медиана 39 мс
тело заметки с правилами дошло до модели 0 из 30 при каждой загрузке
модель считает «агента» собой (sonnet) 2 из 20 0 из 20

На пустых поисках мы поставили один опыт: И, а ИЛИ — только если И ничего не нашло. Пустых поисков стало 9% вместо 59%, оценки не сдвинулись, и на трёх вопросах, на которые базе нечего ответить, ни одна модель ничего не выдумала по случайным совпадениям; они просто перестали переспрашивать. Выпускать это мы не стали. Базе без векторов нужны векторы, и именно такую конфигурацию мерил июльский бенчмарк. Поля запроса сейчас делаются.

Что ответил вопрос про инструкции

У вопроса, с которого мы начали, тоже есть ответ, и короткий. Клиент кладёт инструкции initialize в системный промпт всей сессии, поэтому они действуют на каждом ходу, в том числе на задачу про код. Держите в них то, что должно действовать всегда: о чём база; что значит слово, которое модель может принять на свой счёт («агент здесь — сотрудник-ИИ, которого настраивает заказчик, а не ты»); что фактов о предмете нет в данных обучения модели, поэтому она ищет, даже если уверена; и где лежат правила. Тон, длину и списки проверок — в заметку, которую модель открывает через note_html. Подробности — в руководстве по MCP. Строка с персоной («отвечай, как ответил бы редактор, но не копируй его») не изменила ничего измеримого, и ни в одном варианте правила не протекли в ответ про код.

Почему пять дефектов пережили четыре проверки

Каждая проверка мерила один слой и подменяла собой остальные.

  • Июльский бенчмарк поиска мерил движок скриптом, с включёнными векторами. Развёртывание, которое он должен был описывать, работало без них.
  • Сентябрьский бенчмарк полей мерил идею скриптом, который собирал списки снаружи сервера. Сервер так и не изменили.
  • Сентябрьский бенчмарк инструкций шёл через настоящий клиент — до того, как клиент перешёл на новый протокол.
  • Тесты инструментов проверяли, что отправил сервер, а не что клиент отдал модели.

Ни одна не гоняла развёрнутую конфигурацию через текущий клиент с моделью, которая читает ответы. В сентябре мы писали, что читать надо шаги ассистента, а не его ответ. Пустые поиски с тех пор были в каждом логе каждого прогона. Логи никто не читал, потому что чтение ничем не было назначено.

Что меняется

  • Бенчмарк, который это нашёл, идёт через тот клиент, которым пользуется человек, к базе в развёрнутом виде, и перезапускается, когда обновляется клиент, а не только когда меняем сервер.
  • Рядом с каждой оценкой — два числа, которые поймали бы четыре дефекта из пяти в первый же день: доля поисков, вернувшихся пустыми, и доля чтений заметки, тело которой дошло до модели.
  • Статья, которая мерит предложение, так и говорит — и говорит, когда оно выпущено.

Открыто: хорошо ли ищет база с векторами — мерим после деплоя; поля запроса; и subscriptions/listen, на который сервер теперь отвечает отказом, но по-прежнему объявляет, что список инструментов меняется, так что клиент несколько раз за сессию пробует снова.