Read in:
Русский

Что агенты на самом деле делают с полями поиска

Что это: в сентябре мы измерили три необязательных поля, которые агент передаёт вместе с поиском, — short_query, rephrased_query и expected_answer, — но заполняла их сильная модель в скрипте. В октябре мы их выпустили и посмотрели, что с ними делают Claude Haiku и Claude Sonnet через Claude Code. Четыре опыта, около 1 500 прогонов агентов. Коротко: поля работают, когда их хорошо заполняют; агенты в основном заполняют плохо; если их подталкивать заполнять больше, становится хуже; а одно простое число в результате поиска изменило поведение сильнее любой формулировки.

Как мерили

Три базы:

  • русская документация продукта, 100 заметок, 42 вопроса с размеченной правильной страницей;
  • русский базовый набор бенчмарка Obsidian Help, 58 вопросов;
  • синтетическое английское руководство к выдуманному продукту: 12 длинных заметок (4 000–10 000 слов, по 15–35 разделов) и 40 коротких, 56 вопросов с одним проверяемым ответом, большая часть — глубоко внутри длинной заметки.

Агенты работали через Claude Code CLI, подключён был только MCP-сервер базы, векторный поиск включён (bge-m3). Для оценки поиска мы записывали первый вызов search и прогоняли его по серверу заново с разными полями, считая MRR@10 и nDCG@5 по разметке. Каждое сравнение парное, с bootstrap-интервалом 95%; на 40–60 вопросах разница что-то значит, только начиная примерно с ±0.1 MRR.

Как агенты заполняют поля

Первый вызов search на базе документации:

Кто пишет short_query rephrased_query expected_answer переписал query
Haiku 89% 86% 14% 72%
Sonnet 91% 53% 0% 15%
сильная модель, видит только вопрос и описания 100% 100% 100% —

Sonnet ни разу не написал expected_answer. Haiku написала его в одном случае из семи и почти в трёх случаях из четырёх переписала query, хотя описание просит этого не делать.

Как меняется MRR@10, если добавить к запросу каждое поле (интервал 95%):

Кто пишет + short_query + rephrased_query + expected_answer все три
Haiku +0.01 +0.05 +0.01 +0.05
Sonnet +0.09 0.00 — +0.04
сильная модель +0.10 +0.04 +0.12 [+0.03, +0.21] +0.14 [+0.01, +0.27]

Выше шума только expected_answer от сильной модели и все три поля вместе. На этой базе механизм работает примерно так же, как в сентябре на Obsidian Help (там было +0.10). Ограничивает его агент: поле, которое помогает больше всего, настоящие агенты пропускают.

rephrased_query никому не помог выше шума, а у Sonnet поверх short_query сделал хуже 4 вопроса против 1 лучше. Мы его оставили: в qmd, по образцу которого сделаны поля, такое поле есть, а 42 вопроса не доказывают «бесполезно» так же, как не доказывают «полезно».

ИЛИ для short_query

Полнотекстовый поиск trip2g требует всех слов запроса. short_query — это два-три слова, поэтому ИЛИ там казалось безопасным: длинный query сохраняет И ради точности, короткое поле получает ИЛИ ради полноты. MRR@10 для query + short_query:

Кто пишет И ИЛИ И, а если пусто — ИЛИ
Haiku 0.521 0.595 (+0.07 [+0.01, +0.16]) 0.537
Sonnet 0.654 0.559 (−0.09) 0.630
сильная модель 0.663 0.593 (−0.07) 0.656

ИЛИ спасает неудачные слова Haiku и топит точные слова остальных. В этой базе слово, которым называется главная сущность продукта, есть почти на каждой странице, поэтому short_query: "копия агента" через ИЛИ возвращает почти случайный список, а слияние по рангу даёт ему тот же вес, что и точным спискам. Это июльский результат, только на уровне поля. Запасной вариант почти не срабатывает: из двух-трёх слов И редко возвращает пусто. Оставили И.

Просить агентов заполнять поля

Раз полезное поле не заполняют, можно попросить. Пять формулировок, на базе документации и Obsidian Help, Haiku и Sonnet, 1 000 прогонов:

  • V0: выпущенный текст («Optional. …»);
  • V1: expected_answer помечено «Recommended», с «ответ знать не обязательно, это поле никто не видит»;
  • V2: «You SHOULD fill this whenever you understand the question» на всех трёх полях и «заполняй все три при каждом поиске» в описании инструмента;
  • V3: V2 плюс «если не уверен, да или нет, не утверждай ни то ни другое»;
  • V4: только фраза в описании инструмента.

В V2, V3 и V4 доля вызовов со всеми тремя полями выросла на 44–51 процентный пункт; у Sonnet expected_answer на базе документации — с 0% до 100%. Одна фраза в описании инструмента (V4) сработала так же, как переписывание каждого поля.

Поиск не сдвинулся. Общая разница MRR с V0 — от +0.001 до +0.027 при интервале 95% около ±0.045, в каждом варианте. А expected_answer начал врать: на вопросах, где правда — «нет», заполненное поле утверждало «да» в 30–40% вызовов («откройте карточку агента и выберите „Клонировать“» для функции, которой нет). Строка-защита в V3 этого не остановила. Там, где неверная догадка встречалась с правильной страницей, она выталкивала её из первой десятки.

Описания мы оставили как есть. Агент, которого подталкивают угадывать, угадывает, а догадка, сказанная как факт, для поиска хуже, чем никакой.

Показать длину заметки

Раньше результат поиска ничего не говорил о размере заметки, и агент до открытия не мог отличить руководство на 9 000 слов от абзаца. Мы добавили в каждый результат два числа: size_tokens — ту же оценку, которой пользуется нарезка на куски, и sections — сколько заголовков может пройти expand. Потом синтетическое руководство, три варианта: S0 без чисел, S1 с ними, S2 с ними и фразой в описании инструмента («заметку больше ~2 000 токенов с несколькими разделами не читай целиком: прочитай найденный раздел или сначала вызови expand»).

Вопросы о длинных заметках, по два прогона:

верно прочитал длинную целиком прочитал по разделу получил от инструментов
Haiku S0 90% 0.76 на вопрос 0.24 30 тыс. символов
Haiku S1 83% 0.36 0.68 16 тыс.
Haiku S2 79% 0.12 1.02 12 тыс.
Sonnet S0 88% 0.49 0.43 20 тыс.
Sonnet S1 90% 0.04 0.92 6 тыс.
Sonnet S2 88% 0.02 1.20 7 тыс.

Сами два числа сократили чтение длинных заметок целиком и объём того, что вернули инструменты, вдвое-втрое у обеих моделей, выше шума. Прогоны Sonnet стали примерно вдвое дешевле при той же точности. Дополнительная фраза Sonnet ничего не добавила, а Haiku стоила около десяти пунктов точности: ей сказали читать по разделам, и она читала не тот раздел. На вопросах о коротких заметках ничего не изменилось.

Числа мы выпустили, фразу — нет.

Что из этого следует

  • Узкое место — агент, а не сервер. Каждое поле работает, когда его хорошо заполнили. Настоящие агенты заполняют дешёвые поля, пропускают полезное и переписывают запрос, который их просили не трогать.
  • Формулировка меняет поведение, а не результат. Одна фраза сдвинула долю заполнения на пятьдесят пунктов, поиск — ни на что измеримое, и заставила агента утверждать то, чего он не знал.
  • Факт лучше указания. size_tokens — это число, а не совет, и обе модели правильно им воспользовались, хотя им не говорили, что с ним делать. Указание сверху сделало слабую модель хуже.
  • Мерить надо тем агентом, который будет этим пользоваться. Сентябрьские числа были верны для сильной модели, заполняющей поля в скрипте. В руках агентов, которыми пользуются люди, большей части этой прибавки нет.

Предыдущая часть этой работы — дефекты, поверх починки которых делались эти прогоны, — в статье Три месяца бенчмарков, а модель так и не прочитала ни одной заметки.