dartyushin.techОткрыть меню
← к книге
глава 0814 минут

Product Evaluation: как измерить эффективность AI-подсказок

В предыдущих главах мы разобрали несколько AI-функций, которые помогают разработчику писать код. Autocompletion продолжает код рядом с кареткой. Next Edit Suggestions пытается предсказать следующее изменение. Inline Edit редактирует выбранный участок по инструкции. Commit Generation описывает подготовленный git diff.

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

контекст

LLM

подсказка

пользователь принимает, изменяет или отклоняет её

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

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

Поэтому качества модели недостаточно, чтобы оценить качество продукта.

Разберём, какие метрики можно использовать для AI-функций и почему простой доли принятых подсказок почти всегда оказывается мало.

Acceptance Rate

Начнём с самой очевидной метрики - Acceptance Rate, доли принятых подсказок:

[ AcceptanceRate = \frac{\text{количество принятых подсказок}} {\text{количество показанных подсказок}} ]

Представим, что пользователь реализует уже знакомое нам создание возврата:

import { returnService } from "@/services/returnService";

async function createReturn(request: ReturnRequest) {
    // cursor
}

Модель предлагает следующий код:

return await returnService.submit({
    orderId: request.orderId,
    reason: request.reason,
    requestedBy: request.userId,
});

Пользователь нажимает Tab. Подсказка принята, и событие попадает в статистику:

shown = true
accepted = true

Если за день система показала 1000 подсказок, из которых 300 были приняты, её Acceptance Rate составит 30%.

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

Например, система может предлагать только короткие и очевидные продолжения:

);

Такие подсказки часто принимают, но одно принятие экономит всего несколько нажатий клавиш. Другая модель может предлагать целые строки и блоки кода. Её Acceptance Rate будет ниже, хотя суммарно она напишет за пользователя больше.

Можно выбрать и другую стратегию: показывать подсказки только в тех случаях, когда модель почти уверена в ответе. Доля принятых результатов вырастет, но помогать система станет гораздо реже.

меньше показов

остаются только очевидные подсказки

Acceptance Rate растёт

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

Acceptance Rate описывает только один переход: что произошло сразу после показа. Он не говорит, насколько длинной была подсказка, сохранилась ли она в коде и помогла ли быстрее выполнить задачу.

Что считать показом

Даже знаменатель в формуле Acceptance Rate не так прост, как кажется.

Пользователь печатает быстро. Подсказка может успеть появиться в редакторе на несколько миллисекунд, после чего исчезнуть из-за следующего введённого символа. Технически система её отрисовала, но пользователь не успел ни прочитать результат, ни осознанно его отклонить.

Если считать каждый такой случай показом, Acceptance Rate будет зависеть не только от качества подсказок, но и от скорости набора текста, debounce, latency модели и особенностей UI.

При измерении code completion внутри Google подсказка считалась показанной, только если оставалась видимой более 750 миллисекунд. Для видимых таким образом подсказок измерялись acceptance rate, средняя длина принятого продолжения и доля кода, добавленного с помощью модели.

У другого продукта порог может отличаться, но сам принцип остаётся тем же:

Пользователь может отклонить только ту подсказку, которую он действительно успел увидеть.

Поэтому между генерацией и принятием появляется ещё один этап:

подсказка сгенерирована

прошла фильтры

была показана достаточно долго

пользователь мог принять решение

Запрос к модели может устареть, результат может быть отфильтрован, а пользователь может продолжить печатать до появления ответа. Все эти случаи полезны для оценки системы, но не должны автоматически считаться осознанным отказом от подсказки.

Принятая подсказка не обязательно осталась в коде

Вернёмся к нашему примеру. Пользователь принял предложенный вызов:

return await returnService.submit({
    orderId: request.orderId,
    reason: request.reason,
    requestedBy: request.userId,
});

Затем заметил, что поля userId в request не существует, и изменил код:

const userId = authService.requireUserId();

return await returnService.submit({
    orderId: request.orderId,
    reason: request.reason,
    requestedBy: userId,
});

Подсказка была полезной: модель правильно выбрала метод submit, подготовила структуру аргумента и заполнила два поля. Но первоначальный результат не сохранился целиком.

В другом случае пользователь может принять подсказку, запустить тесты, обнаружить ошибку и полностью удалить добавленный код. Для Acceptance Rate эти два случая выглядят одинаково:

accepted = true

Поэтому после принятия нужно продолжить наблюдение за подсказкой:

shown

accepted

edited

retained

committed

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

Например, для автодополнения можно измерять не только количество принятых подсказок, но и количество принятых и сохранившихся символов. Именно accepted-and-retained characters, наряду с Acceptance Rate, количеством показов и latency, GitHub использует при A/B-тестировании моделей Copilot. Эта метрика исключает код, который пользователь сначала принял, а затем удалил.

Получается более длинная воронка:

eligible

generated

shown

accepted

retained

shipped

На первом шаге возникает ситуация, в которой функция потенциально могла помочь. Затем система генерирует и показывает подсказку. Пользователь принимает её полностью или частично. Через некоторое время часть результата остаётся в коде, а затем попадает в commit или pull request.

Каждый этап отвечает на отдельный вопрос:

generated  → смогла ли система построить ответ
shown      → оказался ли ответ достаточно быстрым и допустимым
accepted   → показался ли ответ полезным пользователю
retained   → выдержал ли ответ последующую проверку
shipped    → стал ли результат частью итогового изменения

Offline-оценка

Собирать продуктовые метрики дорого. Для A/B-теста нужно выпустить новую версию реальным пользователям, дождаться достаточного количества событий и убедиться, что изменение статистически значимо.

Поэтому при разработке сначала используют offline evaluation: заранее подготовленный набор задач, на котором можно быстро сравнить модели, промпты и алгоритмы сбора контекста.

Простейший benchmark для code completion можно построить так:

берём готовый файл

удаляем из него строку или блок

передаём модели prefix и suffix

сравниваем ответ с удалённым кодом

Но точное совпадение с исходным фрагментом - слабая метрика. Одну задачу часто можно решить несколькими способами.

Например, исходный код использовал промежуточную переменную:

const result = await returnService.submit(command);
return result;

Модель могла предложить эквивалентный вариант:

return await returnService.submit(command);

Ответ не совпал с эталоном, хотя поведение программы осталось тем же.

Более надёжный способ - вставить предложенный код в проект и запустить тесты. Такой подход используется в RepoMasterEval - benchmark от исследователей ByteDance для оценки repository-level code completion. Из реальных Python- и TypeScript-репозиториев удаляются фрагменты кода, модель восстанавливает их с учётом контекста проекта, а корректность проверяется существующими и дополнительно усиленными тестами.

Особенно важен не сам способ построения benchmark, а то, как авторы проверили его полезность. В течение месяца они сравнивали изменения offline pass rate с изменениями acceptance rate реального продукта. Улучшения на RepoMasterEval положительно коррелировали с улучшениями в production. При этом результаты на более общих задачах вроде HumanEval не всегда отражали качество модели в реальном code completion.

Это даёт важное требование к offline-оценке:

Хорошая offline-метрика должна предсказывать изменение продуктовых метрик.

Высокий результат на популярном benchmark сам по себе не означает, что пользователи будут чаще принимать подсказки. Benchmark должен воспроизводить контекст, ограничения и типичные задачи конкретной функции.

Для code completion это может быть код вокруг каретки и контекст репозитория. Для Inline Edit - инструкция, выделенный участок и остальная часть файла. Для NES - последовательность предыдущих изменений. Для Commit Generation - реальный git diff и дополнительные данные, доступные продукту.

Offline и online evaluation

Offline- и online-оценки решают разные задачи.

Offline evaluation позволяет быстро проверять большое количество вариантов:

модель A или модель B
другой системный промпт
новый способ retrieval
другой context budget
новый post-processing

Но она работает на заранее собранном наборе примеров. В нём нет реального пользователя, который спешит, отвлекается, меняет намерение, игнорирует UI или не доверяет слишком длинной подсказке.

Online evaluation показывает поведение функции в настоящем рабочем процессе, но проводить такой эксперимент для каждого изменения слишком долго и рискованно.

Поэтому эти подходы обычно соединяются в один цикл:

offline benchmark

внутреннее тестирование

A/B-тест

анализ принятых и отклонённых подсказок

новые примеры для offline benchmark

Offline-оценка отбрасывает заведомо неудачные решения. Внутреннее тестирование помогает найти заметные проблемы с UX и поведением модели. A/B-тест проверяет влияние на реальных пользователей. Неудачные production-примеры затем возвращаются в benchmark.

Так offline-набор постепенно начинает отражать реальные проблемы продукта, а не абстрактные способности модели.

Code Completion

Для code completion основной единицей взаимодействия является продолжение кода рядом с кареткой.

Offline можно измерять:

exact и partial match с исходным продолжением; синтаксическую корректность; возможность собрать проект; прохождение тестов; корректность используемых API; latency генерации.

Latency здесь является частью качества. Даже правильная подсказка бесполезна, если пользователь уже написал код самостоятельно. Sourcegraph при разработке Cody рассматривал задержку как одно из главных ограничений всего completion-пайплайна: retrieval, генерация и обработка должны укладываться во время между действиями пользователя.

Online можно измерять:

shown rate
acceptance rate
partial acceptance rate
accepted characters
accepted lines
retained characters
undo rate
p50 / p95 latency

Количество принятых символов позволяет отличить полезный блок кода от принятой закрывающей скобки. Retention отделяет код, который действительно пригодился, от результата, удалённого сразу после принятия.

В качестве основной продуктовой метрики можно использовать, например:

[ RetainedCharactersRate = \frac{\text{принятые и сохранившиеся символы}} {\text{количество символов, введённых пользователем}} ]

Но использовать её отдельно тоже опасно. Модель может начать предлагать слишком длинные продолжения и увеличить количество сгенерированного кода ценой лишних ошибок и времени на проверку.

Поэтому рядом должны оставаться guardrail-метрики:

latency
hide rate
undo rate
доля некомпилируемых подсказок
количество показов

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

Next Edit Suggestions

В code completion место следующей подсказки уже известно: она появляется рядом с кареткой.

Для Next Edit Suggestions системе нужно решить сразу три задачи:

когда предложить изменение
+
где предложить изменение
+
какой код предложить

Поэтому offline-оценку можно разделить на несколько частей.

Сначала проверяется позиция:

совпал ли файл
совпал ли диапазон
каково расстояние до реального изменения

Затем оценивается сам edit:

exact match
partial match
применимость patch
компиляция
прохождение тестов

Отдельным правильным результатом должно быть отсутствие предложения. После некоторых действий пользователя никакого очевидного следующего изменения нет. Модель, которая всегда пытается что-то исправить, может иметь высокий coverage, но быстро начнёт раздражать.

Эта особенность хорошо видна в A/B-тестах GitHub Copilot NES. В одном из обновлений количество показанных подсказок уменьшилось на 24,5%, но Acceptance Rate вырос на 26,5%, а Hide Rate снизился на 25,6%. Функция стала вмешиваться реже, но её оставшиеся предложения оказались уместнее.

меньше подсказок
    +
выше Acceptance Rate
    +
ниже Hide Rate
    =
более точный выбор момента

Для NES количество показов само по себе не является положительной метрикой. Система должна не максимизировать активность, а выбирать моменты, когда вероятность полезного следующего изменения достаточно высока.

Online можно измерять:

shown rate
acceptance rate
hide rate
переход к предложенной позиции
partial edit acceptance
retained edits
stale suggestions
latency

Если NES сначала перемещает пользователя к другой части файла, а затем предлагает edit, полезно разделять принятие позиции и принятие самого изменения. Пользователь может согласиться, что редактировать нужно именно это место, но написать код самостоятельно.

Inline Edit

В Inline Edit пользователь сам вызывает функцию, выбирает код и формулирует инструкцию:

выделенный код
+
инструкция

предложенный patch

Здесь почти не нужно оценивать правильность момента показа: пользователь явно запросил изменение. Зато возрастает важность понимания инструкции и соблюдения границ редактирования.

Представим запрос:

Получи userId через authService и используй его в requestedBy

Модель может корректно изменить основную функцию, но заодно переименовать аргументы, переформатировать соседние методы или удалить комментарий. Тесты продолжат проходить, хотя patch содержит незапрошенные изменения.

Поэтому offline-проверка должна учитывать не только итоговое поведение:

выполнена ли инструкция
проходят ли тесты
изменён ли только нужный участок
не потерян ли существующий код
не появились ли побочные изменения

Benchmark тоже должен воспроизводить реальный интерфейс функции. В EDIT-Bench задачи собраны из настоящих пользовательских запросов к инструменту редактирования. Модель получает не только инструкцию, но и текущий код, выделенный участок и позицию каретки. Изменение доступного контекста влияло на результат моделей вплоть до 11 процентных пунктов.

Если в продукте пользователь выделяет код, а offline benchmark передаёт модели только текстовую инструкцию, мы оцениваем уже другую функцию.

Online для Inline Edit можно измерять:

доля принятых patches
partial acceptance
regeneration rate
follow-up instructions
undo / revert rate
время до принятого результата
retention после commit

Полезной метрикой становится расстояние между сгенерированным patch и итоговым кодом пользователя:

исходный код

AI patch

ручные исправления

финальный код

Чем меньше изменений потребовалось внести после генерации, тем большую часть работы выполнила система.

Однако edit distance тоже не описывает всё качество. Пользователь может принять большой patch без изменений, но потратить несколько минут на его проверку. Поэтому для длинных Inline Edit важны время до подтверждения, количество просмотренных hunks и результаты последующих тестов.

Commit Generation

Commit Generation отличается от предыдущих функций тем, что генерирует не код, а текстовое описание изменений.

Для одного git diff может существовать несколько хороших сообщений:

feat: add return submission
feat(returns): create returns through returnService
Add authenticated return creation

Сравнивать такой результат с единственным эталоном через точное совпадение бессмысленно. Метрики вроде BLEU или ROUGE тоже могут поставить низкую оценку хорошему сообщению только потому, что оно использует другие слова.

При этом Acceptance Rate для commit message трудно определить однозначно. Пользователь может нажать кнопку генерации, получить сообщение, изменить одно слово и создать commit. Это не полное принятие, но генерация явно была полезной.

JetBrains предложил использовать в качестве online-метрики количество изменений, внесённых пользователем между сгенерированным и реально закоммиченным сообщением. Затем исследователи проверили, какие offline-метрики лучше всего коррелируют с таким поведением. На их наборе данных edit distance показал наиболее высокую корреляцию, тогда как BLEU, METEOR, ROUGE и другие популярные метрики коррелировали с online-качеством слабо.

Для Commit Generation можно собирать следующие online-метрики:

commit без исправлений
количество добавленных символов
количество удалённых символов
normalized edit distance
regeneration rate
полная замена сообщения
отказ от генерации
время до создания commit

Но небольшое количество правок ещё не гарантирует правильность текста. Пользователь может не заметить, что сообщение описывает изменение, которого не было в diff.

Поэтому offline evaluation должна дополнительно проверять:

соответствие сообщения реальным изменениям; отсутствие выдуманных фактов; наличие основной сути изменения; соблюдение формата команды; корректность type и scope; отсутствие лишних технических деталей.

Для этого можно использовать человеческую разметку, LLM-as-a-Judge с фиксированным набором критериев или структурированные проверки отдельных полей. Главное, чтобы offline-метрика была сопоставлена с реальными пользовательскими исправлениями.

От подсказки к продуктивности

Даже retained-код ещё не означает, что разработчик стал работать быстрее.

Модель могла написать десять строк, но пользователь потратил две минуты на их чтение. Или подсказка позволила быстрее закончить функцию, но добавила ошибку, которую обнаружили на code review.

Можно представить полезность AI-функции как разницу между сэкономленной работой и стоимостью взаимодействия:

Product Value =
    saved work
    - review cost
    - correction cost
    - interruption cost
    - latency cost

Точно посчитать каждую часть сложно, поэтому продукт использует наблюдаемые приближения:

saved work        → retained characters, edit distance
review cost       → время до принятия, размер patch
correction cost   → undo, follow-up edits, regeneration
interruption cost → hide rate, dismiss rate
latency cost      → время от действия до показа

Следующий уровень - метрики рабочего процесса:

время выполнения задачи
количество итераций build / test
время до успешной сборки
время до commit или pull request
количество исправлений после review

Например, Google при внедрении ML-based code completion измерял не только Acceptance Rate. В эксперименте с более чем десятью тысячами разработчиков функция имела Acceptance Rate 25-34%, создавала более 3% кода и сокращала среднюю продолжительность coding iteration примерно на 6%.

Это уже связывает взаимодействие с подсказкой и изменение реального рабочего процесса:

подсказки принимают

часть кода остаётся

между сборками проходит меньше времени

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

Что измерять для разных функций

Для всех рассмотренных функций можно сохранить общую схему:

eligible
→ generated
→ shown
→ accepted
→ retained
→ shipped

Но основная единица пользы будет отличаться:

Code Completion
→ принятые и сохранившиеся символы

Next Edit Suggestions
→ принятые и сохранившиеся edits
  с учётом правильности позиции

Inline Edit
→ расстояние от AI patch
  до финального пользовательского patch

Commit Generation
→ расстояние от сгенерированного сообщения
  до реально созданного commit message

Рядом с основной метрикой должны находиться ограничения:

Code Completion
→ latency, undo rate, compilation errors

Next Edit Suggestions
→ shown rate, hide rate, stale suggestions

Inline Edit
→ scope violations, regeneration, test failures

Commit Generation
→ factual errors, policy violations, abandonment

Одной универсальной метрики для всех функций нет. Но есть общий принцип: измерять нужно не только первый ответ модели, а весь путь результата через продукт.

Как построить оценку функции

Практический процесс можно разделить на несколько шагов.

Сначала определяем ожидаемую пользу:

какую работу функция должна экономить?

Для completion это ручной набор кода. Для NES - поиск следующего места и внесение связанного изменения. Для Inline Edit - самостоятельное преобразование существующего кода. Для Commit Generation - написание сообщения.

Затем строим продуктовую воронку и собираем события:

invoked / eligible
generated
shown
accepted
edited
retained
committed

После этого выбираем основную метрику и guardrails. Например:

основная метрика:
accepted-and-retained characters

guardrails:
latency
hide rate
undo rate
ошибки компиляции

Следующим шагом создаём offline benchmark, который воспроизводит реальный вход функции и проверяет те свойства, которые важны пользователю.

Наконец, связываем offline и online evaluation. Если рост benchmark score не приводит к улучшению production-метрик, значит benchmark измеряет не совсем ту задачу, которую решает продукт.

Получается замкнутый цикл:

пользовательские взаимодействия

продуктовые метрики

анализ ошибок

offline benchmark

новая версия системы

A/B-тест

В предыдущих главах мы рассматривали, как улучшить подсказку: добавить контекст, найти определения через LSP, выбрать релевантные фрагменты, изменить промпт или использовать другую модель.

Теперь появляется способ проверить, действительно ли это улучшение помогло.

Хорошая AI-функция - не та, чьи ответы получают самый высокий score на абстрактном benchmark. И даже не обязательно та, чьи подсказки чаще принимают.

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

Источники

  1. RepoMasterEval: Evaluating Code Completion via Real-World Repositories - Qinyun Wu et al., 2024. Benchmark ByteDance для repository-level completion и исследование корреляции offline pass rate с production acceptance rate.
  2. The lifecycle of a code AI completion - Sourcegraph, 2023. Описание жизненного цикла Cody Autocomplete и роли latency, retrieval, генерации и A/B-тестирования.
  3. ML-Enhanced Code Completion Improves Developer Productivity - Google, 2022. Результаты внедрения code completion для более чем десяти тысяч разработчиков.
  4. The road to better completions: Building a faster, smarter GitHub Copilot with a new custom model - GitHub, 2025. Использование accepted-and-retained characters, acceptance, shown rate и latency для оценки completion.
  5. Evolving GitHub Copilot’s next edit suggestions through custom model training - GitHub, 2025. A/B-метрики для NES: shown rate, acceptance rate и hide rate.
  6. EDIT-Bench: Evaluating LLM Abilities to Perform Real-World Instructed Code Edits - Wayne Chi et al., 2025. Benchmark Inline Edit, собранный из реальных пользовательских инструкций и контекстов.
  7. Towards Realistic Evaluation of Commit Message Generation by Matching Online and Offline Settings - Petr Tsvetkov et al., 2024. Исследование JetBrains о связи offline-метрик с пользовательскими исправлениями commit messages.