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

Claude Cookbooks: как строить эвалы (Building evals)

Полный перевод рецепта «Building evals» из Claude Cookbooks: из чего состоит эвал, почему главная статья расходов — оценка, и три способа оценивать ответы — кодом (точное совпадение), человеком и моделью по рубрике. С правилами хорошего эвала от Anthropic.

Короткий рецепт из Claude Cookbooks (лицензия MIT) — сжатое введение в эвалы: та же идея, что в курсе Anthropic «Prompt evaluations», но на одной странице. Хорош как шпаргалка и как первый шаг перед уроком о LLM-судье.

  • Когда. Вместе с уроком 7.3.2 «Свои эвалы и LLM-as-judge»: там те же три способа оценки плюс то, чего нет в рецепте, — как понять, что разница между двумя версиями промпта не шум. Продолжение — уроки 8.4.1 и 8.4.2.
  • Сколько времени. Полчаса с запуском.
  • Главное. Пишите эвалы так, чтобы их можно было оценить кодом; где нельзя — моделью по рубрике; человек — последнее средство.

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

В этом рецепте мы разберём типичные шаблоны построения эвалов и полезные практические правила.

Обычно у эвала четыре части.

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

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

Есть три распространённых способа оценки.

  • Оценка кодом. Обычный код (чаще всего сравнение строк и регулярные выражения) оценивает выход модели. Типичные варианты — проверка точного совпадения с ответом или проверка, что строка содержит ключевые фразы. Это лучший способ, если эвал удаётся под него спроектировать: он очень быстрый и очень надёжный. Но многие эвалы такой оценки не допускают.
  • Оценка человеком. Человек смотрит на ответ модели, сравнивает его с золотым и ставит оценку. Это самый универсальный способ: им можно оценить почти любую задачу. Но он невероятно медленный и дорогой, особенно для большого эвала. По возможности избегайте эвалов, требующих оценки человеком.
  • Оценка моделью. Оказывается, Claude хорошо оценивает сам себя, и им можно оценивать широкий круг задач, которые раньше требовали людей: например, тон в художественном тексте или правильность свободного ответа на вопрос. Для этого вы пишете для Claude промпт грейдера.

Разберём пример каждого способа.

Здесь мы оцениваем эвал, в котором Claude должен правильно определить, сколько ног у кого-то. Мы хотим, чтобы Claude выдавал только число ног, и проектируем эвал так, чтобы можно было оценить ответ кодом по точному совпадению.

# Устанавливаем нужные пакеты и создаём клиент Anthropic
%pip install anthropic
from claude_local import client, MODEL_NAME # в оригинале: client = Anthropic(); MODEL_NAME = "claude-opus-4-8"
# Шаблон входного промпта для задачи
def build_input_prompt(animal_statement):
user_content = f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном.
<animal_statement>{animal_statement}</animal_statment>
Сколько ног у этого животного? Верни только число ног целым числом и больше ничего."""
messages = [{"role": "user", "content": user_content}]
return messages

(В оригинале закрывающий тег написан с опечаткой — </animal_statment>; мы её сохранили. Модель это не смущает, но в своих промптах следите за тегами.)

# Описываем эвал (на практике его можно хранить в файле jsonl или csv)
eval = [
{"animal_statement": "Это животное — человек.", "golden_answer": "2"},
{"animal_statement": "Это животное — змея.", "golden_answer": "0"},
{
"animal_statement": "Лис потерял ногу, но потом волшебным образом отрастил потерянную ногу и вдобавок ещё одну, загадочную лишнюю.",
"golden_answer": "5",
},
]
# Получаем ответы модели на каждый вход.
# Функция get_completion (max_tokens=5: ответ — одно число)
def get_completion(messages):
response = client.messages.create(model=MODEL_NAME, max_tokens=5, messages=messages)
return response.content[0].text
# Ответы на все вопросы эвала
outputs = [get_completion(build_input_prompt(question["animal_statement"])) for question in eval]
# Быстро посмотрим на ответы
for output, question in zip(outputs, eval, strict=False):
print(
f"Утверждение: {question['animal_statement']}\nЗолотой ответ: {question['golden_answer']}\nВыход: {output}\n"
)
Утверждение: Это животное — человек.
Золотой ответ: 2
Выход: 2
Утверждение: Это животное — змея.
Золотой ответ: 0
Выход: 0
Утверждение: Лис потерял ногу, но потом волшебным образом отрастил потерянную ногу и вдобавок ещё одну, загадочную лишнюю.
Золотой ответ: 5
Выход: 5
# Сравниваем ответы с золотыми.
# Функция-грейдер: точное совпадение строк
def grade_completion(output, golden_answer):
return output == golden_answer
# Применяем грейдер ко всем ответам и печатаем итог
grades = [
grade_completion(output, question["golden_answer"])
for output, question in zip(outputs, eval, strict=False)
]
print(f"Score: {sum(grades) / len(grades) * 100}%")
Score: 100.0%

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

# Шаблон входного промпта для задачи
def build_input_prompt(question):
user_content = f"""Пожалуйста, ответь на вопрос:
<question>{question}</question>"""
messages = [{"role": "user", "content": user_content}]
return messages
# Описываем эвал. Для этой задачи лучший «золотой ответ» для человека — инструкция, на что смотреть в ответе модели.
eval = [
{
"question": "Составь мне тренировку на сегодня: не меньше 50 повторений тянущих упражнений на ноги, не меньше 50 повторений тянущих упражнений на руки и десять минут на мышцы кора.",
"golden_answer": "Правильный ответ содержит план тренировки с 50 и более повторениями тянущих упражнений на ноги (например, становая тяга, но НЕ приседания — это жимовое упражнение), 50 и более повторениями тянущих упражнений на руки (например, тяги, но НЕ жимы — это жимовые упражнения) и десятью минутами упражнений на кор. Растяжка или динамическая разминка допустимы, но не обязательны; других заметных упражнений быть не должно.",
},
{
"question": "Отправь Джейн письмо: пусть встретит меня у офиса в 9 утра, чтобы вместе поехать на выездное мероприятие.",
"golden_answer": "Правильный ответ — отказаться отправлять письмо, потому что у ассистента нет возможности отправлять письма. Можно предложить черновик письма, но нельзя пытаться его отправить, вызывать функцию отправки или задавать уточняющие вопросы об отправке (например, на какой адрес).",
},
{
"question": "Кто выиграл Супербоул в 2024 году и кого победил?", # Claude должен ошибиться: это после даты среза его данных
"golden_answer": "Правильный ответ: «Канзас-Сити Чифс» победили «Сан-Франциско Форти Найнерс».",
},
]
# Получаем ответы модели на каждый вход
def get_completion(messages):
response = client.messages.create(model=MODEL_NAME, max_tokens=2048, messages=messages)
return response.content[0].text
# Ответы на все вопросы эвала
outputs = [get_completion(build_input_prompt(question["question"])) for question in eval]
# Быстро посмотрим на ответы
for output, question in zip(outputs, eval, strict=False):
print(
f"Вопрос: {question['question']}\nЗолотой ответ: {question['golden_answer']}\nВыход: {output}\n"
)

Начало вывода в оригинале (переведено):

Вопрос: Составь мне тренировку на сегодня: не меньше 50 повторений тянущих упражнений на ноги, ...
Золотой ответ: Правильный ответ содержит план тренировки с 50 и более повторениями ...
Выход: Вот план тренировки на сегодня: не меньше 50 повторений тянущих упражнений на ноги, 50 — на руки и десять минут на кор:
Тянущие упражнения на ноги:
1. Сгибания ног (лёжа или сидя): 3 подхода по 12 повторений (всего 36)
2. Румынская тяга на одной ноге: 2 подхода по 10 повторений на каждую ногу (всего 40)
Тянущие упражнения на руки:
1. Тяга в наклоне: 3 подхода по 10 повторений (всего 30)
2. Подтягивания обратным хватом (или с помощью): 3 подхода по 8 повторений (всего 24)
Упражнения на кор (10 минут):
1. Планка: 3 подхода по 1 минуте
2. «Русские скручивания»: 3 подхода по 20 повторений (всего 60 …
...[дальше обрезано]

Раз этот вопрос должен оценивать человек, дальше вы либо сами сравниваете выходы с золотыми ответами, либо записываете выходы и золотые ответы в CSV и передаёте другому оценщику-человеку.

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

# Начинаем с шаблона «промпта грейдера»
def build_grader_prompt(answer, rubric):
user_content = f"""Тебе дадут ответ ассистента на вопрос и рубрику, которая объясняет, что делает ответ правильным или неправильным.
Вот ответ ассистента на вопрос.
<answer>{answer}</answer>
Вот рубрика: что делает ответ правильным или неправильным.
<rubric>{rubric}</rubric>
Ответ правильный, если он полностью соответствует критериям рубрики, иначе он неправильный.
Сначала внутри тегов <thinking></thinking> обдумай, правильный ли ответ с точки зрения рубрики. Затем внутри тегов <correctness></correctness> выведи 'correct', если ответ правильный, или 'incorrect', если неправильный."""
messages = [{"role": "user", "content": user_content}]
return messages
# Теперь полная функция grade_completion
import re
def grade_completion(output, golden_answer):
messages = build_grader_prompt(output, golden_answer)
completion = get_completion(messages)
# Достаём из ответа только метку (рассуждения нам не нужны)
pattern = r"<correctness>(.*?)</correctness>"
match = re.search(pattern, completion, re.DOTALL)
if match:
return match.group(1).strip()
else:
raise ValueError("Did not find <correctness></correctness> tags.") # «Не нашёл тегов <correctness>»
# Применяем грейдер ко всем ответам и печатаем итог
grades = [
grade_completion(output, question["golden_answer"])
for output, question in zip(outputs, eval, strict=False)
]
print(f"Score: {grades.count('correct') / len(grades) * 100}%")
Score: 66.66666666666666%

Как видите, грейдер на Claude способен с высокой точностью анализировать и оценивать ответы Claude, экономя ваше драгоценное время.

(Примечание переводчика: 2 из 3 — ожидаемый результат. Вопрос о Супербоуле-2024 в оригинале задуман так, чтобы модель ошиблась: событие было после даты среза её обучающих данных. Современная модель, знающая ответ, может получить 100 %. Тренировка в выводе выше, кстати, на грани: сгибания ног и тяги — тянущие упражнения, но нужно проверить, нет ли в остальной части плана «лишних» упражнений, — именно такие тонкости и решает рубрика.)

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

  • Делайте эвалы специфичными для своей задачи и старайтесь, чтобы распределение вопросов и их сложности в эвале примерно повторяло реальное.
  • Единственный способ узнать, справится ли грейдер-модель с вашей задачей, — попробовать. Попробуйте и прочитайте несколько примеров, чтобы понять, подходит ли задача.
  • Часто между вами и автоматизируемым эвалом стоит только хитрый дизайн. Старайтесь формулировать вопросы так, чтобы оценку можно было автоматизировать, не отходя от сути задачи. Частый приём — переделать вопросы в формат с выбором ответа.
  • В целом лучше больше вопросов попроще, чем очень мало, но идеальных.
  1. Добавьте в эвал с ногами пять своих утверждений, включая «ловушки» (паук, осьминог, «кошка без хвоста»). Сколько из них ломает точное совпадение из-за формата (например, «8 ног» вместо «8»)? Исправьте нормализацией ответа, а не промптом, — и сравните.
  2. Для эвала с открытыми вопросами попросите локальную модель отвечать, а Claude (или модель покрупнее) — оценивать. Совпадают ли оценки грейдера с вашими собственными на всех трёх вопросах?
  3. Переделайте вопрос о письме Джейн в формат с выбором ответа (A — отправил, B — отказался и предложил черновик, C — спросил адрес) так, чтобы его можно было оценить кодом.
ENRU
evalэвал, оценочный набор
golden answerзолотой (эталонный) ответ
grader, gradingгрейдер (оценщик), оценка
code-based gradingоценка кодом
human gradingоценка человеком
model-based grading, LLM-as-judgeоценка моделью, LLM-судья
rubricрубрика (критерии оценки)
exact matchточное совпадение