Your AI Product Needs Evals
Хамель Хусейн, независимый консультант (ранее руководивший командой CodeSearchNet, предшественника GitHub Copilot), утверждает, что главная причина провала LLM-продуктов — отсутствие надёжных систем оценки (evals). Успех зависит от скорости итераций, которая опирается на три процесса: оценку качества, отладку и изменение поведения системы. На примере Lucy — ИИ-ассистента риелторской SaaS-платформы Rechat — описаны три уровня оценки: юнит-тесты (Level 1, у Rechat их сотни), оценка человеком и моделью с логированием трейсов через LangSmith (Level 2) и A/B-тестирование (Level 3). Автор подчёркивает необходимость постоянно смотреть на данные, строить собственные инструменты просмотра, использовать LLM для генерации тестов и синтетических данных, а также измерять precision и recall при согласовании модели-судьи с человеком. Готовая система оценки бесплатно открывает возможности fine-tuning и быстрой отладки, создавая маховик для стремительных итераций.
Мотивация
Я начал работать с языковыми моделями пять лет назад, когда возглавлял команду, создавшую CodeSearchNet — предшественника GitHub CoPilot. С тех пор я видел множество успешных и неудачных подходов к созданию LLM-продуктов. Я обнаружил, что у неудачных продуктов почти всегда есть общая первопричина: неспособность создать надёжные системы оценки.
Сейчас я независимый консультант, помогающий компаниям создавать предметно-ориентированные ИИ-продукты. Надеюсь, что, внимательно прочитав этот пост, компании смогут сэкономить тысячи долларов на консалтинговых услугах. Как бы я ни любил зарабатывать деньги, мне ненавистно видеть, как люди раз за разом совершают одну и ту же ошибку.
В этом посте я излагаю свои мысли о построении систем оценки для ИИ-продуктов на базе LLM.
Быстрые итерации == Успех
Как и в разработке ПО, успех в ИИ зависит от того, насколько быстро вы можете итерировать. У вас должны быть процессы и инструменты для:
Оценки качества (напр.: тесты). Отладки проблем (напр.: логирование и просмотр данных). Изменения поведения или системы (промпт-инжиниринг, fine-tuning, написание кода)
Многие фокусируются исключительно на пункте №3 выше, что мешает им вывести свои LLM-продукты дальше уровня демо.1 Качественное выполнение всех трёх видов деятельности создаёт благотворный цикл, отличающий отличные ИИ-продукты от посредственных (визуализацию этого цикла см. на диаграмме ниже).
Если вы оптимизируете процесс оценки, все остальные виды деятельности становятся простыми. Это очень похоже на то, как тесты в разработке ПО приносят колоссальную отдачу в долгосрочной перспективе, несмотря на необходимость предварительных вложений.
Чтобы привязать этот пост к реальной ситуации, я разберу кейс, в котором мы построили систему для быстрого улучшения. В основном я сосредоточусь на оценке, поскольку это наиболее критичный компонент.
Кейс: Lucy, ИИ-ассистент для недвижимости
Rechat — это SaaS-приложение, позволяющее специалистам по недвижимости выполнять различные задачи: управлять договорами, искать объекты, создавать креативные материалы, управлять встречами и многое другое. Идея Rechat в том, что можно делать всё в одном месте, а не переключаться между множеством разных инструментов.
ИИ-ассистент Rechat, Lucy — это канонический ИИ-продукт: разговорный интерфейс, избавляющий от необходимости кликать, печатать и перемещаться по ПО. На начальных этапах Lucy быстрый прогресс достигался с помощью промпт-инжиниринга. Однако по мере расширения охвата Lucy производительность ИИ вышла на плато. Симптомами этого были:
Устранение одного режима отказа приводило к появлению других — словно игра в «ударь крота». Видимость эффективности ИИ-системы по разным задачам была ограниченной и не выходила за рамки субъективных «прикидок на ощущение». Промпты разрастались в длинные и громоздкие формы, пытаясь охватить многочисленные крайние случаи и примеры.
Проблема: как систематически улучшать ИИ?
Чтобы пробить это плато, мы создали систематический подход к улучшению Lucy, сосредоточенный на оценке. Наш подход проиллюстрирован на диаграмме ниже.
Эта диаграмма — добросовестная попытка проиллюстрировать мою ментальную модель улучшения ИИ-систем. В реальности процесс нелинеен и может принимать множество разных форм, которые могут и не походить на эту диаграмму.
Ниже я разбираю различные компоненты этой системы в контексте оценки.
Типы оценки
Строгая и систематическая оценка — важнейшая часть всей системы. Именно поэтому «Оценка и курирование» выделены жёлтым в центре диаграммы. Большую часть времени вам следует тратить на то, чтобы сделать вашу оценку более надёжной и оптимизированной.
Стоит рассматривать три уровня оценки:
Уровень 1: Юнит-тесты Уровень 2: Оценка моделью и человеком (включая отладку) Уровень 3: A/B-тестирование
Стоимость Уровня 3 > Уровня 2 > Уровня 1. Это определяет частоту и манеру их выполнения. Например, я часто запускаю оценки Уровня 1 при каждом изменении кода, Уровня 2 — с заданной периодичностью, а Уровня 3 — только после значительных изменений продукта. Также полезно покрыть значительную часть ваших тестов Уровня 1, прежде чем переходить к тестам на основе модели, поскольку последние требуют больше работы и времени на выполнение.
Не существует строгой формулы, когда вводить каждый уровень тестирования. Нужно балансировать между быстрым получением обратной связи от пользователей, управлением восприятием пользователей и целями вашего ИИ-продукта. Это не слишком отличается от того балансирования, которое приходится выполнять для продуктов в целом.
Уровень 1: Юнит-тесты
Юнит-тесты для LLM — это утверждения (assertions), подобные тем, что вы писали бы в pytest. В отличие от типичных юнит-тестов, эти утверждения стоит организовать для использования и за пределами юнит-тестов — например, для очистки данных и автоматических повторных попыток (используя ошибку утверждения для корректировки курса) во время инференса модели. Важно, чтобы эти утверждения выполнялись быстро и дёшево по мере разработки приложения, чтобы вы могли запускать их при каждом изменении кода. Если вам трудно придумать утверждения, стоит критически изучить свои трейсы и режимы отказа. Также не стесняйтесь использовать LLM, чтобы помочь себе в мозговом штурме утверждений!
Шаг 1: Пишите узконаправленные тесты
Наиболее эффективный способ мыслить о юнит-тестах — разбить охват вашего LLM на функции (features) и сценарии. Например, одна из функций Lucy — способность находить объекты недвижимости, которую мы можем разбить на сценарии так:
Функция: Поиск объявлений
Тестируемая функция — это вызов функции, реагирующий на запрос пользователя найти объявление о недвижимости. Например: «Пожалуйста, найди объявления с более чем 3 спальнями дешевле $2M в Сан-Хосе, Калифорния»
LLM преобразует это в запрос, который выполняется по CRM. Затем утверждение проверяет, что возвращается ожидаемое количество результатов. В нашем наборе тестов есть три пользовательских ввода, которые запускают каждый из сценариев ниже, после чего выполняются соответствующие утверждения (это упрощённый пример для иллюстрации):
Есть также общие тесты, не привязанные к какой-либо одной функции. Например, вот код одного такого общего теста, который гарантирует, что UUID не упоминается в выводе:
const noExposedUUID = message => { // Remove all text within double curly braces const sanitizedComment = message.comment.replace(/\{\{.*?\}\}/g, '') // Search for exposed UUIDs const regexp = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ig const matches = Array.from(sanitizedComment.matchAll(regexp)) expect(matches.length, 'Exposed UUIDs').to.equal(0, 'Exposed UUIDs found') }
Результаты CRM, возвращаемые LLM, содержат поля, которые не должны показываться пользователю — например, UUID, связанный с записью. Наш промпт для LLM указывает модели не включать UUID. Мы используем простое регулярное выражение, чтобы убедиться, что ответ LLM не содержит UUID.
У Rechat сотни таких юнит-тестов. Мы постоянно обновляем их на основе новых отказов, которые наблюдаем в данных, по мере того как пользователи бросают вызов ИИ или продукт развивается. Эти юнит-тесты крайне важны для быстрого получения обратной связи при итерациях над вашей ИИ-системой (промпт-инжиниринг, улучшение RAG и т. д.). Многие со временем перерастают свои юнит-тесты и переходят к другим уровням оценки по мере зрелости продукта, но крайне важно не пропускать этот шаг!
Шаг 2: Создавайте тестовые случаи
Чтобы протестировать эти утверждения, нужно сгенерировать тестовые случаи или входные данные, которые запустят все сценарии, которые вы хотите проверить. Я часто использую LLM для синтетической генерации таких входов; например, вот один такой промпт, который Rechat применяет для генерации синтетических входов для функции создания и получения контактов:
Write 50 different instructions that a real estate agent can give to his assistant to create contacts on his CRM. The contact details can include name, phone, email, partner name, birthday, tags, company, address and job. For each of the instructions, you need to generate a second instruction which can be used to look up the created contact. . The results should be a JSON code block with only one string as the instruction like the following: [ ["Create a contact for John (johndoe@apple.com)", "What's the email address of John Smith?"] ]
С помощью приведённого выше промпта мы генерируем тестовые случаи, как показано ниже:
[ [ 'Create a contact for John Smith (johndoe@apple.com) with phone number 123-456-7890 and address 123 Apple St.', 'What\'s the email address of John Smith?' ], [ 'Add Emily Johnson with phone 987-654-3210, email emilyj@email.com, and company ABC Inc.', 'What\'s the phone number for Emily Johnson?' ], [ 'Create a contact for Tom Williams with birthday 10/20/1985, company XYZ Ltd, and job title Manager.', 'What\'s Tom Williams\' job title?' ], [ 'Add a contact for Susan Brown with partner name James Brown, and email susanb@email.com.', 'What\'s the partner name of Susan Brown?' ], … ]
Для каждого из этих тестовых случаев мы выполняем первый пользовательский ввод, чтобы создать контакт. Затем выполняем второй запрос, чтобы получить этот контакт. Если CRM возвращает не ровно 1 результат, мы понимаем, что возникла проблема либо при создании, либо при получении контакта. Мы также можем запускать общие утверждения — например, то, что проверяет отсутствие UUID в ответе. Эти тесты нужно постоянно обновлять по мере того, как вы наблюдаете данные через оценку человеком и отладку. Главное — сделать их максимально сложными, но при этом отражающими взаимодействие пользователей с системой.
Вам не нужно ждать продакшен-данных, чтобы тестировать систему. Вы можете делать обоснованные предположения о том, как пользователи будут использовать ваш продукт, и генерировать синтетические данные. Можно также дать небольшой группе пользователей попользоваться продуктом и позволить их активности уточнить вашу стратегию генерации синтетических данных. Один из признаков того, что вы пишете хорошие тесты и утверждения, — когда модель с трудом их проходит; эти режимы отказа становятся проблемами, которые позже можно решить такими техниками, как fine-tuning.
В связи с этим, в отличие от традиционных юнит-тестов, вам не обязательно нужен 100%-й процент прохождения. Ваш процент прохождения — это продуктовое решение, зависящее от того, какие отказы вы готовы терпеть.
Шаг 3: Регулярно запускайте и отслеживайте свои тесты
Есть много способов оркестровать тесты Уровня 1. Rechat использует CI-инфраструктуру (напр., GitHub Actions, GitLab Pipelines и т. д.) для выполнения этих тестов. Однако инструментарий для этой части рабочего процесса зарождается и быстро развивается.
Мой совет — оркеструйте тесты с наименьшим трением в вашем технологическом стеке. Помимо отслеживания тестов, нужно отслеживать их результаты во времени, чтобы видеть, прогрессируете ли вы. Если вы используете CI, стоит собирать метрики вместе с версиями ваших тестов/промптов за пределами CI-системы для удобного анализа и отслеживания.
Я рекомендую начинать с простого и использовать вашу существующую аналитическую систему для визуализации результатов тестов. Например, Rechat использует Metabase для отслеживания результатов своих LLM-тестов во времени. Ниже — скриншот дашборда, который Rechat построил с помощью Metabase:
Этот скриншот показывает распространённость конкретной ошибки (выделена жёлтым) в Lucy до (слева) и после (справа) того, как мы её устранили.
Уровень 2: Оценка человеком и моделью
После того как вы заложили прочный фундамент из тестов Уровня 1, можно переходить к другим формам валидации, которые невозможно проверить только утверждениями. Предварительное условие для проведения оценки человеком и на основе модели — логирование ваших трейсов.
Логирование трейсов
Трейс — это понятие, давно существующее в разработке ПО, представляющее собой лог последовательности событий, таких как пользовательские сессии или путь запроса через распределённую систему. Иными словами, трейсинг — это логическая группировка логов. В контексте LLM трейсы часто означают разговоры, которые вы ведёте с LLM. Например, сообщение пользователя, за которым следует ответ ИИ, за которым следует ещё одно сообщение пользователя, — это пример трейса.
Существует растущее число решений для логирования трейсов LLM.2 Rechat использует LangSmith, который логирует трейсы и позволяет просматривать их в удобочитаемом виде с интерактивной площадкой (playground) для итераций над промптами. Иногда логирование трейсов требует инструментирования вашего кода. В данном случае Rechat использовал LangChain, который автоматически логирует события трейсов в LangSmith за вас. Вот скриншот того, как это выглядит:
Мне нравится LangSmith — он не требует использования LangChain, интуитивно понятен и прост в применении. Поиск, фильтрация и чтение трейсов — важнейшие функции для любого решения, которое вы выберете. Я обнаружил, что некоторые инструменты реализуют эти базовые функции неправильно!
Просмотр ваших трейсов
Вы должны устранить всё трение в процессе просмотра данных. Это означает отображение ваших трейсов предметно-ориентированными способами. Я часто обнаруживал, что лучше создать собственный инструмент для просмотра и разметки данных, чтобы собрать всю нужную информацию на одном экране. В случае Lucy нам нужно было смотреть на множество источников информации (лог трейсов, CRM и т. д.), чтобы понять, что сделал ИИ. Это именно тот тип трения, который нужно устранить. В случае Rechat это означало добавление такой информации, как:
Какой инструмент (функция) и сценарий оценивались. Был ли трейс результатом синтетического ввода или реального пользовательского ввода. Фильтры для навигации между разными комбинациями инструментов и сценариев. Ссылки на CRM и систему логирования трейсов для текущей записи.
Я создавал разные варианты этого инструмента для каждой задачи, над которой работал. Иногда мне даже приходится встраивать другое приложение, чтобы увидеть, как выглядит пользовательское взаимодействие. Ниже — скриншот инструмента, который мы построили для оценки трейсов Rechat:
Ещё одно проектное решение, специфичное для Lucy: мы заметили, что многие отказы связаны с мелкими ошибками в финальном выводе LLM (формат, содержание и т. д.). Мы решили сделать финальный вывод редактируемым человеком, чтобы можно было курировать и исправлять данные для fine-tuning.
Эти инструменты можно создать с помощью лёгких фронтенд-фреймворков, таких как Gradio, Streamlit, Panel или Shiny, менее чем за день. Показанный выше инструмент был построен на Shiny for Python. Кроме того, есть инструменты вроде Lilac, который использует ИИ для семантического поиска и фильтрации данных, что невероятно удобно для нахождения набора похожих точек данных при отладке проблемы.
Я часто начинаю с разметки примеров как хороших или плохих. Я обнаружил, что присвоение баллов или более детальных рейтингов сложнее в управлении, чем бинарные оценки. Существуют продвинутые техники, которые можно использовать, чтобы сделать оценку человеком более эффективной или точной (напр., активное обучение, консенсусное голосование и т. д.), но я рекомендую начинать с чего-то простого. Наконец, как и в случае с юнит-тестами, вам следует организовывать и анализировать результаты оценки человеком, чтобы оценить, прогрессируете ли вы со временем.
Как будет обсуждаться далее, эти размеченные примеры измеряют качество вашей системы, валидируют автоматизированную оценку и курируют высококачественные синтетические данные для fine-tuning.
Сколько данных нужно просматривать?
Меня часто спрашивают, сколько данных изучать. На старте стоит изучать как можно больше данных. Обычно я как минимум читаю трейсы, сгенерированные из ВСЕХ тестовых случаев, и трейсы, порождённые пользователями. Вы никогда не сможете перестать смотреть на данные — бесплатного сыра не бывает. Однако со временем вы можете больше прибегать к выборке из данных, снижая нагрузку.3
Автоматизированная оценка с помощью LLM
Многие вендоры хотят продать вам инструменты, которые якобы устраняют необходимость в том, чтобы человек смотрел на данные. Хорошая идея — чтобы люди периодически оценивали хотя бы выборку трейсов. Я часто обнаруживаю, что «корректность» довольно субъективна, и вам нужно согласовать модель с человеком.
Вам следует отслеживать корреляцию между оценкой на основе модели и оценкой человеком, чтобы решить, насколько вы можете полагаться на автоматическую оценку. Кроме того, собирая от разметчиков критические замечания с объяснением, почему они принимают то или иное решение, вы можете итерировать над моделью-оценщиком, чтобы согласовать её с людьми через промпт-инжиниринг или fine-tuning. Тем не менее для согласования модели-оценщика я склонен отдавать предпочтение промпт-инжинирингу.
Я обожаю использовать низкотехнологичные решения вроде Excel для итераций по согласованию оценки на основе модели с людьми. Например, я каждые несколько дней отправлял своему коллеге Филлипу следующую таблицу для оценки в другом сценарии использования, связанном с генератором запросов на естественном языке. Эта таблица содержала следующую информацию:
ответ модели: это предсказание, сделанное LLM. критика модели: это критика, написанная (обычно более мощной) LLM о предсказании вашей исходной LLM. исход по модели: это бинарная метка, которую модель-критик присваивает ответу модели как «хорошему» или «плохому».
Затем Филлип заполняет свою версию той же информации — то есть свою критику, исход и желаемый ответ — для 25–50 примеров за раз (это столбцы с префиксом «phillip_» ниже):
Эта информация позволяла мне итерировать над промптом модели-критика, чтобы со временем достаточно хорошо согласовать её с Филлипом. Это тоже легко отслеживать низкотехнологичным способом в таблице:
Это скриншот таблицы, где мы фиксировали наши попытки согласовать оценку на основе модели с человеком-оценщиком.
Общие советы по оценке на основе модели:
Используйте самую мощную модель, какую можете себе позволить. Чтобы хорошо что-то критиковать, часто требуются продвинутые способности к рассуждению. Для критики выводов нередко можно обойтись более медленной, но более мощной моделью, чем та, что используется в продакшене. Оценка на основе модели — это мета-проблема внутри вашей более крупной проблемы. Вам придётся поддерживать мини-систему оценки, чтобы отслеживать её качество. Иногда я делал fine-tuning модели на этом этапе (но стараюсь этого не делать). После того как вы привели модель-оценщик в соответствие с человеком, нужно продолжать периодически выполнять упражнения для мониторинга согласия модели и человека.
В этом примере мы использовали согласие между моделью и человеком-оценщиком, потому что наш набор данных был примерно сбалансирован (около 50% случаев были отказами). Однако использование сырого согласия в целом не рекомендуется и может вводить в заблуждение при несбалансированных классах. Вместо этого вам обычно следует измерять precision и recall по отдельности, чтобы получить более точную картину согласованности вашего судьи.
Больше всего в создании хорошей модели-оценщика мне нравится то, что её критику можно использовать для курирования высококачественных синтетических данных, о чём я расскажу позже.
Уровень 3: A/B-тестирование
Наконец, всегда полезно проводить A/B-тесты, чтобы убедиться, что ваш ИИ-продукт стимулирует желаемое поведение или результаты пользователей. A/B-тестирование для LLM не слишком отличается от такового для других типов продуктов. Если вы хотите узнать больше об A/B-тестировании, я рекомендую почитать блог Eppo (созданный коллегами, с которыми я раньше работал и которые являются звёздами A/B-тестирования).
Этот этап можно отложить, пока вы не будете достаточно готовы и убеждены, что ваш ИИ-продукт пригоден для показа реальным пользователям. Этот уровень оценки обычно уместен только для более зрелых продуктов.
Оценка RAG
Помимо оценки вашей системы в целом, вы можете оценивать подкомпоненты вашего ИИ, такие как RAG. Оценка RAG выходит за рамки этого поста, но вы можете узнать больше об этой теме в посте Джейсона Лю.
Системы оценки бесплатно открывают суперспособности
Помимо быстрых итераций, системы оценки открывают возможность делать fine-tuning и отладку, что может вывести ваш ИИ-продукт на новый уровень.
Fine-tuning
Rechat устранил множество режимов отказа через fine-tuning, которые были невозможны при одном лишь промпт-инжиниринге. Fine-tuning лучше всего подходит для изучения синтаксиса, стиля и правил, тогда как такие техники, как RAG, снабжают модель контекстом или актуальными фактами.
99% труда, связанного с fine-tuning, — это сборка высококачественных данных, покрывающих весь охват вашего ИИ-продукта. Однако если у вас есть надёжная система оценки, как у Rechat, у вас уже есть мощный движок генерации и курирования данных! Подробнее о процессе fine-tuning я расскажу в будущем посте.4
Синтез и курирование данных
Чтобы проиллюстрировать, почему курирование и синтез данных достаются почти бесплатно, как только у вас есть система оценки, рассмотрим случай, когда вы хотите создать дополнительные данные для fine-tuning для упомянутого ранее поиска объявлений. Сначала вы можете использовать LLM для генерации синтетических данных с помощью такого промпта:
Imagine if Zillow was able to parse natural language. Come up with 50 different ways users would be able to search listings there. Use real names for cities and neighborhoods. You can use the following parameters:
Это почти идентично упражнению по созданию тестовых случаев! Затем вы можете использовать свои тесты Уровня 1 и Уровня 2, чтобы отфильтровать нежелательные данные, которые не проходят утверждения или которые модель-критик считает ошибочными. Вы также можете использовать существующие инструменты оценки человеком для просмотра трейсов, чтобы курировать трейсы для набора данных fine-tuning.
Отладка
Когда вы получаете жалобу или видите ошибку, связанную с вашим ИИ-продуктом, вы должны уметь быстро её отладить. Если у вас есть надёжная система оценки, у вас уже есть:
База данных трейсов, которую можно искать и фильтровать. Набор механизмов (утверждения, тесты и т. д.), которые помогают помечать ошибки и плохое поведение. Инструменты поиска и навигации по логам, помогающие найти первопричину ошибки. Например, ошибкой может быть RAG, баг в коде или плохо работающая модель. Возможность вносить изменения в ответ на ошибку и быстро проверять их эффективность.
Короче говоря, существует невероятно большое пересечение между инфраструктурой, необходимой для оценки, и той, что нужна для отладки.
Заключение
Системы оценки создают маховик, позволяющий вам очень быстро итерировать. Почти всегда именно здесь люди застревают при создании ИИ-продуктов. Надеюсь, этот пост даст вам интуицию о том, как подходить к построению ваших систем оценки. Несколько ключевых выводов, которые стоит держать в голове:
Устраните ВСЁ трение в просмотре данных. Держите всё простым. Не покупайте навороченные LLM-инструменты. Сначала используйте то, что у вас есть. Вы делаете что-то не так, если не смотрите на большое количество данных. Не полагайтесь на универсальные фреймворки оценки для измерения качества вашего ИИ. Вместо этого создайте систему оценки, специфичную для вашей задачи. Пишите много тестов и часто их обновляйте. LLM можно использовать, чтобы разблокировать создание системы оценки. Примеры включают использование LLM для: генерации тестовых случаев и написания утверждений, генерации синтетических данных, критики и разметки данных и т. д. Переиспользуйте свою инфраструктуру оценки для отладки и fine-tuning.
Буду рад услышать от вас, если этот пост оказался полезным или если у вас есть вопросы. Моя почта: hamel@parlance-labs.com.
Эта статья — адаптация этого разговора, который у меня состоялся с Emil Sedgh и Hugo Browne-Anderson в подкасте Vanishing Gradients. Спасибо Jeremy Howard, Eugene Yan, Shreya Shankar, Jeremy Lewi и Joseph Gleasure за рецензирование этой статьи.
Сноски
Это не значит, что люди ленивы. Многие просто не знают, как настроить системы оценки, и пропускают эти шаги.↩︎ Среди примеров — arize, human loop, openllmetry и honeyhive.↩︎ Разумная эвристика — продолжать читать логи, пока не почувствуете, что больше не узнаёте ничего нового.↩︎ Если ждать невмоготу, я скоро буду вести этот курс по fine-tuning.↩︎