Курс Anthropic: оценка промптов, часть 2 — эвалы в promptfoo (Prompt evaluations, уроки 5–9)
Перевод уроков 5–9 курса Prompt evaluations из anthropics/courses: инструмент promptfoo, эвал с точным совпадением и трансформацией ответа, классификация с contains-all, свой грейдер на Python, LLM-судья через llm-rubric и своя рубрика с оценкой по трём критериям. Конфиги даны и для локальной модели в Ollama.
Вторая часть перевода курса Prompt evaluations. В первой части эвалы писались вручную на Python; здесь тот же цикл «промпт → эвал → правка» переносится в promptfoo — открытый инструмент, который сам прогоняет промпты по тест-кейсам и моделям, проверяет ответы и показывает результаты таблицей в браузере. Последние два урока — оценка ответов другой моделью (LLM-судья): встроенной рубрикой и своей функцией на Python.
Как читать вместе с курсом
Заголовок раздела «Как читать вместе с курсом»- Когда. После первой части и урока 7.3.2. Уроки 8–9 — практика к разделу «LLM-as-judge» урока 7.3.2; там же — смещения судьи и проверка судьи по людям, о которых курс почти не говорит. Регрессионный прогон эвалов в CI — урок 8.4.2.
- Сколько времени. Около трёх часов; урок 9 с восемью длинными статьями на локальной модели идёт долго.
- Что понадобится. Node.js 20+ (для
npx promptfoo), Python 3 (promptfoo вызывает из него наши функции), Ollama с моделямиqwen3:1.7b,qwen3:8bи, для роли судьи,qwen3:14b.
Подготовка
Заголовок раздела «Подготовка»Этого раздела в оригинале нет.
-
Скачайте папки уроков. Каждый урок курса — отдельная папка с конфигом и данными:
Окно терминала git clone --depth 1 https://github.com/anthropics/courses.gitcd courses/prompt_evaluations/05_prompt_foo_code_graded_animals -
Запустите Ollama с длинным контекстом — промпты урока 9 содержат статьи Википедии на тысячи слов:
Окно терминала OLLAMA_CONTEXT_LENGTH=32768 ollama serveollama pull qwen3:1.7b && ollama pull qwen3:8b && ollama pull qwen3:14b -
Проверьте promptfoo:
npx promptfoo@latest --version. Первая загрузка займёт минуту.
Провайдер Ollama в promptfoo записывается так (по документации promptfoo):
providers: - id: ollama:chat:qwen3:8b config: think: false # Qwen3 по умолчанию «думает»; без этого размышления попадут в ответ num_ctx: 32768 # окно контекста для этой модели temperature: 0Урок 5. Знакомство с promptfoo
Заголовок раздела «Урок 5. Знакомство с promptfoo»Примечание: урок лежит в папке с файлами кода. Чтобы повторить эвал, скачайте папку целиком.
Мы видели, как писать эвалы с нуля, — это работает, хотя и громоздко. На практике удобнее специализированные инструменты. Их сейчас много, и новые появляются постоянно: promptfoo, Vellum, Scale Evaluation, Prompt Layer, ChainForge и многие другие.
Один из открытых и простых вариантов — promptfoo. Он даёт готовую инфраструктуру для пакетного тестирования, контроля версий и аналитики, так что разработчики занимаются промптами, а не поддержкой самописного тестового фреймворка. С ним легко прогонять эвалы по многим промптам, моделям и провайдерам и наглядно сравнивать результаты. После прогона promptfoo строит дашборд с таблицей «тест-кейсы × промпты» и сводкой процентов (в оригинале — скриншот).
Первый эвал в promptfoo
Заголовок раздела «Первый эвал в promptfoo»Возьмём эвал «сколько ног у животного» из урока 3 и перенесём в promptfoo набор данных и три промпта. Сам эвал нарочно простой: здесь важны инструмент и процесс.
Установка
Заголовок раздела «Установка»Перейдите в папку, где будет код эвала, и выполните:
npx promptfoo@latest initКоманда создаст файл promptfooconfig.yaml — в нём «вся магия». В нём настраиваются:
- провайдеры (какие модели использовать в эвалах);
- промпты, которые мы оцениваем;
- тесты, которые запускаем.
Провайдеры
Заголовок раздела «Провайдеры»В поле providers указываем одну или несколько моделей. В оригинале — строки вида
anthropic:messages:claude-3-haiku-20240307 (тогда поддерживались Claude 3 Haiku, Sonnet, Opus, Claude 3.5 Sonnet и
старые Claude 2.x и Instant 1.2). Удалите содержимое promptfooconfig.yaml и замените его таким:
description: "Эвал: ноги животных"
providers: - id: ollama:chat:qwen3:1.7b # в оригинале: "anthropic:messages:claude-3-haiku-20240307" config: {think: false, temperature: 0}description— необязательная подпись: что оцениваем.providers— какой моделью прогонять эвал. Моделей может быть несколько — увидим в конце урока.
Для моделей Claude promptfoo ищет переменную окружения ANTHROPIC_API_KEY:
export ANTHROPIC_API_KEY=your_api_key_hereПромпты
Заголовок раздела «Промпты»Промпты можно задать разными способами: текстом прямо в YAML, из файла JSON, текстового файла, другого YAML или файла
Python. Авторы предпочитают держать все промпты в одном файле Python — каждый промпт функцией, возвращающей строку.
Создайте файл prompts.py:
# promptfoo вызывает функцию с одним аргументом — словарём context,# переменные теста лежат в context["vars"]
def simple_prompt(context): animal_statement = context["vars"]["animal_statement"] return f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном. <animal_statement>{animal_statement}</animal_statement>
Сколько ног у этого животного? Ответь числом"""
def better_prompt(context): animal_statement = context["vars"]["animal_statement"] return f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном. <animal_statement>{animal_statement}</animal_statement>
Сколько ног у этого животного? Ответь только одним числом цифрами, например 2 или 9"""Теперь скажем конфигу, что промпты берутся из prompts.py:
description: "Эвал: ноги животных"
prompts: - file://prompts.py:simple_prompt # в оригинале без file:// — так тоже работало - file://prompts.py:better_prompt
providers: - id: ollama:chat:qwen3:1.7b config: {think: false, temperature: 0}Каждая функция из prompts.py — отдельная строка. Мы сказали promptfoo, что хотим оценить оба промпта.
Тесты в promptfoo задаются многими способами; начнём с самого распространённого — CSV-файла. В нём можно сразу указать логику проверки. Встроенные проверки (assertions) мы увидим в следующих уроках, а здесь нужно лишь точное совпадение ответа модели с ожидаемым числом ног. Для этого у CSV два столбца:
animal_statement— входное утверждение вроде «Животное — слон»;__expected— ожидаемый правильный ответ (обратите внимание на два подчёркивания: это синтаксис promptfoo).
Создайте файл animal_legs_tests.csv:
animal_statement,__expected"Животное — человек.","2""Животное — змея.","0""Лис потерял лапу, но потом чудесным образом отрастил потерянную лапу и сверх того ещё одну, загадочную.","5""Животное — собака.","4""Животное — кошка с двумя лишними лапами.","6""Животное — слон.","4""Животное — птица.","2""Животное — рыба.","0""Животное — паук с двумя лишними ногами","10""Животное — осьминог.","8""Животное — осьминог, который потерял две ноги, а потом отрастил три.","9""Животное — двухголовое восьминогое мифическое существо.","8"И подключим его в конфиг:
description: "Эвал: ноги животных"
prompts: - file://prompts.py:simple_prompt - file://prompts.py:better_prompt
providers: - id: ollama:chat:qwen3:1.7b config: {think: false, temperature: 0}
tests: file://animal_legs_tests.csvЗапуск эвала
Заголовок раздела «Запуск эвала»Провайдер, промпты и тесты заданы — запускаем:
npx promptfoo@latest evalДля каждого промпта promptfoo:
- берёт каждое
animal_statementиз CSV; - собирает полный промпт с этим утверждением;
- отправляет запрос модели;
- проверяет, совпадает ли ответ с ожидаемым из CSV.
Результаты печатаются в терминале таблицей. У авторов (Claude 3 Haiku): левый столбец — утверждение, средний —
ответы и оценки simple_prompt, который проваливает все тест-кейсы, правый — better_prompt, который проходит
большинство, кроме логически сложных.
Просмотр результатов
Заголовок раздела «Просмотр результатов»promptfoo умеет запускать в браузере дашборд для изучения результатов:
npx promptfoo@latest viewКоманда спросит, запустить ли сервер (ответьте y), и откроет страницу. Вверху — главная сводка (проценты по
каждому промпту). Можно разобраться в конкретных результатах: нажмите на значок лупы в ячейке — откроется окно с
ответом и подробностями проверки. Для simple_prompt видно, что модель ответила правильно (например, 0), но
добавила лишний пояснительный текст, из-за которого провалила точное совпадение. better_prompt отвечает одной
цифрой вроде 5 или 0, но ошибается на утверждениях, где нужно рассуждать, — например, про лиса, отрастившего лишнюю
лапу.
Третий промпт
Заголовок раздела «Третий промпт»В уроке о проверке кодом лучший результат дала цепочка рассуждений. Добавим в prompts.py третий промпт:
def chain_of_thought_prompt(context): animal_statement = context["vars"]["animal_statement"] return f"""Тебе дадут утверждение о животном, и твоя задача — определить, сколько у этого животного ног.
Вот утверждение о животном. <animal_statement>{animal_statement}</animal_statement>
Сколько ног у этого животного? Сначала порассуждай о количестве ног у животного, думая по шагам внутри тегов <thinking>. Затем выведи итоговый ответ внутри тегов <answer>. Внутри тегов <answer> напиши только количество ног целым числом и больше ничего."""Перед запуском нужно решить одну проблему: новый промпт отвечает с тегами <thinking> и <answer>. Чтобы честно
оценить его, нужно достать число из тегов <answer> и сравнить с ожидаемым. promptfoo позволяет задавать свои
трансформации (transforms), которые меняют ответ модели перед проверкой. Создайте файл transform.py:
def get_transform(output, context): if "<thinking>" in output: # ответ промпта с цепочкой рассуждений try: return output.split("<answer>")[1].split("</answer>")[0].strip() except Exception as e: print(f"Ошибка в get_transform: {e}") return output return output # ответы остальных промптов не трогаемФункция get_transform получает ответ модели (о параметре context — в следующих уроках) и может превратить его во
что угодно. Здесь два случая:
- если в ответе есть теги
<thinking>, это ответ промпта с рассуждением — вытаскиваем число из<answer>; - иначе возвращаем ответ как есть (для промптов без рассуждения).
Осталось сказать promptfoo, что трансформацию нужно применять ко всем тестам:
description: "Эвал: ноги животных"
prompts: - file://prompts.py:simple_prompt - file://prompts.py:better_prompt - file://prompts.py:chain_of_thought_prompt
providers: - id: ollama:chat:qwen3:1.7b config: {think: false, temperature: 0}
tests: file://animal_legs_tests.csv
defaultTest: options: transform: file://transform.pyБлок в конце применяет функцию из transform.py ко всем тестам; по умолчанию promptfoo ищет в файле функцию
get_transform. Снова запускаем npx promptfoo@latest eval и npx promptfoo@latest view. Теперь в таблице четыре
столбца, и у авторов промпт с цепочкой рассуждений ответил верно на 100 % вопросов!
Сравнение моделей
Заголовок раздела «Сравнение моделей»promptfoo позволяет легко прогнать эвал на разных моделях. Чтобы промпт набрал 100 % на Haiku, понадобилась работа над промптом. А что будет на более сильной модели? Достаточно добавить второго провайдера:
description: "Эвал: ноги животных"
prompts: - file://prompts.py:simple_prompt - file://prompts.py:better_prompt - file://prompts.py:chain_of_thought_prompt
providers: # в оригинале: anthropic:messages:claude-3-haiku-20240307 и anthropic:messages:claude-3-5-sonnet-20240620 - id: ollama:chat:qwen3:1.7b config: {think: false, temperature: 0} - id: ollama:chat:qwen3:8b config: {think: false, temperature: 0}
tests: file://animal_legs_tests.csv
defaultTest: options: transform: file://transform.pyОдна строка в YAML — и эвал идёт на двух моделях. В дашборде авторов первые три столбца — ответы Claude 3 Haiku,
последние три — Claude 3.5 Sonnet. Sonnet прошёл эвал на 100 % даже с simple_prompt, который на Haiku набрал 0 %.
Такая информация бесценна: важно не только, какой промпт лучше, но и какая связка «модель + промпт» лучше для задачи.
Замечание на полях. Почему Claude 3.5 Sonnet не набрал 100 % с цепочкой рассуждений? Он ошибся на утверждении
«Животное — осьминог»: в <thinking> модель рассудила, что ног у осьминога нет — его конечности называют «руками»,
но не «ногами». Перейдя на «более умную» модель, мы получили чуть худший результат с этим промптом: модель
оказалась «слишком умной». Чтобы все модели отвечали одинаково, можно уточнить в промпте, что считается «ногой».
В этом уроке мы лишь попробовали promptfoo. Дальше — более сложная логика проверки кодом, свои грейдеры и оценка моделью.
Урок 6. Классификация в promptfoo
Заголовок раздела «Урок 6. Классификация в promptfoo»Примечание: урок лежит в папке с файлами кода.
В первой части мы оценивали промпты, которые относят жалобы клиентов — например, «Как только я открываю ваше приложение, телефон начинает жутко тормозить» или «Не могу понять, как сменить пароль» — к пяти категориям: Ошибка ПО, Неисправность оборудования, Ошибка пользователя, Сбой сервиса, Запрос функции. Перенесём этот эвал в promptfoo.
Подготовка
Заголовок раздела «Подготовка»Как и раньше, начинаем с npx promptfoo@latest init и очищаем promptfooconfig.yaml. Провайдер — маленькая модель:
эвал мы прогоним несколько раз (в оригинале по той же причине выбран дешёвый Claude 3 Haiku):
description: "Эвал: классификация жалоб"
providers: - id: ollama:chat:qwen3:1.7b # в оригинале: "anthropic:messages:claude-3-haiku-20240307" config: {think: false, temperature: 0}Промпты
Заголовок раздела «Промпты»Как в прошлом уроке: каждый промпт — функция Python, возвращающая строку, все функции — в prompts.py. Это те же
два промпта, что в уроке 4:
def basic_prompt(context): complaint = context["vars"]["complaint"] return f""" Отнеси следующую жалобу клиента к одной или нескольким из этих категорий: Ошибка ПО, Неисправность оборудования, Ошибка пользователя, Запрос функции, Сбой сервиса. Ответь только категорией.
Жалоба: {complaint}
Категория: """
def improved_prompt(context): complaint = context["vars"]["complaint"] return f""" Ты — ИИ-ассистент, специализирующийся на классификации обращений в службу поддержки. Твоя задача — анализировать жалобы клиентов и относить их к одной или нескольким из следующих категорий:
1. Ошибка ПО: программа работает не так, как задумано. 2. Неисправность оборудования: проблемы с физическими устройствами или компонентами. 3. Ошибка пользователя: трудности из-за того, что пользователь не понял или неправильно использовал продукт. 4. Запрос функции: предложения новых возможностей или улучшений. 5. Сбой сервиса: общесистемные проблемы, влияющие на доступность сервиса.
Важные правила: - Жалоба может относиться к нескольким категориям. Тогда перечисли все подходящие, но по возможности старайся выбрать одну.
Примеры: 1. Жалоба: «Приложение вылетает, когда я пытаюсь сохранить прогресс». Категория: Ошибка ПО
2. Жалоба: «Клавиатура не работает после того, как я пролил на неё кофе». Категория: Неисправность оборудования
3. Жалоба: «Не могу найти на вашем сайте кнопку входа». Категория: Ошибка пользователя
4. Жалоба: «Было бы здорово, если бы в вашем приложении была тёмная тема». Категория: Запрос функции
5. Жалоба: «Ни один ваш сервис не загружается ни у меня, ни у коллег». Категория: Сбой сервиса
6. Жалоба: «Приложение ломается каждый раз, когда я пытаюсь сменить фото профиля». Категория: Ошибка ПО
7. Жалоба: «Приложение глючит у меня на телефоне, а ваш сайт, кажется, лежит, так что я совсем застрял!» Категория: Ошибка ПО, Сбой сервиса
8. Жалоба: «Ваша программа делает мой компьютер жутко медленным, ненавижу её!» Категория: Ошибка ПО
9. Жалоба: «Ваше дурацкое приложение всегда ломается, когда я пытаюсь что-то сделать с картинками». Категория: Ошибка ПО
Теперь, пожалуйста, классифицируй следующую жалобу клиента:
<complaint>{complaint}</complaint>
Ответь только подходящими категориями и больше ничего не пиши. Категория: """Подключаем промпты в конфиг:
description: "Эвал: классификация жалоб"
prompts: - file://prompts.py:basic_prompt - file://prompts.py:improved_prompt
providers: - id: ollama:chat:qwen3:1.7b config: {think: false, temperature: 0}Тестовый набор
Заголовок раздела «Тестовый набор»Остаётся привести набор данных из урока 4 к виду, удобному для promptfoo. Главное отличие от прошлого урока: проверка — уже не точное совпадение. Нужно, чтобы ответ модели содержал правильную категорию (или категории). Например, для жалобы «Облачное хранилище не синхронизируется, и я не вижу свои файлы с других устройств» ответ должен содержать и «Ошибка ПО», и «Сбой сервиса» — а модель может назвать их в любом порядке.
К счастью, в promptfoo много встроенных проверок, например:
contains— ответ содержит подстроку;contains-all— ответ содержит все подстроки из списка;contains-any— ответ содержит хотя бы одну из подстрок;contains-json— ответ содержит корректный JSON (с необязательной проверкой по JSON Schema);contains-sql— ответ содержит корректный SQL;contains-xml— ответ содержит корректный XML;equals— ответ совпадает в точности;icontains,icontains-all,icontains-any— то же без учёта регистра;regex— ответ соответствует регулярному выражению;- и многие другие (полный список встроенных проверок).
Возьмём contains-all: ответ должен содержать все нужные категории. В столбце __expected CSV можно указать любую
встроенную проверку. Создайте dataset.csv:
complaint,__expected"Приложение вылетает каждый раз, когда я пытаюсь загрузить фото","contains-all:Ошибка ПО""Компьютер не видит мой принтер","contains-all:Неисправность оборудования""Не могу понять, как сменить пароль","contains-all:Ошибка пользователя""Сайт полностью лёг, не открывается ни одна страница","contains-all:Сбой сервиса""Было бы здорово, если бы в приложении появилась тёмная тема","contains-all:Запрос функции""Программа зависает, когда я пытаюсь сохранить большие файлы","contains-all:Ошибка ПО""Беспроводная мышь не работает даже с новыми батарейками","contains-all:Неисправность оборудования""Я случайно удалил важные файлы, поможете восстановить?","contains-all:Ошибка пользователя""Ни один ваш сервер не отвечает, у вас авария?","contains-all:Сбой сервиса""Можно добавить экспорт данных в CSV?","contains-all:Запрос функции""Приложение вылетает, а телефон перегревается","contains-all:Ошибка ПО,Неисправность оборудования""Я не помню свой пароль!","contains-all:Ошибка пользователя""Новое обновление что-то сломало, и приложение у меня больше не работает","contains-all:Ошибка ПО""Кажется, я что-то неправильно установил, и теперь компьютер вообще не включается","contains-all:Ошибка пользователя,Неисправность оборудования""Ваш сервис лежит, а мне срочно нужна функция пакетной обработки файлов","contains-all:Сбой сервиса,Запрос функции""Видеокарта издаёт странные звуки","contains-all:Неисправность оборудования""Клавиатура ни с того ни с сего совсем перестала работать","contains-all:Неисправность оборудования""Как только я открываю ваше приложение, телефон начинает жутко тормозить","contains-all:Ошибка ПО""Можно сделать интерфейс удобнее? Я вечно теряюсь в меню","contains-all:Запрос функции,Ошибка пользователя""Облачное хранилище не синхронизируется, и я не вижу свои файлы с других устройств","contains-all:Ошибка ПО,Сбой сервиса"В CSV два столбца: complaint — сама жалоба, __expected — проверка contains-all. Например, строка про сервис и
пакетную обработку требует, чтобы ответ модели содержал и «Сбой сервиса», и «Запрос функции».
Последний шаг — подключить тесты в promptfooconfig.yaml:
description: "Эвал: классификация жалоб"
prompts: - file://prompts.py:basic_prompt - file://prompts.py:improved_prompt
providers: - id: ollama:chat:qwen3:1.7b config: {think: false, temperature: 0}
tests: file://dataset.csvnpx promptfoo@latest evalnpx promptfoo@latest viewУ авторов улучшенный промпт с примерами заметно лучше простого: примеры помогают модели понять, в каких ситуациях
нужно назначать несколько категорий. В веб-просмотре basic_prompt набрал 80 %, improved_prompt — 100 %.
Как всегда, помните: учебные наборы данных маленькие и не отражают реальных эвалов. Авторы всегда советуют не меньше 100 строк.
Урок 7. Свои грейдеры на Python
Заголовок раздела «Урок 7. Свои грейдеры на Python»Примечание: урок лежит в папке с файлами кода.
Мы видели встроенные грейдеры promptfoo вроде точного совпадения и contains-all. Они часто полезны, но promptfoo
позволяет писать и свою логику проверки для более специфичных задач. Для демонстрации возьмём очень простой шаблон
промпта:
Напиши короткий абзац про {{topic}}. Упомяни слово «{{topic}}» ровно {{count}} раз, не больше и не меньше.
{{topic}} и {{count}} заполняются значениями вроде «пальто» и 7. Чтобы проверить ответ, нужна своя логика: в
ответе слово «пальто» должно встретиться ровно 7 раз.
Подготовка
Заголовок раздела «Подготовка»Как всегда, npx promptfoo@latest init и очищаем конфиг. Провайдеров два — сравним, как маленькая и большая модели
справятся с задачей:
description: Подсчёт упоминаний
providers: # в оригинале: anthropic:messages:claude-3-haiku-20240307 и anthropic:messages:claude-3-5-sonnet-20240620 - id: ollama:chat:qwen3:1.7b config: {think: false} - id: ollama:chat:qwen3:8b config: {think: false}Промпт прямо в YAML
Заголовок раздела «Промпт прямо в YAML»Мы писали промпты функциями в файле Python — авторы рекомендуют этот способ, но есть и другие. Самый простой — написать промпт текстом прямо в YAML:
description: Подсчёт упоминанийprompts: - >- Напиши короткий абзац про {{topic}}. Упомяни слово «{{topic}}» ровно {{count}} раз, не больше и не меньше. Пиши только строчными буквами.providers: - id: ollama:chat:qwen3:1.7b config: {think: false} - id: ollama:chat:qwen3:8b config: {think: false}Обратите внимание на переменные {{topic}} и {{count}} в двойных фигурных скобках: это синтаксис шаблонов
Nunjucks, он скоро пригодится.
Тест-кейсы прямо в YAML
Заголовок раздела «Тест-кейсы прямо в YAML»Раньше тесты и логика проверки лежали в CSV. Но их можно описать и прямо в конфиге:
description: Подсчёт упоминанийprompts: - >- Напиши короткий абзац про {{topic}}. Упомяни слово «{{topic}}» ровно {{count}} раз, не больше и не меньше. Пиши только строчными буквами.providers: - id: ollama:chat:qwen3:1.7b config: {think: false} - id: ollama:chat:qwen3:8b config: {think: false}tests: - vars: topic: кенгуру count: 3 - vars: topic: какаду count: 2 - vars: topic: метро count: 4 - vars: topic: пальто count: 7 - vars: topic: такси count: 6Внизу — 5 тест-кейсов, у каждого свои topic и count. promptfoo прогонит каждый тест, подставив значения в шаблон.
Логики проверки пока нет, но эвал уже можно запустить, чтобы убедиться, что переменные подставляются верно:
npx promptfoo@latest eval. В каждой строке — абзацы на заданную тему. Осталось реализовать проверку числа
упоминаний.
Свой грейдер
Заголовок раздела «Свой грейдер»promptfoo позволяет определять свои функции-грейдеры на Python. Создайте файл count.py:
import re
def get_assert(output, context): topic = context["vars"]["topic"] goal_count = int(context["vars"]["count"]) # слово целиком: в начале текста или после пробела pattern = fr'(^|\s)\b{re.escape(topic)}\b'
actual_count = len(re.findall(pattern, output.lower()))
pass_result = goal_count == actual_count
result = { "pass": pass_result, "score": 1 if pass_result else 0, "reason": f"Ожидалось, что «{topic}» встретится {goal_count} раз. На деле: {actual_count}", } return resultpromptfoo сам ищет в файле функцию get_assert и передаёт ей два аргумента:
- ответ модели;
- словарь
contextс переменными и промптом, который породил ответ.
Функция может вернуть:
bool(прошёл / не прошёл);float(оценку);- словарь GradingResult.
Мы возвращаем словарь GradingResult с ключами pass (булево значение), score (число) и reason (строка с
объяснением). Функция достаёт из context тему и число, регулярным выражением считает вхождения темы в ответ и
возвращает результат.
Подключаем грейдер в конфиг:
description: Подсчёт упоминанийprompts: - >- Напиши короткий абзац про {{topic}}. Упомяни слово «{{topic}}» ровно {{count}} раз, не больше и не меньше. Пиши только строчными буквами.providers: - id: ollama:chat:qwen3:1.7b config: {think: false} - id: ollama:chat:qwen3:8b config: {think: false}defaultTest: assert: - type: python value: file://count.pytests: - vars: {topic: кенгуру, count: 3} - vars: {topic: какаду, count: 2} - vars: {topic: метро, count: 4} - vars: {topic: пальто, count: 7} - vars: {topic: такси, count: 6}defaultTest говорит promptfoo, что к каждому тесту применяется грейдер из count.py.
npx promptfoo@latest eval, затем npx promptfoo@latest view. У авторов Claude 3.5 Sonnet набрал 100 %, а Claude 3
Haiku — 20 %. Чтобы убедиться, нажмите на лупу и посмотрите на промпт и ответ: у Haiku в абзаце про пинцет (tweezers)
слово встречается не 7 раз, у Sonnet — ровно 7.
Этот эвал немного нелеп, но он показывает, как определить свою логику проверки на Python. Встроенные проверки promptfoo и свои функции-грейдеры вместе позволяют написать практически любой эвал с проверкой кодом.
Урок 8. Оценка моделью в promptfoo
Заголовок раздела «Урок 8. Оценка моделью в promptfoo»Примечание: урок лежит в папке с файлами кода.
До сих пор мы писали только эвалы с проверкой кодом. Там, где это возможно, они самые простые и дешёвые: дают ясную объективную оценку по заранее заданным критериям и идеальны для задач с чёткими измеримыми результатами. Беда в том, что код может проверить лишь некоторые ответы — те, что сводятся к точному совпадению, сравнению чисел или другой программной логике.
Но многие реальные применения языковых моделей требуют более тонкой оценки. Допустим, мы делаем чат-бота для средней школы. Нужно проверить, что ответы написаны языком по возрасту, выдерживают учебный тон, не отвечают на неучебные вопросы и объясняют на уровне, понятном школьникам. Эти критерии субъективны и зависят от контекста, их трудно проверить кодом. Здесь помогает оценка моделью!
Оценка моделью использует способности языковых моделей, чтобы проверять ответы по сложным, тонким критериям. Беря другую модель в качестве оценщика, мы получаем то же понимание языка и контекста, которое породило исходный ответ. Так можно строить более сложные метрики: тон, уместность, соответствие ситуации и даже креативность — то, что обычно недоступно проверке кодом.
Главная идея: сама оценка — это задача обработки естественного языка. Модели-оценщику дают некоторое сочетание:
- исходного промпта или вопроса;
- ответа модели, который нужно оценить;
- набора критериев или правил оценки;
- инструкций, как оценивать и ставить балл.
Так оценка становится целостной: учитывает не только фактическую точность, но и стиль, следование правилам и общее качество ответа в его контексте. Типичные вопросы к модели-оценщику:
- Насколько этот ответ извиняющийся?
- Фактически ли точен ответ с учётом предоставленного контекста?
- Не слишком ли ответ упоминает свой контекст и информацию?
- Действительно ли ответ отвечает на вопрос как следует?
- Насколько ответ соответствует нашим правилам тона, бренда и стиля?
Оценка моделью в promptfoo
Заголовок раздела «Оценка моделью в promptfoo»Как и всё в promptfoo, оценку моделью можно сделать по-разному. Здесь — самый простой способ: встроенная проверка. В следующем уроке напишем свою функцию-оценщик.
Встроенная проверка llm-rubric — универсальный грейдер promptfoo для подхода «LLM как судья». Достаточно добавить в
promptfooconfig.yaml:
assert: - type: llm-rubric # модель-оценщик (в оригинале: anthropic:messages:claude-3-opus-20240229) provider: id: ollama:chat:qwen3:14b config: {think: false} # критерий, по которому оценивается ответ value: Не извиняетсяЭта проверка попросит модель-оценщика решить, извиняется ли ответ.
Свой эвал
Заголовок раздела «Свой эвал»Оценим промпты для учебного помощника школьников средних классов. Чат-бот должен отвечать на вопросы по школьным предметам, но не отвечать на посторонние. Начнём с простого промпта:
Ты — интерактивный помощник-репетитор для учеников средней школы.Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы.Отвечай только на вопросы, связанные со школьной учёбой.Вот вопрос ученика: {{question}}Напишем эвал с оценкой моделью, чтобы проверить, что помощник отказывается отвечать на вопросы вроде «Как уговорить родителей купить мне телефон?» или «Какие есть весёлые бесплатные видеоигры?». Теоретически можно проверять кодом: искать регулярными выражениями фразы отказа вроде «Извините» или «Не могу помочь». Но это очень хрупкий подход; оценка моделью работает гораздо лучше.
Базовая конфигурация
Заголовок раздела «Базовая конфигурация»Как всегда, в новой папке выполняем npx promptfoo@latest init. Для простоты промпты и тесты пишем прямо в
promptfooconfig.yaml:
description: "Эвал: школьный помощник"
prompts: - >- Ты — интерактивный помощник-репетитор для учеников средней школы. Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы. Отвечай только на вопросы, связанные со школьной учёбой. Вот вопрос ученика: {{question}}
providers: - id: ollama:chat:qwen3:8b # в оригинале: anthropic:messages:claude-3-5-sonnet-20240620 config: {think: false}
defaultTest: assert: - type: llm-rubric provider: # в оригинале: anthropic:messages:claude-3-opus-20240229 id: ollama:chat:qwen3:14b config: {think: false} value: 'Отказывается отвечать на вопрос и вместо этого переводит разговор на учебные темы'
tests: - vars: question: Какая лучшая бесплатная мобильная игра? - vars: question: Как уговорить родителей разрешить мне ложиться спать позже? - vars: question: Какие есть весёлые идеи для темы пижамной вечеринки? - vars: question: Какие есть хорошие идеи для розыгрышей, за которые мне не влетит? - vars: question: Какие известные футболисты-левши ты знаешь?Что здесь есть:
prompts— простой исходный промпт, который мы оцениваем. Обычно промпты лежат в файле Python, но можно писать их прямо в конфиге;providers— модель, которая отвечает на вопросы;defaultTest— каждый тест оценивается проверкойllm-rubric: ответ должен отказывать и переводить разговор на учебные темы. Оценивает модель посильнее (в оригинале — Claude 3 Opus при отвечающей Claude 3.5 Sonnet);tests— набор тестов. Обычно их держат в CSV, но можно и прямо в YAML. Все тесты содержат вопросы, на которые модель должна отказаться отвечать.
Запускаем npx promptfoo@latest eval. У авторов промпт справился с большинством примеров (хотя набор очень мал), но
охотно ответил на вопрос о футболистах. В веб-просмотре видно и ответ модели, и обоснование модели-оценщика.
Второй промпт
Заголовок раздела «Второй промпт»Добавим второй, более подробный промпт — с перечнем тем, которые модель может обсуждать:
prompts: - >- Ты — интерактивный помощник-репетитор для учеников средней школы. Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы. Отвечай только на вопросы, связанные со школьной учёбой. Вот вопрос ученика: {{question}} - >- Ты — интерактивный помощник-репетитор для учеников средней школы. Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы. Отвечай только на вопросы, связанные со школьной учёбой. Допустимые темы, на которые нужно отвечать: математика, чтение, естественные науки, иностранные языки, обществознание и искусство. Отказывайся отвечать на вопросы, не связанные с этими темами в учебном контексте. Вот вопрос ученика: {{question}}Остальная часть конфига прежняя. Запускаем снова: у авторов изменения во втором промпте помогли! Учтите, что набор слишком мал для реального использования.
Оценка извинений
Заголовок раздела «Оценка извинений»Присмотревшись к ответам, замечаем: большинство начинается с извинений вроде «Извини» или «Прошу прощения». Для пользователей это не лучший опыт, и мы решили это исправить. Оценим третий промпт:
Ты — интерактивный помощник-репетитор для учеников средней школы.Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы.Отвечай только на вопросы, связанные со школьной учёбой.Допустимые темы, на которые нужно отвечать: математика, чтение, естественные науки, иностранные языки, обществознание и искусство.Отказывайся отвечать на вопросы, не связанные с этими темами в учебном контексте.Не извиняйся и не используй извиняющийся тон при отказе. Вместо этого мягко подталкивай ученика к школьным темам.Вот вопрос ученика: {{question}}Промпт прямо запрещает извиняться и просит мягко направлять учеников к учебным темам. Добавим вторую проверку
llm-rubric — на извиняющийся тон. Полный конфиг:
description: "Эвал: школьный помощник"
prompts: - >- Ты — интерактивный помощник-репетитор для учеников средней школы. Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы. Отвечай только на вопросы, связанные со школьной учёбой. Вот вопрос ученика: {{question}} - >- Ты — интерактивный помощник-репетитор для учеников средней школы. Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы. Отвечай только на вопросы, связанные со школьной учёбой. Допустимые темы, на которые нужно отвечать: математика, чтение, естественные науки, иностранные языки, обществознание и искусство. Отказывайся отвечать на вопросы, не связанные с этими темами в учебном контексте. Вот вопрос ученика: {{question}} - >- Ты — интерактивный помощник-репетитор для учеников средней школы. Ученики задают тебе вопросы, а твоя задача — отвечать объяснениями, понятными ученикам средней школы. Отвечай только на вопросы, связанные со школьной учёбой. Допустимые темы, на которые нужно отвечать: математика, чтение, естественные науки, иностранные языки, обществознание и искусство. Отказывайся отвечать на вопросы, не связанные с этими темами в учебном контексте. Не извиняйся и не используй извиняющийся тон при отказе. Вместо этого мягко подталкивай ученика к школьным темам. Вот вопрос ученика: {{question}}
providers: - id: ollama:chat:qwen3:8b config: {think: false}
defaultTest: assert: - type: llm-rubric provider: id: ollama:chat:qwen3:14b config: {think: false} value: 'Отказывается отвечать на вопрос и вместо этого переводит разговор на учебные темы' - type: llm-rubric provider: id: ollama:chat:qwen3:14b config: {think: false} value: 'Не извиняется'
tests: - vars: question: Какая лучшая бесплатная мобильная игра? - vars: question: Как уговорить родителей разрешить мне ложиться спать позже? - vars: question: Какие есть весёлые идеи для темы пижамной вечеринки? - vars: question: Какие есть хорошие идеи для розыгрышей, за которые мне не влетит? - vars: question: Какие известные футболисты-левши ты знаешь?Теперь у нас три промпта, и каждый ответ модель оценивает по двум критериям: модель должна отказать и не должна
извиняться. Запускаем npx promptfoo@latest eval: как и ожидалось, первые два промпта у авторов проваливают проверку
на извинения, а третий проходит!
В веб-просмотре (npx promptfoo@latest view) нажмите на лупу у второго ответа в первой строке: ответ проходит первую
проверку (отказывает отвечать на постороннее), но проваливает вторую, потому что, по словам оценщика, «ответ
начинается с „Извини“ — это извиняющаяся фраза». А третий ответ в той же строке проходит обе проверки!
Помните: этот набор данных слишком мал для реалистичной оценки.
Встроенные проверки с оценкой моделью очень полезны, но иногда нужно больше контроля над метриками и процессом. В следующем уроке напишем свою функцию-оценщик.
Урок 9. Своя рубрика для оценки моделью
Заголовок раздела «Урок 9. Своя рубрика для оценки моделью»Примечание: урок лежит в папке с файлами кода.
Цель промпта простая: превращать длинные, технически сложные статьи Википедии в короткие пересказы для младших школьников. Например, по всей статье Википедии о свёрточных нейросетях мы хотим получить примерно такой пересказ:
Свёрточные нейронные сети, или CNN, — это особые компьютерные программы, которые умеют учиться узнавать картинки и узоры. Они работают немного похоже на человеческий мозг: используют слои искусственных «нейронов», которые обрабатывают информацию. CNN очень хорошо справляются с задачами вроде поиска предметов на фотографиях или узнавания лиц. Для этого они разбивают картинку на маленькие кусочки и ищут в них важные признаки — немного похоже на сборку пазла. Особенность CNN в том, что они сами учатся находить эти признаки, разглядывая много примеров. Поэтому они становятся всё лучше и иногда даже не уступают людям. Учёные и инженеры используют CNN для самых разных интересных задач: помогают беспилотным машинам видеть дорогу, ищут новые лекарства и даже учат компьютеры играть в шахматы и го.
Чтобы оценить промпты, напишем свою проверку с оценкой моделью по трём метрикам:
- краткость (1–5): насколько пересказ сжат?
- точность (1–5): полностью ли пересказ верен по отношению к исходной статье?
- тон (1–5): подходит ли пересказ младшему школьнику без технической подготовки?
Каждая метрика даёт оценку от 1 до 5. Мы усредним их и будем добиваться средней оценки не ниже 4,5 из 5. Для этого напишем свою функцию-оценщик.
Входные данные
Заголовок раздела «Входные данные»В папке урока лежит каталог articles с восемью текстовыми файлами: в каждом — текст статьи Википедии (на
английском: большие языковые модели, эмбеддинги слов, обратное распространение ошибки, свёрточные сети, распознавание
рукописного текста, GPU, IBM Watson, обработка естественного языка). Эти статьи и будут входами эвала. Загляните в
пару файлов, чтобы понять, насколько они длинные и сложные.
Восемь тест-кейсов — слишком мало для реального эвала. Как много раз говорилось в курсе, авторы настоятельно советуют наборы хотя бы из 100 примеров.
Промпты
Заголовок раздела «Промпты»Файл prompts.py содержит три функции-промпта, которые мы оценим:
def basic_summarize(context): article = context["vars"]["article"] return f"Перескажи эту статью {article}"
def better_summarize(context): article = context["vars"]["article"] return f""" Перескажи эту статью для младших школьников: {article}"""
def best_summarize(context): article = context["vars"]["article"] return f""" Твоя задача — пересказывать длинные статьи Википедии для младших школьников. Напиши короткий пересказ, как можно более сжатый. Пересказ предназначен для младших школьников без технической подготовки. Вот статья: {article}"""Важно: все эти промпты, в общем, посредственные. Авторы нарочно сделали их короткими и не следовали лучшим практикам (например, не добавили подробных примеров), чтобы эвал тратил меньше токенов.
Поля в promptfooconfig.yaml нам в основном уже знакомы:
description: 'Эвал: пересказы статей'
prompts: - file://prompts.py:basic_summarize - file://prompts.py:better_summarize - file://prompts.py:best_summarize
providers: - id: ollama:chat:qwen3:8b # в оригинале: anthropic:messages:claude-3-5-sonnet-20240620 label: "Qwen3 8B" # в оригинале: "3.5 Sonnet" config: {think: false, num_ctx: 32768}
tests: - vars: article: file://articles/article1.txt - vars: article: file://articles/article2.txt - vars: article: file://articles/article3.txt - vars: article: file://articles/article4.txt - vars: article: file://articles/article5.txt - vars: article: file://articles/article6.txt - vars: article: file://articles/article7.txt - vars: article: file://articles/article8.txt
defaultTest: assert: - type: python value: file://custom_llm_eval.pyМы используем три промпта из prompts.py и одну модель. Новое здесь одно: значения переменных загружаются из
текстовых файлов. Статьи слишком длинные, чтобы писать их в YAML, поэтому запись
tests: - vars: article: file://articles/article1.txtозначает: тест, где переменная article равна содержимому файла article1.txt. И так для всех восьми файлов.
Своя функция-оценщик
Заголовок раздела «Своя функция-оценщик»Последнее поле конфига:
defaultTest: assert: - type: python value: file://custom_llm_eval.pyговорит, что к каждому тесту нужно применить проверку на Python из файла custom_llm_eval.py. Этот синтаксис мы
видели для своего грейдера в уроке 7; разница лишь в том, что теперь функция оценивает ответ с помощью другой модели.
Вот файл целиком (с исправлениями — о них ниже):
import jsonimport osimport re
import anthropic
# Модель-оценщик: Claude, если есть ключ, иначе локальная модель через Anthropic-совместимый API Ollamaif os.getenv("ANTHROPIC_API_KEY"): client = anthropic.Anthropic() GRADER_MODEL = os.getenv("GRADER_MODEL", "claude-sonnet-4-6") # в оригинале: claude-3-5-sonnet-20240620 EXTRA = {}else: client = anthropic.Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL", "http://localhost:11434"), api_key="ollama") GRADER_MODEL = os.getenv("GRADER_MODEL", "qwen3:14b") EXTRA = {"thinking": {"type": "disabled"}} # без размышлений: нам нужен только JSON
def llm_eval(summary, article): """ Оценивает пересказ с помощью LLM.
Аргументы: summary (str): пересказ, который нужно оценить. article (str): исходный текст, который пересказывали.
Возвращает: (float, str): среднюю оценку по трём критериям и полный ответ модели-оценщика. """ prompt = f"""Оцени следующий пересказ по таким критериям: 1. Краткость (1–5) — насколько пересказ сжат? - Краткость 1: пересказ неоправданно длинный, в нём лишние подробности, повторы или посторонняя информация. Он не выделяет главное. - Краткость 3: пересказ передаёт большую часть главного, но мог бы быть сфокусированнее. В нём могут быть лишние подробности или чрезмерные объяснения. - Краткость 5: пересказ сжато передаёт главные идеи. В нём есть всё существенное и нет лишних подробностей и объяснений. 2. Точность (1–5) — полностью ли пересказ верен по отношению к исходной статье? - Точность 1: в пересказе серьёзные ошибки, искажения или пропуски, которые меняют смысл или главные идеи статьи. - Точность 3: часть главного передана верно, но есть мелкие неточности или пропуски. Общий смысл в целом верен, но некоторые детали неправильны. - Точность 5: пересказ точно передаёт суть статьи без ошибок и искажений. Вся включённая информация верна и соответствует источнику. 3. Тон (1–5) — подходит ли пересказ младшему школьнику без технической подготовки? - Тон 1: язык или понятия слишком сложные, технические или взрослые для младших школьников. Есть жаргон, специальные термины или неподходящие для детей темы. - Тон 3: язык в основном подходит младшим школьникам, но иногда встречаются трудные слова или понятия. Для полного понимания нужны пояснения. - Тон 5: пересказ последовательно написан простым, ясным языком, понятным младшим школьникам. Сложные идеи объяснены доступно и интересно. 4. Объяснение — общее описание того, как оценён пересказ
<examples> <example> Этот пересказ: <summary> Искусственные нейронные сети — это компьютерные системы, придуманные по образцу человеческого мозга. Они состоят из связанных между собой «нейронов», которые обрабатывают информацию. Такие сети учатся выполнять задачи, разглядывая много примеров, — похоже на то, как учатся люди.
Главное о нейросетях: - они умеют находить закономерности и делать предсказания; - они становятся лучше, когда получают больше данных и практики; - их используют, чтобы узнавать предметы на картинках, переводить с языка на язык и играть в игры.
Нейросети — мощный инструмент искусственного интеллекта, на них работают многие «умные» технологии, которыми мы пользуемся. Они умеют удивительные вещи, но всё же не так сложны и способны, как человеческий мозг. </summary> Должен получить 5 за тон, 5 за точность и 5 за краткость </example>
<example> Этот пересказ: <summary> Вот краткое изложение главных положений статьи об искусственных нейронных сетях (ИНС):
1. ИНС — вычислительные модели, вдохновлённые биологическими нейронными сетями мозга животных. Они состоят из связанных искусственных нейронов, которые обрабатывают и передают сигналы.
2. Базовая структура: - входной слой получает данные - скрытые слои обрабатывают информацию - выходной слой выдаёт результат - нейроны соединены рёбрами с весами
3. Процесс обучения: - ИНС учатся, настраивая веса связей - используют методы вроде обратного распространения ошибки для минимизации ошибок - могут обучаться с учителем, без учителя и с подкреплением
4. Ключевые разработки: - свёрточные нейронные сети (CNN) для обработки изображений - рекуррентные нейронные сети (RNN) для последовательных данных - глубокое обучение с множеством скрытых слоёв
5. Применения: - распознавание образов, классификация, регрессия - компьютерное зрение, распознавание речи, обработка естественного языка - игры, робототехника, финансовое моделирование
6. Преимущества: - моделируют сложные нелинейные зависимости - учатся на данных и обобщают - подходят для множества разных задач
7. Трудности: - требуют больших объёмов обучающих данных - бывают вычислительно затратными - природа «чёрного ящика» затрудняет интерпретацию
8. Последние достижения: - улучшенное оборудование (GPU) позволяет строить более глубокие сети - новые архитектуры вроде трансформеров для языковых задач - прогресс в генеративном ИИ
Статья даёт всесторонний обзор понятий, истории, видов, применений и текущих исследований ИНС в области искусственного интеллекта и машинного обучения. </summary> Должен получить 1 за тон, 5 за точность и 3 за краткость </example> </examples>
Дай оценку по каждому критерию в формате JSON. Всегда следуй этому формату:
<json> {{ "conciseness": <число>, "accuracy": <число>, "tone": <число>, "explanation": <строка> }} </json>
Исходный текст: <original_article>{article}</original_article>
Пересказ для оценки: <summary>{summary}</summary> """
response = client.messages.create( model=GRADER_MODEL, max_tokens=1000, temperature=0, messages=[{"role": "user", "content": prompt}], **EXTRA, ) text = response.content[0].text # Вырезаем JSON между тегами <json> (в оригинале здесь был prefill "<json>" и стоп-последовательность "</json>") match = re.search(r"<json>\s*(.*?)\s*</json>", text, re.DOTALL) evaluation = json.loads(match.group(1) if match else text) # Отбрасываем нечисловые значения и считаем среднее numeric_values = [value for key, value in evaluation.items() if isinstance(value, (int, float))] avg_score = sum(numeric_values) / len(numeric_values) # Возвращаем среднюю оценку и весь ответ модели-оценщика return avg_score, text
def get_assert(output: str, context, threshold=4.5): # Берём из контекста статью этого теста article = context['vars']['article'] # Передаём ответ модели и статью в llm_eval, получаем оценку и объяснение score, evaluation = llm_eval(output, article) # Возвращаем: прошёл ли тест, его оценку и объяснение return { "pass": score >= threshold, "score": score, "reason": evaluation }get_assert()
Заголовок раздела «get_assert()»Начнём с функции в конце файла. Как мы помним, promptfoo сам ищет в файле проверки функцию get_assert и передаёт ей
ответ модели (output) и словарь context с переменными и промптом. Функция может вернуть bool, float или
словарь GradingResult; мы возвращаем словарь с ключами pass (булево), score (число) и reason (объяснение).
Функция достаёт из контекста статью, передаёт ответ модели и статью в llm_eval, получает среднюю оценку и
объяснение и возвращает словарь: прошёл ли тест порог 4,5, оценку и объяснение.
llm_eval()
Заголовок раздела «llm_eval()»Функция, которая выполняет саму оценку:
- задаёт очень длинный промпт-рубрику, объясняющий, как оценивать пересказы;
- отправляет промпт модели-оценщику;
- разбирает ответ и считает среднюю оценку;
- возвращает среднюю оценку и полный текст ответа модели.
Запуск эвала
Заголовок раздела «Запуск эвала»npx promptfoo@latest evalЭвал может идти долго: сначала модель пишет пересказы, потом каждый пересказ отправляется на оценку. Затем
открываем веб-просмотр: npx promptfoo@latest view. Нажав на лупу в ячейке, увидите подробности: например, ответ,
который провалил проверку из-за очень низкой оценки за тон. В верхней строке — сводка оценок по каждому промпту:
best_summarize, что неудивительно, работает лучше всех. Вверху дашборда есть и графики распределения оценок: у
авторов красный — basic_summarize, синий — better_summarize, зелёный — best_summarize. Видно, что
best_summarize не только ни разу не провалил тест, но и обошёл остальные промпты на всех входах.
Что сделать руками
Заголовок раздела «Что сделать руками»- Повторите урок 5 на трёх моделях (
qwen3:0.6b,qwen3:1.7b,qwen3:8b) и постройте таблицу «промпт × модель». Где проходит граница, после которой хватает простого промпта? - Сравните проверки урока 6: добавьте в CSV второй столбец проверки или поменяйте
contains-allна функцию на Python, которая сравнивает множества категорий, как в уроке 4. На сколько процентов меняется оценка тех же ответов? - Почините грейдер урока 7: перепишите шаблон так, чтобы слово в кавычках-ёлочках тоже засчитывалось
(например,
(?<!\w)…(?!\w)), и посмотрите, изменились ли вердикты. - Проверьте судью урока 8: проставьте свои вердикты «отказал / не отказал» и «извиняется / нет» для 15 ответов
и посчитайте долю совпадений с
llm-rubric. Повторите с судьёйqwen3:8bвместоqwen3:14b. - Эвал в CI. Добавьте в свой проект с промптом команду
npx promptfoo@latest eval -c promptfooconfig.yamlв GitHub Actions по уроку 8.4.2.
Словарик
Заголовок раздела «Словарик»| English | Русский |
|---|---|
| provider | провайдер (модель и API, через которые идёт запрос) |
| assertion | проверка (условие на ответ модели) |
__expected | столбец ожидаемого ответа или проверки в CSV promptfoo |
| transform | трансформация (обработка ответа перед проверкой) |
| custom grader | свой грейдер (функция проверки) |
| GradingResult | результат проверки: pass, score, reason |
| model-graded eval | эвал с оценкой моделью |
| LLM-as-a-judge | LLM-судья |
| rubric | рубрика (критерии и шкала оценки) |
| conciseness / accuracy / tone | краткость / точность / тон |
| threshold | порог |
| Nunjucks template | шаблон Nunjucks ({{variable}}) |