Перейти к содержимому
ПереводыAnthropic

Курс Anthropic: оценка промптов, часть 1 — эвалы своими руками (Prompt evaluations, уроки 1–4)

Перевод уроков 1–4 курса Prompt evaluations из anthropics/courses: что такое эвалы и чем они отличаются от бенчмарков, из чего состоит эвал, три способа оценки (люди, код, модель), оценка вручную в Workbench и два эвала с проверкой кодом — подсчёт ног у животных и классификация жалоб.

Prompt evaluations — четвёртый курс anthropics/courses и, по оценке нашей страницы «Учимся у создателей», один из самых ценных: он учит измерять качество промпта числом, а не «на глаз». В курсе девять уроков. Перевод разбит на две части:

  1. Эвалы своими руками (эта страница): уроки 1–4 — понятия, оценка людьми в Workbench, эвалы с проверкой кодом на чистом Python.
  2. Эвалы в promptfoo: уроки 5–9 — тот же цикл в инструменте promptfoo, свои грейдеры на Python, оценка другой моделью (LLM-судья) по встроенной и своей рубрике.
  • Когда. Вместе с уроком 7.3.2 «Свои эвалы и LLM-as-judge»: там те же понятия (свой eval-набор, exact match, LLM-судья) и статистика, которой в курсе Anthropic почти нет. Урок 1 этого курса начинается с бенчмарков — это урок 7.3.1. Продолжение — уроки 8.4.1 (решение о выпуске версии по интервалам) и 8.4.2 (эвалы из логов и в CI).
  • Сколько времени. Около двух часов вместе с запуском эвалов.
  • Что можно пропустить. Урок 2 про Workbench — если у вас нет ключа Anthropic: интерфейс доступен только в консоли. Идею (оценивать ответы вручную по шкале и сравнивать версии промпта) он передаёт и без запуска.

Этот урок и следующие восемь посвящены подходам к оценке (evaluations, «эвалам») приложений на больших языковых моделях — критически важной части разработки.

Большие языковые модели стали мощным инструментом для бизнеса в самых разных отраслях. Но чтобы раскрыть их потенциал, мало просто встроить модель в продукт — нужны тщательная оценка и оптимизация. Здесь и появляются оценки промптов, или «эвалы».

Две настоящие цитаты из внутренней команды архитекторов решений Anthropic о том, почему эвалы так важны:

Неумение команд измерять качество моделей — главное препятствие для использования LLM в продакшене; из-за него промптинг остаётся искусством, а не наукой.

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

Есть две главные причины, по которым разработчики не пишут эвалы:

  1. многие вообще не знакомы с идеей эвалов;
  2. неясно, как их реализовать на практике.

Курс объясняет и то, и другое: что такое эвалы и как их писать. Этот вводный урок — о первом: понятие эвалов и обзор разных подходов к их написанию.

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

Бенчмарки — это стандартизированные экзамены мира ИИ. Как баллы SAT должны дать колледжам общее представление об учебных способностях абитуриента, так и бенчмарки дают общее представление о том, насколько хорошо модель справляется с широким кругом задач.

Компании, создающие большие языковые модели, прогоняют эти бенчмарки, чтобы показать, на что способны их модели. Вы наверняка видели впечатляющие баллы на тестах с причудливыми названиями вроде ARC, MMLU или TruthfulQA. Они охватывают всё — от понимания прочитанного до сложных рассуждений и знаний в разных областях. Бенчмарки полезны, чтобы сравнивать модели и следить за общим прогрессом. Возможно, вам попадались карточки моделей с таблицами баллов. (В оригинале — пример такой таблицы.)

Бенчмарки отлично подходят для хвастовства и общей картины, но это не вся история. Это как знать чей-то IQ: он может (а может и нет!) что-то сказать об общем интеллекте, но не скажет, хорош ли человек на вашей конкретной работе.

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

LLM — это сверхмощные швейцарские ножи для текста. Они умеют с языком удивительные вещи — от стихов до программ. Но когда вы используете модель для конкретной задачи — отвечать на письма клиентов или писать описания товаров, — нужно знать, насколько хорошо она справляется именно с этим.

Оценки промптов, или «эвалы» (их ещё называют эвалами под заказчика, customer evaluations), — это систематические тесты, которые измеряют, насколько хорошо модель работает на вашей конкретной задаче. Это мост между общими способностями модели и требованиями вашего приложения. Хорошие эвалы показывают, что связка «модель + промпт» не просто проходит общие бенчмарки, а отлично решает нужные вам задачи.

Главные выгоды эвалов:

  • итеративное улучшение промпта — лучше ли вторая версия промпта первой на моей задаче?
  • контроль качества до и после выпуска и правок промпта — не вызвало ли последнее изменение регресс?
  • объективное сравнение моделей — можно ли перейти на новейшую модель Anthropic и сохранить или улучшить результат на эвале?
  • возможная экономия — можно ли перейти на самую дешёвую и быструю модель и сохранить результат?

Работа над промптом идёт итеративно, и в центре цикла — эвалы (в оригинале — схема цикла):

  • сначала составляем тест-кейсы (о них — чуть ниже);
  • затем пишем черновик промпта под задачу;
  • проверяем промпт на тест-кейсах и измеряем, насколько хорошо модель справилась, — получаем базовый балл;
  • получив базовый балл, меняем промпт и повторяем.

Эвалы — это метрики, которые количественно описывают качество связки «промпт + модель». Без количественного измерения как узнать, улучшают ли правки промпт?

У хорошо продуманной оценки промпта четыре основные части:

  • Входной пример — инструкция или вопрос, который получает модель. Важно, чтобы примеры точно отражали то, что приложение будет получать в реальной работе.
  • Эталонный ответ (golden answer) — правильный или идеальный ответ, с которым сравнивают ответ модели. Чтобы составить качественные эталоны, часто нужны эксперты в предметной области.
  • Ответ модели — то, что модель действительно сгенерировала в ответ на промпт. Его мы и оцениваем, сравнивая с эталоном.
  • Оценка (score) — количественная или качественная величина, описывающая качество ответа на этот вход. Способ оценки зависит от задачи и выбранного подхода.

Обычно авторы советуют не меньше 100 пар «тест-кейс — эталон». Но чтобы не тратить деньги учащихся на API, в большинстве эвалов этого курса пар гораздо меньше.

Допустим, мы хотим классифицировать жалобы клиентов с помощью LLM. (Очень) маленький набор для эвала может выглядеть так:

eval_data = [
{
"complaint": "Приложение вылетает каждый раз, когда я пытаюсь загрузить фото",
"golden_answer": ["Ошибка ПО"]
},
{
"complaint": "Компьютер не видит мой принтер",
"golden_answer": ["Неисправность оборудования"]
},
{
"complaint": "Не могу понять, как сменить пароль",
"golden_answer": ["Ошибка пользователя"]
}
]

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

Правильный способ оценки критически важен для пользы эвала. У каждого способа свои сильные стороны и свои задачи.

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

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

Виды оценки людьми:

  • Экспертная проверка. Специалисты оценивают точность и глубину ответов. Для банковского чат-бота, который обсуждает ипотеку, юристы могут вручную проверить, что ответы соответствуют законам о справедливом кредитовании и верно описывают условия. Дерматологи могут оценить советы модели по скринингу рака кожи: верно ли распознана проблема, правильно ли выбрана срочность, соответствует ли совет последним исследованиям.
  • Панель пользователей. Группа оценивает ответы на ясность, полезность, увлекательность и другие человеческие критерии.

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

Главное преимущество — скорость и масштабируемость: настроив один раз, можно быстро и одинаково проверить тысячи ответов. Но с тонкими или субъективными ответами код справляется плохо. Частые приёмы: точное совпадение строк, наличие ключевых слов, шаблоны регулярных выражений.

Виды оценки кодом:

  • Точное совпадение строк — самый строгий вариант: ответ модели должен совпасть с эталоном символ в символ. Как тест с одним правильным вариантом: на вопрос «Какая столица у Франции?» принимается только «Париж».
  • Наличие ключевых слов — проверяем, есть ли в ответе нужные слова или фразы, независимо от порядка и контекста. У чат-бота поддержки на вопрос «Как сбросить мой термостат SmartHome?» в ответе обязательно должны быть «удерживать», «кнопку», «5 секунд» и «мигающий индикатор».
  • Регулярные выражения проверяют сложные шаблоны текста. Банковский чат-бот, оценивающий право на кредитную карту, может быть обязан отвечать по шаблону Ваш кредитный рейтинг \d{3} (позволяет|не позволяет) получить нашу карту \w+, чтобы в ответе были и рейтинг, и решение.
  • И многие другие!

Оценка с помощью LLM — нечто среднее между кодом и людьми. Ответы оценивает другая модель (или иногда та же самая). Тщательно составив промпт для оценки, можно использовать понимание языка моделью, чтобы проверять самые разные критерии.

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

Виды оценки моделью:

  • качество сводки — насколько она кратка и точна?
  • оценка тона — соответствует ли ответ правилам бренда и тону общения?
  • любое другое качество! — можно задать свою рубрику, по которой модель оценит ответы по любым метрикам: насколько ответ извиняющийся? упоминает ли он конкурента?

Хорошие оценки промптов — критически важный шаг в разработке LLM-приложений. Систематически проверяя и дорабатывая промпты, вы добиваетесь стабильных, качественных результатов под свои задачи. Время, вложенное в хорошие эвалы, окупается качеством, более простой оптимизацией и уверенностью в решении. Дальше научимся писать эвалы!

Этот урок показывает, как проводить оценку людьми в Anthropic Workbench — очень простом наглядном интерфейсе для быстрого прототипирования промптов и ручной оценки. Для продакшена авторы советуют более масштабируемые подходы, но Workbench — отличное место, чтобы начать с ручной оценки, прежде чем переходить к строгим эвалам с проверкой кодом или моделью.

Workbench — раздел консоли Anthropic, где удобно быстро прототипировать промпты и вручную оценивать ответы. Слева вводится промпт. Допустим, мы делаем приложение для перевода кода с любого языка на Python и хотим написать для него лучший промпт. Первая попытка:

Ты — опытный программист, твоя задача — переводить код с другого языка программирования на Python. Твоя цель — точный и идиоматичный перевод исходного кода на Python.
Вот исходный код для перевода:
<source_code>
{{SOURCE_CODE}}
</source_code>
Исходный код написан на этом языке:
<source_language>
{{SOURCE_LANGUAGE}}
</source_language>
Пожалуйста, переведи этот код на Python

Обратите внимание на переменные {{SOURCE_CODE}} и {{SOURCE_LANGUAGE}}: позже их заменят конкретными значениями.

Вставляем промпт в левую часть Workbench. Затем задаём тестовые значения переменных кнопкой переменных ({ }): откроется окно, где нужно ввести SOURCE_CODE и SOURCE_LANGUAGE. Нажимаем «Run» и смотрим ответ модели справа.

Проверять промпт на одном наборе переменных — хорошее начало, но в Workbench есть и встроенный инструмент оценки, который прогоняет промпт на многих входах. Переключаемся кнопкой «Evaluate» вверху. Откроется таблица, где первая строка уже заполнена нашим первым результатом (код на JavaScript).

Кнопкой «Add Row» добавим два новых тест-кейса: код на Ruby и код на C#. Затем можно нажать «Run» у каждой строки или оранжевую кнопку «Run Remaining», чтобы запустить все ещё не запущенные строки. Нажимаем «Run Remaining» и смотрим ответы.

Теперь внимательно смотрим на ответы и ставим им оценки: в правой колонке у каждого ответа есть выбор балла.

Ответ для JavaScript. Модель перевела const chickenCount = 99 в chicken_count = 99, а потом на полэкрана объяснила: в Python нет const, переменные объявляются без ключевого слова, по соглашению имена констант пишут заглавными (CHICKEN_COUNT = 99), и обе версии корректны.

Ответ для Ruby. «Конечно! Вот перевод кода на Python:» — далее класс Person с __init__, методами birthday, introduce и методом класса create_family, создание семьи, приветствия и день рождения Чарли. А затем ещё девять пунктов объяснений: initialize стал __init__, восклицательный знак из birthday! убран, интерполяция строк заменена f-строками, map — генератором списка и т. д.

Все ответы неплохо переводят код, но есть главные проблемы:

  • Не нужны назойливые вступления вроде «Конечно! Вот перевод кода на Python». Это пустая трата выходных токенов!
  • В нынешнем формате ответ очень трудно разбирать программно. Как кодом отдельно вытащить переведённый код на Python?
  • Не нужны длинные объяснения в конце. Нам нужен только переведённый код.

Поставим каждому ответу 3 из 5.

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

Пожалуйста, переведи этот код на Python.
Оформи ответ так:
<python_code>
Здесь твой перевод на Python
</python_code>
Выведи только теги <python_code>, без какого-либо другого текста

Вернувшись в режим «Prompt», обновляем промпт (Workbench подсвечивает добавленный текст зелёным) и нажимаем «Run». Новый ответ — именно то, что нужно: без вступления и длинных объяснений.

Возвращаемся в режим оценки. В левом верхнем углу видно «v2» — вторая версия промпта. Нажимаем «Run Remaining»: новые ответы для всех тест-кейсов выглядят отлично — без вступлений и объяснений, только теги <python_code> с переведённым кодом. Ставим всем 5 из 5!

Попробовав два промпта, можно сравнить результаты рядом. Кнопкой «+ Add Comparison» вверху справа выбираем прошлую версию промпта (v1): Workbench покажет ответы и оценки людей для обеих версий бок о бок. Видно, что промпт v2 гораздо лучше подходит для нашей задачи!

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

В этом уроке начнём с очень простого эвала с проверкой кодом, а в следующем возьмём более реалистичный промпт. Порядок действий (в оригинале — схема):

  1. Составить тестовый набор для эвала.
  2. Написать первую версию промпта.
  3. Прогнать её через эвал и получить оценку.
  4. Изменить промпт по результатам.
  5. Прогнать изменённый промпт и, надеемся, получить оценку выше!

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

Первый шаг — набор данных: входы и эталоны. Это список словарей с ключами animal_statement и golden_answer:

eval_data = [
{"animal_statement": "Животное — человек.", "golden_answer": "2"},
{"animal_statement": "Животное — змея.", "golden_answer": "0"},
{"animal_statement": "Лис потерял лапу, но потом чудесным образом отрастил потерянную лапу и сверх того ещё одну, загадочную.", "golden_answer": "5"},
{"animal_statement": "Животное — собака.", "golden_answer": "4"},
{"animal_statement": "Животное — кошка с двумя лишними лапами.", "golden_answer": "6"},
{"animal_statement": "Животное — слон.", "golden_answer": "4"},
{"animal_statement": "Животное — птица.", "golden_answer": "2"},
{"animal_statement": "Животное — рыба.", "golden_answer": "0"},
{"animal_statement": "Животное — паук с двумя лишними ногами", "golden_answer": "10"},
{"animal_statement": "Животное — осьминог.", "golden_answer": "8"},
{"animal_statement": "Животное — осьминог, который потерял две ноги, а потом отрастил три.", "golden_answer": "9"},
{"animal_statement": "Животное — двухголовое восьминогое мифическое существо.", "golden_answer": "8"},
]

Некоторые вопросы с подвохом, например: «Лис потерял лапу, но потом чудесным образом отрастил потерянную лапу и сверх того ещё одну». Это пригодится позже!

Функция ниже принимает одно утверждение о животном и возвращает готовый список сообщений с первой версией промпта:

def build_input_prompt(animal_statement):
user_content = f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном.
<animal_statement>{animal_statement}</animal_statement>
Сколько ног у этого животного? Ответь числом"""
messages = [{'role': 'user', 'content': user_content}]
return messages
build_input_prompt(eval_data[0]['animal_statement'])

Теперь простая функция, которая отправляет список сообщений в API:

from claude_local import client, MODEL_NAME # в оригинале: client = Anthropic(), MODEL_NAME = "claude-3-haiku-20240307"
def get_completion(messages):
response = client.messages.create(
model=MODEL_NAME,
max_tokens=200,
messages=messages
)
return response.content[0].text
full_prompt = build_input_prompt(eval_data[0]['animal_statement'])
get_completion(full_prompt) # у авторов: '2'

Получаем 2 — проверку на глаз прошло: у людей обычно две ноги. Следующий шаг — собрать и запустить эвал на всех 12 примерах.

Подставим каждый вход из eval_data в шаблон промпта, отправим готовые промпты модели и соберём все ответы:

outputs = [get_completion(build_input_prompt(question['animal_statement'])) for question in eval_data]
outputs

У авторов вышло:

['2', '0', '5', '4', '6', '4',
'Based on the provided animal statement, "The animal is a bird.", the animal has 2 legs.\n\nResponse: 2',
'0', '8', 'An octopus has 8 legs.', '5', '8']

Уже видно, что промпт надо улучшать: некоторые ответы — не только числа! Посмотрим на ответы рядом с эталонами:

for output, question in zip(outputs, eval_data):
print(f"Утверждение: {question['animal_statement']}\nЭталон: {question['golden_answer']}\nОтвет: {output}\n")

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

def grade_completion(output, golden_answer):
return output == golden_answer # точное совпадение строк
grades = [grade_completion(output, question['golden_answer']) for output, question in zip(outputs, eval_data)]
print(f"Оценка: {sum(grades)/len(grades)*100}%") # у авторов: 66.66666666666666%

У нас есть базовая оценка! Первый промпт дал точность 66,6 %. Просмотрев результаты, видим две явные проблемы.

Нам нужен ответ-число, а часть ответов — не числа:

Утверждение: Животное — птица.
Эталон: 2
Ответ: Based on the provided animal statement, "The animal is a bird.", the animal has 2 legs.

Это исправимо промптом!

Кроме того, некоторые ответы просто неправильные:

Утверждение: Животное — осьминог, который потерял две ноги, а потом отрастил три.
Эталон: 9
Ответ: 5

и

Утверждение: Животное — паук с двумя лишними ногами
Эталон: 10
Ответ: 8

Эти входы с подвохом, и модели с ними трудно. Это тоже попробуем исправить промптом!

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

def build_input_prompt2(animal_statement):
user_content = f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном.
<animal_statement>{animal_statement}</animal_statement>
Сколько ног у этого животного? Ответь только числом цифрами, например 2 или 6, и больше ничего."""
messages = [{'role': 'user', 'content': user_content}]
return messages

Главное добавление — строка «Ответь только числом цифрами, например 2 или 6, и больше ничего». Проверим новый промпт на всех входах:

outputs2 = [get_completion(build_input_prompt2(question['animal_statement'])) for question in eval_data]
outputs2 # у авторов: ['2', '0', '6', '4', '6', '4', '2', '0', '8', '8', '5', '8']

Теперь все ответы — числа! Но с самими числами всё ещё есть явные проблемы, например:

Утверждение: Животное — паук с двумя лишними ногами
Эталон: 10
Ответ: 8

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

grades = [grade_completion(output, question['golden_answer']) for output, question in zip(outputs2, eval_data)]
print(f"Оценка: {sum(grades)/len(grades)*100}%") # у авторов: 75.0%

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

Теперь займёмся логическими ошибками вроде такой:

Утверждение: Лис потерял лапу, но потом чудесным образом отрастил потерянную лапу и сверх того ещё одну, загадочную.
Эталон: 5
Ответ: 6

Можно применить цепочку рассуждений (chain of thought): дать модели указание сначала порассуждать и лишь потом ответить. Раз эвал уже есть, мы можем проверить, действительно ли цепочка рассуждений помогает!

Напишем промпт, который просит модель «думать вслух» в тегах <thinking>. Логика немного усложнится: понадобится удобный способ достать итоговый ответ. Поэтому попросим модель писать итоговый ответ в тегах <answer>, чтобы число было легко извлечь:

def build_input_prompt3(animal_statement):
user_content = f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном.
<animal_statement>{animal_statement}</animal_statement>
Сколько ног у этого животного?
Сначала порассуждай о количестве ног у животного, думая по шагам внутри тегов <thinking>.
Затем выведи итоговый ответ внутри тегов <answer>.
Внутри тегов <answer> напиши только количество ног целым числом и больше ничего."""
messages = [{'role': 'user', 'content': user_content}]
return messages
outputs3 = [get_completion(build_input_prompt3(question['animal_statement'])) for question in eval_data]
for output, question in zip(outputs3, eval_data):
print(f"Утверждение: {question['animal_statement']}\nЭталон: {question['golden_answer']}\nОтвет: {output}\n")

Пример ответа у авторов:

Утверждение: Лис потерял лапу, но потом чудесным образом отрастил потерянную лапу и сверх того ещё одну, загадочную.
Эталон: 5
Ответ: Вот мои рассуждения по шагам:
<thinking>
1. Сначала сказано, что лис потерял лапу.
2. Но потом лис «чудесным образом отрастил потерянную лапу и сверх того ещё одну».
3. Значит, у лиса было 4 лапы, он потерял 1, затем отрастил потерянную и ещё одну — итого 5 лап.
</thinking>
<answer>5</answer>

По крайней мере в этом примере логика стала лучше. Теперь ответ нужно сделать «оцениваемым»: перед проверкой извлечь число между тегами <answer>. Функция для этого:

import re
def extract_answer(text):
pattern = r'<answer>(.*?)</answer>'
match = re.search(pattern, text)
if match:
return match.group(1)
else:
return None
extracted_outputs3 = [extract_answer(output) for output in outputs3]
extracted_outputs3 # у авторов: ['2', '0', '5', '4', '6', '4', '2', '0', '10', '8', '9', '8']

Посчитаем оценку и посмотрим, помогла ли цепочка рассуждений:

grades3 = [grade_completion(output, question['golden_answer']) for output, question in zip(extracted_outputs3, eval_data)]
print(f"Оценка: {sum(grades3)/len(grades3)*100}%") # у авторов: 100.0%

Оценка выросла до 100 %! Эвал даёт некоторую уверенность, что правки промпта действительно улучшают ответы. Это простой пример с точным совпадением; в следующем уроке разберём кое-что посложнее.

Урок 4. Эвал с проверкой кодом: классификация

Заголовок раздела «Урок 4. Эвал с проверкой кодом: классификация»

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

  • Ошибка ПО (Software Bug)
  • Неисправность оборудования (Hardware Malfunction)
  • Ошибка пользователя (User Error)
  • Запрос функции (Feature Request)
  • Сбой сервиса (Service Outage)

Например, жалоба «Сайт полностью лёг, не открывается ни одна страница» должна получить категорию «Сбой сервиса». Иногда допустимы две категории: «Кажется, я что-то неправильно установил, и теперь компьютер вообще не включается» — это и «Ошибка пользователя», и «Неисправность оборудования».

Обычно в эвале нужно около 100 примеров, но чтобы уроки были простыми (быстрыми и дешёвыми), берём урезанный набор — список словарей с ключами complaint и golden_answer:

eval_data = [
{"complaint": "Приложение вылетает каждый раз, когда я пытаюсь загрузить фото", "golden_answer": ["Ошибка ПО"]},
{"complaint": "Компьютер не видит мой принтер", "golden_answer": ["Неисправность оборудования"]},
{"complaint": "Не могу понять, как сменить пароль", "golden_answer": ["Ошибка пользователя"]},
{"complaint": "Сайт полностью лёг, не открывается ни одна страница", "golden_answer": ["Сбой сервиса"]},
{"complaint": "Было бы здорово, если бы в приложении появилась тёмная тема", "golden_answer": ["Запрос функции"]},
{"complaint": "Программа зависает, когда я пытаюсь сохранить большие файлы", "golden_answer": ["Ошибка ПО"]},
{"complaint": "Беспроводная мышь не работает даже с новыми батарейками", "golden_answer": ["Неисправность оборудования"]},
{"complaint": "Я случайно удалил важные файлы, поможете восстановить?", "golden_answer": ["Ошибка пользователя"]},
{"complaint": "Ни один ваш сервер не отвечает, у вас авария?", "golden_answer": ["Сбой сервиса"]},
{"complaint": "Можно добавить экспорт данных в CSV?", "golden_answer": ["Запрос функции"]},
{"complaint": "Приложение вылетает, а телефон перегревается", "golden_answer": ["Ошибка ПО", "Неисправность оборудования"]},
{"complaint": "Я не помню свой пароль!", "golden_answer": ["Ошибка пользователя"]},
{"complaint": "Новое обновление что-то сломало, и приложение у меня больше не работает", "golden_answer": ["Ошибка ПО"]},
{"complaint": "Кажется, я что-то неправильно установил, и теперь компьютер вообще не включается", "golden_answer": ["Ошибка пользователя", "Неисправность оборудования"]},
{"complaint": "Ваш сервис лежит, а мне срочно нужна функция пакетной обработки файлов", "golden_answer": ["Сбой сервиса", "Запрос функции"]},
{"complaint": "Видеокарта издаёт странные звуки", "golden_answer": ["Неисправность оборудования"]},
{"complaint": "Клавиатура ни с того ни с сего совсем перестала работать", "golden_answer": ["Неисправность оборудования"]},
{"complaint": "Как только я открываю ваше приложение, телефон начинает жутко тормозить", "golden_answer": ["Ошибка ПО"]},
{"complaint": "Можно сделать интерфейс удобнее? Я вечно теряюсь в меню", "golden_answer": ["Запрос функции", "Ошибка пользователя"]},
{"complaint": "Облачное хранилище не синхронизируется, и я не вижу свои файлы с других устройств", "golden_answer": ["Ошибка ПО", "Сбой сервиса"]},
]

Начнём с простого промпта и измерим, как он работает. Функция принимает жалобу и возвращает строку промпта:

def basic_prompt(complaint):
return f"""
Отнеси следующую жалобу клиента к одной или нескольким из этих категорий:
Ошибка ПО, Неисправность оборудования, Ошибка пользователя, Запрос функции, Сбой сервиса.
Ответь только подходящей категорией или категориями и больше ничего не пиши.
Жалоба: {complaint}
Категория:
"""

Логика оценки чуть сложнее, чем в эвале «сколько ног» из прошлого урока:

from claude_local import client, MODEL_NAME
def get_model_response(prompt, model_name):
response = client.messages.create(
model=model_name,
max_tokens=200,
messages=[{'role': 'user', 'content': prompt}]
)
return response.content[0].text
def calculate_accuracy(eval_data, model_responses):
correct_predictions = 0
total_predictions = len(eval_data)
for item, response in zip(eval_data, model_responses):
# Сравниваем множества категорий без учёта регистра и порядка
golden_set = set(category.lower() for category in item["golden_answer"])
prediction_set = set(category.strip().lower() for category in response.split(','))
if golden_set == prediction_set:
correct_predictions += 1
return correct_predictions / total_predictions
def evaluate_prompt(prompt_func, eval_data, model_name):
print(f"Оцениваем на модели: {model_name}")
model_responses = [get_model_response(prompt_func(item['complaint']), model_name) for item in eval_data]
accuracy = calculate_accuracy(eval_data, model_responses)
print(f"Точность: {accuracy:.2%}")
for item, response in zip(eval_data, model_responses):
print(f"\nЖалоба: {item['complaint']}")
print(f"Эталон: {item['golden_answer']}")
print(f"Ответ модели: {response}")
return accuracy

Функция evaluate_prompt делает следующее:

  1. Подставляет каждый вход в функцию-шаблон промпта и отправляет готовый промпт модели функцией get_model_response, собирая ответы.
  2. Считает точность, сравнивая ответы модели с эталонами, — для этого вызывает calculate_accuracy.
  3. calculate_accuracy с помощью множеств (set) проверяет, что в ответе ровно нужные категории. Помните: это уже не эвал на точное совпадение, как «подсчёт ног».
  4. calculate_accuracy возвращает точность.
  5. evaluate_prompt печатает итоговые результаты.

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

evaluate_prompt(basic_prompt, eval_data, model_name=MODEL_NAME) # в оригинале — "claude-3-haiku-20240307"; у авторов: 85.00%

Первый промпт дал 85 % точности. Изменим его и перезапустим эвал. Новая версия подробнее объясняет категории и содержит 9 примеров «вход — выход»:

def improved_prompt(complaint):
return f"""
Ты — ИИ-ассистент, специализирующийся на классификации обращений в службу поддержки. Твоя задача — анализировать жалобы клиентов и относить их к одной или нескольким из следующих категорий:
1. Ошибка ПО: программа работает не так, как задумано.
2. Неисправность оборудования: проблемы с физическими устройствами или компонентами.
3. Ошибка пользователя: трудности из-за того, что пользователь не понял или неправильно использовал продукт.
4. Запрос функции: предложения новых возможностей или улучшений.
5. Сбой сервиса: общесистемные проблемы, влияющие на доступность сервиса.
Важные правила:
- Жалоба может относиться к нескольким категориям. Тогда перечисли все подходящие, но по возможности старайся выбрать одну.
Примеры:
1. Жалоба: «Приложение вылетает, когда я пытаюсь сохранить прогресс».
Категория: Ошибка ПО
2. Жалоба: «Клавиатура не работает после того, как я пролил на неё кофе».
Категория: Неисправность оборудования
3. Жалоба: «Не могу найти на вашем сайте кнопку входа».
Категория: Ошибка пользователя
4. Жалоба: «Было бы здорово, если бы в вашем приложении была тёмная тема».
Категория: Запрос функции
5. Жалоба: «Ни один ваш сервис не загружается ни у меня, ни у коллег».
Категория: Сбой сервиса
6. Жалоба: «Приложение ломается каждый раз, когда я пытаюсь сменить фото профиля».
Категория: Ошибка ПО
7. Жалоба: «Приложение глючит у меня на телефоне, а ваш сайт, кажется, лежит, так что я совсем застрял!»
Категория: Ошибка ПО, Сбой сервиса
8. Жалоба: «Ваша программа делает мой компьютер жутко медленным, ненавижу её!»
Категория: Ошибка ПО
9. Жалоба: «Ваше дурацкое приложение всегда ломается, когда я пытаюсь что-то сделать с картинками».
Категория: Ошибка ПО
Теперь, пожалуйста, классифицируй следующую жалобу клиента:
<complaint>{complaint}</complaint>
Ответь только подходящими категориями и больше ничего не пиши.
Категория:
"""
evaluate_prompt(improved_prompt, eval_data, model_name=MODEL_NAME) # у авторов: 100.00%

С улучшенным промптом точность 100 %! Мы снова следуем стандартному циклу «промпт + эвал» со схемы из урока 1.

Помните: это очень простой эвал на очень маленьком наборе данных. Урок показывает общий процесс эвалов с проверкой кодом и не претендует на образец эвала продакшен-масштаба!

Подход работает, но писать всю логику оценки с нуля трудоёмко, а сравнивать результаты рядом неудобно. А что, если взять инструмент, который сам строит красивые таблицы и графики и легко прогоняет эвал на нескольких моделях? Как раз это — в следующем уроке: фреймворк для повторяемых и масштабируемых эвалов продакшен-уровня.

  1. Прогоните все три промпта урока 3 на двух моделях (qwen3:1.7b и qwen3:8b) и сведите таблицу «промпт × модель». Повторяется ли картина авторов: цепочка рассуждений выручает слабую модель сильнее, чем сильную?
  2. Расширьте набор урока 4 до 60 жалоб (можно попросить модель сгенерировать черновик, но эталоны проставьте сами!) и посчитайте точность обеих версий с интервалом и парное сравнение по уроку 8.4.1. Остаётся ли разница?
  3. Нормализация. Добавьте в calculate_accuracy очистку ответа: убрать «Категория:», кавычки и точки. Насколько изменилась точность «плохого» промпта? Какая часть его провалов была форматом, а не смыслом?
  4. Смешанная проверка. Для урока 4 напишите вторую метрику — «хотя бы одна категория совпала» — и сравните её с точным совпадением множеств. Когда в реальном продукте важнее одна, когда другая?
EnglishРусский
evaluation, evalоценка, эвал
benchmarkбенчмарк
customer / use-case evaluationэвал под свою задачу
test caseтест-кейс, пример для проверки
golden answerэталонный ответ
score / baseline scoreоценка / базовая оценка
human / code / model gradingоценка людьми / кодом / моделью
exact string matchточное совпадение строк
keyword presenceналичие ключевых слов
regressionрегресс (ухудшение после изменения)
WorkbenchWorkbench (песочница в консоли Anthropic)
chain of thoughtцепочка рассуждений
accuracyточность (доля верных ответов)