Содержание
  1. Общая задача регрессии и границы набора
  2. Три инструмента на одних критериях
  3. Первичная опора для каждого признака
  4. Три решения выбора по процессу команды
  5. Три входа, ожидаемые JSON и общий критерий
  6. Формат отдельно от смысла и неизвестности
  7. Отрицательные контроли и ноль вместо false
  8. Карта полей для каждого маршрута
  9. Локальный пакет, модель, judge и облачный кабинет
  10. Частые вопросы
  11. Что остаётся за границей трёх случаев
Подборки и сравнения

Инструменты для тестирования LLM: 3 варианта

10 ноября 2026 · 14 минут чтения

Инструменты для тестирования LLM: 3 варианта — Promptfoo, DeepEval и Pydantic Evals. Все три решают одну задачу: прогнать фиксированные случаи через вашу модель и поймать нарушение формата, неверный смысл и выдуманную определённость. Ниже — общая задача регрессии, сравнение по одним критериям, готовый набор из трёх случаев и карта переноса полей в каждый инструмент.

Общая задача регрессии и границы набора

Регрессия ответов LLM — это повторный прогон одного и того же набора случаев после каждого изменения промпта, модели или кода приложения. Задача формулируется узко: зафиксировать входы, зафиксировать ожидаемые выходы и сравнить с ними фактический ответ. Для нашей учебной задачи проверяем именно эти условия; в других приложениях регрессионный набор может охватывать дополнительные свойства.

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

Для учебного примера возьмём один системный промпт и три случая. Промпт требует ровно два поля: role — строка, can_edit — boolean либо null. Контекст выдаётся вместе с вопросом. Если право не указано, can_edit равен null. Отсутствие сведений в этом контракте означает неизвестность; запрет без основания не добавляется.

Три случая покрывают три разные проверки. T1: роль viewer читает документ, изменять не может — ожидаем {"role": "viewer", "can_edit": false}. T2: роль editor читает и изменяет — ожидаем {"role": "editor", "can_edit": true}. T3: роль guest существует, права не описаны — ожидаем {"role": "guest", "can_edit": null}. Такой набор проверяет формат, смысл и работу с неизвестностью в одном прогоне.

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

Три инструмента на одних критериях

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

Promptfoo — инструмент с конфигурацией в YAML. Документация проекта описывает запуск через npx promptfoo@latest init --example getting-started, который создаёт каталог с файлом promptfooconfig.yaml, содержащим промпты, провайдеров и тестовые случаи. Там же указано, что провайдеры требуют аутентификации, например переменной OPENAI_API_KEY. Прогон выполняется командой promptfoo eval, просмотр результатов — promptfoo view.

DeepEval — Python-фреймворк, встраиваемый в pytest. Документация проекта сообщает, что установка идёт командой pip install -U deepeval, а deepeval test run собирает и запускает файлы проверок так же, как это делает pytest. Тестовый случай описывается классом LLMTestCase с обязательными полями input и actual_output и необязательным expected_output. Метрики возвращают оценку от 0 до 1, порог задаётся параметром threshold.

Pydantic Evals — Python-пакет с код-ориентированным подходом. Документация проекта описывает установку pip install pydantic-evals и модель данных из Dataset, Case, Evaluator и EvaluationReport. Прогон выполняется методом dataset.evaluate_sync(task_function), который возвращает объект отчёта. Пакет работает с любой вызываемой Python-функцией и не требует приложения на PydanticAI.

Все три инструмента дают одинаковый минимум: фиксированные случаи, автоматическую проверку и отчёт. Различия лежат в способе описания и в точке встраивания в процесс команды.

Первичная опора для каждого признака

Каждый сравнительный признак опирается на конкретный документ. Это позволяет читателю проверить утверждение и не переносить на инструмент чужие возможности.

Матрица прогонов. Документация Promptfoo описывает проверку каждого промпта, модели и тестового случая, то есть полную матрицу. В примере сравнения моделей на пятнадцати загадках указано, что полный прогон делает сорок пять вызовов модели плюс вызовы оценки. Это документированный план прогона; фактический расход в статье не измерялся.

Типы проверок. Документация Promptfoo перечисляет детерминированные проверки: equals, contains, regex, is-json, contains-json, а также проверки на JavaScript, Python и Ruby. Там же описаны проверки с участием модели: llm-rubric, factuality, context-faithfulness. Любой тип проверки можно инвертировать префиксом not-.

Расширение метрик. Документация DeepEval по пользовательским метрикам требует наследовать класс BaseMetric и реализовать методы measure() и a_measure(), которые обязаны установить self.score и self.success. Там же приведён пример метрики на основе ROUGE, которая не использует LLM.

Типизированные случаи. Документация Pydantic Evals описывает Case со структурированными inputs, необязательным expected_output и метаданными. Evaluator может быть детерминированной проверкой формата или оценкой недетерминированного вывода.

Границы внешних вызовов. Документация Promptfoo прямо указывает, что проверки стоимости и задержки оценивают уже завершённые ответы и не ограничивают расходы и не останавливают медленные запросы. Документация DeepEval сообщает, что весь быстрый старт выполняется локально, а отправка трассировок в Confident AI включается отдельным входом в систему.

Три решения выбора по процессу команды

Выбор зависит от того, как команда уже работает. Ниже три заполненных решения для разных процессов.

Процесс команды Начать с Почему
Сравнить два шаблона ответа на двух провайдерах и трёх случаях, отчёт открывается как таблица Promptfoo Документированная матрица промптов, провайдеров и тестов плюс просмотрщик результатов
В проекте уже pytest, нужна собственная детерминированная метрика рядом с тестами приложения DeepEval Документированный процесс pytest и расширение BaseMetric; Confident AI необязателен для локального запуска
Команда поддерживает Python-функцию со структурированными входами и хочет типизированные Case, Dataset, Evaluator и программный отчёт Pydantic Evals Документированный кодовый процесс и самостоятельный пакет, работающий с любой вызываемой функцией

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

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

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

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

Три входа, ожидаемые JSON и общий критерий

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

Системный промпт задаёт контракт ответа.

ТЕКСТ
Отвечай только одним объектом JSON с ровно двумя полями: role — строка, can_edit — boolean либо null. Используй только выданный контекст. Если право не указано, can_edit=null. Отсутствие сведений не означает запрет.

Три случая задают входы и ожидаемые выходы.

ТЕКСТ14 строк
T1
context: Роль viewer позволяет читать документ. Изменять документ с этой ролью нельзя.
question: Для роли viewer верни JSON с role и can_edit.
expected: {"role": "viewer", "can_edit": false}

T2
context: Роль editor позволяет читать и изменять документ.
question: Для роли editor верни JSON с role и can_edit.
expected: {"role": "editor", "can_edit": true}

T3
context: Роль guest существует. Её права в этом документе не описаны.
question: Для роли guest верни JSON с role и can_edit. Если право не описано, используй null.
expected: {"role": "guest", "can_edit": null}

Общий критерий проверки формулируется так: ответ проходит, если он разбирается как JSON, содержит ровно два поля role и can_edit, поле role является строкой, поле can_edit является boolean или null, и весь объект совпадает с ожидаемым. Критерий один для всех трёх случаев и не меняется между инструментами.

Такой вход проверяет три вещи одновременно. T1 и T2 проверяют соответствие смысла контексту. T3 проверяет, что неизвестность передана как null. Формат проверяется во всех трёх случаях, потому что критерий требует строгий JSON с ровно двумя полями.

Формат отдельно от смысла и неизвестности

Проверка формата и проверка смысла — разные операции, и их нужно разделять. Формат проверяется разбором строки и проверкой типов полей. Смысл проверяется сравнением разобранного объекта с ожидаемым. Неизвестность проверяется отдельным случаем, где ожидаемое значение равно null.

Ручной oracle на Python выполняет все три проверки в одной функции.

PYTHON14 строк
import json

def check(raw, expected):
    try:
        actual = json.loads(raw)
    except (json.JSONDecodeError, TypeError):
        return False
    if not isinstance(actual, dict) or set(actual) != {"role", "can_edit"}:
        return False
    if type(actual["role"]) is not str:
        return False
    if actual["can_edit"] is not None and type(actual["can_edit"]) is not bool:
        return False
    return actual == expected

Разберём порядок проверок. Сначала строка разбирается как JSON; при ошибке разбора функция возвращает False. Затем проверяется, что результат — словарь с ровно двумя нужными ключами. Затем проверяется тип поля role. Затем проверяется тип поля can_edit с отдельным разрешением на null. В конце объект сравнивается с ожидаемым целиком.

Функция возвращает только итоговый boolean. При ручном разборе по этим шагам можно различить причины отказа. Ответ editor может редактировать понятен по смыслу, но не проходит разбор JSON. Ответ {"role": "viewer", "can_edit": true} разбирается корректно, но противоречит контексту. Ответ {"role": "guest", "can_edit": false} формально корректен, но подменяет неизвестность запретом.

Проверка неизвестности требует явного случая. Без T3 набор не отличит модель, которая честно возвращает null, от модели, которая всегда выбирает false. Этот случай стоит держать в наборе отдельно и не объединять с проверками известных прав.

Отрицательные контроли и ноль вместо false

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

Случай Ответ Результат Причина
T1 {"role":"viewer","can_edit":false} проходит Формат и смысл совпадают
T1 {"role":"viewer","can_edit":true} не проходит JSON валиден, смысл противоречит контексту
T2 editor может редактировать не проходит Смысл понятен, требуемого JSON нет
T3 {"role":"guest","can_edit":false} не проходит Неизвестность подменена запретом
T3 {"role":"guest","can_edit":null} проходит Неизвестность передана корректно
T1 {"role":"viewer","can_edit":0} не проходит Число 0 не проходит строгую проверку boolean

Последний контроль важен отдельно. В Python число 0 считается равным False, поэтому нестрогая проверка пропустила бы такой ответ. Функция check отдельно допускает None и требует тип bool у любого другого значения can_edit, поэтому числа 0 и 1 отклоняются. Это делает проверку формата строгой и не зависит от особенностей сравнения значений в языке.

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

Сбой провайдера в этот набор не входит. Ошибка сети или исчерпанная квота дают отдельный статус и не смешиваются с неправильным ответом модели. Иначе статистика прогона покажет сбои инфраструктуры как ошибки качества.

Карта полей для каждого маршрута

Перенос комплекта в каждый инструмент требует сопоставления полей. Карта показывает, куда попадает каждый элемент.

Элемент комплекта Promptfoo DeepEval Pydantic Evals
Контекст и вопрос vars тестового случая input в LLMTestCase структурированные inputs в Case
Системный промпт prompts в конфигурации системная инструкция приложения часть Task
Фактический ответ вывод провайдера actual_output возврат Task
Ожидаемый JSON assert с проверкой expected_output expected_output
Проверка формата is-json плюс строгая проверка полей и типов собственная метрика BaseMetric Evaluator с проверкой типа
Смысловая проверка javascript или python assert measure() в метрике Evaluator.evaluate()
Отчёт promptfoo view deepeval test run и локальный JSON EvaluationReport

В конфигурации Promptfoo список шаблонов называется prompts; строка карты обозначает перенос системной инструкции в этот список.

Для Promptfoo контекст и вопрос передаются как переменные, общий текст — как промпт, три случая сохраняются независимо. Проверка JSON и семантический oracle оформляются как assertions. Конкретный синтаксис пользовательской проверки стоит сверить с текущей документацией проекта.

Для DeepEval каждый случай сохраняет input, фактическую строку actual_output и ожидаемый JSON как expected_output. Детерминированная проверка оформляется собственной метрикой на основе BaseMetric и запускается в существующем процессе pytest. Точный oracle заменяет субъективную оценку моделью там, где нужна строгая проверка.

Для Pydantic Evals один Case содержит структурированные inputs с контекстом и вопросом плюс expected_output. Task вызывает существующую функцию приложения, Evaluator применяет общий критерий. Нужно зафиксировать, возвращает Task сырую строку или разобранный словарь: контракт проверки должен совпадать с этим возвратом.

Локальный пакет, модель, judge и облачный кабинет

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

Локальный пакет работает в вашем окружении. Promptfoo можно запускать через npx или установить через npm либо brew; Python-пакеты устанавливаются через pip. Обращение к модели и отправка результатов в облачный кабинет настраиваются отдельно.

Вызов удалённой проверяемой модели может требовать ключа и оплачиваться по тарифу провайдера. Локальная функция или модель без внешнего API не требует такого ключа автоматически. В нашем учебном комплекте реальные вызовы и расходы не измерялись; маршрут исполнения выбирается при переносе в приложение.

Вызов judge — это ещё один расход, который возникает только при проверках с участием модели. Документация Promptfoo перечисляет проверки llm-rubric, factuality и context-faithfulness, которые обращаются к модели для оценки. Документация DeepEval описывает метрики на основе LLM и отдельно показывает метрику на ROUGE, которая работает без обращения к модели. Детерминированные проверки формата не тратят вызовы judge.

Облачный кабинет — это необязательный слой поверх локального запуска. Документация DeepEval описывает локальный запуск тестового процесса и отдельное включение отправки результатов в Confident AI. При выборе удалённой модели оценивания её вызовы остаются внешними. Pydantic Evals может отправлять результаты в Logfire, если установлено дополнительное расширение. Локальный запуск работает без облачного кабинета.

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

Частые вопросы

Какие инструменты тестирования LLM выбрать для начала?

Начните с того, что совпадает с вашим процессом. Для сравнения промптов и моделей в таблице подходит Promptfoo. Для проекта на pytest с собственной метрикой — DeepEval. Для типизированных случаев и программного отчёта — Pydantic Evals. Все три решают одну задачу регрессии, и выбор определяется точкой встраивания.

Есть ли открытые инструменты оценки LLM?

Все три решения из этой подборки устанавливаются локально и запускаются без обязательного облачного сервиса. DeepEval и Pydantic Evals ставятся через pip, Promptfoo — через npx, npm или brew. Облачные кабинеты подключаются отдельно и остаются необязательными для базового прогона.

Как проверять формат ответа LLM отдельно от смысла?

Разделите проверку на два шага. Сначала разберите строку как JSON и проверьте типы полей, затем сравните разобранный объект с ожидаемым. Для строгого контракта Promptfoo используйте is-json и точную проверку полей: contains-json может допускать окружающий текст. В DeepEval и Pydantic Evals детерминированная проверка оформляется собственной функцией или метрикой.

Почему ответ с нулём вместо false не проходит проверку?

В Python число 0 считается равным False, поэтому нестрогое сравнение пропустило бы такой ответ. Строгая проверка типа через type(value) is not bool отсекает числа. Это делает проверку формата независимой от особенностей сравнения значений в языке.

Нужен ли облачный кабинет для запуска проверок?

Базовый прогон работает локально. Для локального процесса DeepEval кабинет Confident AI необязателен. Если выбран облачный judge, данные всё равно передаются его провайдеру. Pydantic Evals может отправлять результаты в Logfire при установке расширения. Облачный слой добавляется, когда нужен общий просмотр прогонов.

Что остаётся за границей трёх случаев

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

Баллы разных метрик не переносятся между инструментами. Оценка от 0 до 1 в DeepEval и взвешенная оценка assertions в Promptfoo считаются по разным правилам. Сравнивать их напрямую нельзя, и переносить порог из одного инструмента в другой тоже.

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

Читайте также

3 материала