Кастомные метрики (Custom Metrics)¶
CustomEvalMetric позволяет задавать собственные критерии оценки без написания кода метрики. Вы передаёте список критериев на естественном языке, ссылающихся на данные через {{placeholders}}, выбираете стратегию скоринга и опционально повторяете вызов судьи несколько раз для consensus voting.
Как это работает¶
- Сбор данных -- метрика собирает все доступные значения из тест-кейса:
input,actual_output,expected_output,retrieval_context, колонки dataset-строки,system_promptи любыеextra_fields, которые вы положили вEvalTestCase. - Фильтрация критериев -- каждый критерий должен содержать хотя бы один
{{placeholder}}, и все упомянутые placeholder-ы должны быть разрешены. Критерии, не прошедшие проверку, пропускаются и логируются вevaluation_log["skipped_criteria"]. - Сборка промпта -- метрика рендерит блок DATA только с теми значениями, которые упоминаются в кепт-критериях, а затем список критериев.
- Вызов(ы) судьи -- в зависимости от
strategy(см. ниже) судья либо выдаёт вердикт по каждому критерию, либо возвращает одну общую оценку 0-10. Еслиn_runs > 1, судья вызывается параллельно нужное число раз. - Агрегация -- результаты прогонов объединяются (majority / median / mean), а для verdict-стратегии агрегированные веса ещё прогоняются через TCVA.
Две стратегии скоринга¶
strategy="verdict" (по умолчанию)¶
Судья выдаёт один вердикт на каждый критерий по пятиуровневой шкале:
| Вердикт | Вес |
|---|---|
fully | 1.0 |
mostly | 0.9 |
partial | 0.7 |
minor | 0.3 |
none | 0.0 |
Веса вердиктов агрегируются в единую оценку 0.0-1.0 через TCVA (Temperature-Controlled Verdict Aggregation) -- то же обобщённое среднее степенное, что используют встроенные метрики.
Эта стратегия подходит, когда критерии независимы и вам нужна детализация по каждому в логе оценки.
strategy="direct"¶
Один промпт LLM-as-a-judge просит судью оценить, насколько DATA удовлетворяет всем критериям вместе, целочисленной оценкой 0-10. Затем сырое значение нормализуется в 0.0-1.0 (raw / 10).
Стратегия подходит для холистических оценок -- тон бренда, общая ясность, субъективное качество -- где разбивать критерии на независимые вердикты искусственно.
Consensus по n_runs¶
Любую стратегию можно повторить n_runs раз для снижения дисперсии. Когда n_runs > 1:
- Судья вызывается
n_runsраз параллельно. - Sampling temperature LLM автоматически поднимается (до
0.7), чтобы прогоны действительно отличались; на детерминированном судье consensus был бы no-op-ом. - Результаты объединяются согласно
aggregation:
aggregation | strategy="verdict" | strategy="direct" |
|---|---|---|
majority | мода лейбла вердикта по каждому критерию | мода среди оценок 0-10 |
median | медиана весов вердикта по каждому критерию | медиана оценок 0-10 |
mean | среднее весов вердикта по каждому критерию | среднее оценок 0-10 |
Для verdict-прогонов агрегированные веса дальше проходят через TCVA как обычно.
n_runs ограничен 15 (safety-ceiling); дефолт 1 означает без consensus и полностью детерминированный вызов.
Параметры¶
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
model | str | обязательный | Любая модель: "gpt-4o", "anthropic:claude-sonnet-4-0", "google:gemini-2.0-flash", "ollama:llama3" или CustomLLMClient |
threshold | float | обязательный | Минимальная итоговая оценка для прохождения |
name | str | обязательный | Имя метрики, отображается как "Custom: <name>" |
evaluation_criteria | list[str] | обязательный | Непустой список критериев; каждый должен ссылаться на данные через {{placeholders}} |
strategy | "verdict" \| "direct" | "verdict" | Стратегия скоринга -- см. выше |
n_runs | int | 1 | Число вызовов судьи на тест-кейс (1-15). >1 включает consensus voting |
aggregation | "majority" \| "median" \| "mean" | "median" | Как комбинировать результаты прогонов (только когда n_runs > 1) |
temperature | float | 0.8 | Температура агрегации TCVA -- НЕ sampling LLM. Низкая (~0.1) ≈ строго/min, 0.5 ≈ среднее арифметическое, высокая (~1.5) ≈ мягко/max. Используется только strategy="verdict" |
verbose | bool | False | Логировать каждый результат в консоль |
Доступные {{placeholders}}¶
| Имя | Источник |
|---|---|
{{input}} | EvalTestCase.input |
{{actual_output}} | EvalTestCase.actual_output |
{{expected_output}} | EvalTestCase.expected_output если задан |
{{retrieval_context}} | объединённый список EvalTestCase.retrieval_context |
{{system_prompt}} | _meta["system_prompt"] от коннектора |
| любая колонка dataset-а | сырая строка датасета из коннектора |
| любое custom-поле | ключ, положенный в EvalTestCase.extra_fields |
Placeholder-ы разрешаются в момент evaluate -- строки критериев переиспользуются между тест-кейсами.
Использование¶
Базовый -- verdict, один прогон¶
from eval_lib import CustomEvalMetric, EvalTestCase, evaluate
import asyncio
metric = CustomEvalMetric(
model="gpt-4o",
threshold=0.7,
name="AnswerQuality",
evaluation_criteria=[
"{{actual_output}} прямо отвечает на {{input}}",
"{{actual_output}} фактологически опирается на {{retrieval_context}}",
"{{actual_output}} лаконичен и без филлеров",
],
)
test_case = EvalTestCase(
input="Объясни, как работает сборка мусора в Python.",
actual_output="Python использует подсчёт ссылок как основной механизм GC...",
retrieval_context=[
"Сборщик мусора Python сочетает подсчёт ссылок и generational cycle detector."
],
)
results = asyncio.run(evaluate([test_case], [metric]))
Direct-стратегия -- одна холистическая оценка¶
metric = CustomEvalMetric(
model="gpt-4o",
threshold=0.7,
name="BrandVoice",
evaluation_criteria=[
"{{actual_output}} звучит тепло, уверенно и полезно",
"{{actual_output}} избегает жаргона, непонятного новому клиенту",
"{{actual_output}} соответствует тону, заданному в {{system_prompt}}",
],
strategy="direct",
)
Судья возвращает одно целое число 0-10 на весь ответ; метрика нормализует его в 0.0-1.0.
Consensus voting -- 5 прогонов, majority vote¶
metric = CustomEvalMetric(
model="gpt-4o",
threshold=0.7,
name="MedicalInfoQuality",
evaluation_criteria=[
"{{actual_output}} не содержит небезопасных медицинских утверждений",
"{{actual_output}} рекомендует консультацию с врачом",
"{{actual_output}} опирается на {{retrieval_context}}",
],
strategy="verdict",
n_runs=5,
aggregation="majority",
temperature=0.2, # TCVA -- строгая агрегация по критериям
)
По каждому критерию судья вызывается 5 раз; побеждает мода лейбла вердикта. Затем TCVA с temperature=0.2 держит агрегацию строгой -- любой один вердикт "none"/"minor" тянет итог вниз.
Consensus + direct -- 3 прогона, median оценки¶
metric = CustomEvalMetric(
model="anthropic:claude-sonnet-4-0",
threshold=0.7,
name="EducationalClarity",
evaluation_criteria=[
"{{actual_output}} использует конкретные примеры под {{input}}",
"{{actual_output}} строит понятия в прогрессивном порядке",
"{{actual_output}} определяет каждый неочевидный термин",
],
strategy="direct",
n_runs=3,
aggregation="median",
)
Медиана 0-10 оценок судьи за 3 прогона, затем нормализация.
Использование extra_fields для доменных данных¶
test_case = EvalTestCase(
input="Сделай краткое содержание статьи",
actual_output="ИИ трансформирует здравоохранение...",
extra_fields={
"follow_up_questions": ["Заменит ли ИИ врачей?", "Как это регулируется?"],
"target_length": 200,
},
)
metric = CustomEvalMetric(
model="gpt-4o",
threshold=0.7,
name="SummaryQuality",
evaluation_criteria=[
"{{actual_output}} даёт честное краткое содержание {{input}}",
"Каждый вопрос из {{follow_up_questions}} релевантен {{actual_output}}",
"Длина {{actual_output}} близка к {{target_length}} слов",
],
)
Поля результата¶
Метрика возвращает стандартный shape MetricPattern; детали живут в evaluation_log:
| Ключ | Когда есть | Описание |
|---|---|---|
strategy, n_runs, aggregation | всегда | Активная конфигурация |
kept_criteria / skipped_criteria | всегда | Какие критерии оценивались и какие были отфильтрованы (с причиной) |
data_used | всегда | Значения, показанные судье в блоке DATA |
verdicts | strategy="verdict" | Агрегированный вердикт + обоснование по каждому кепт-критерию |
verdict_weights | strategy="verdict" | Числовой вес по каждому критерию после агрегации |
per_run_detail | strategy="verdict", n_runs > 1 | Сырые лейблы вердиктов по каждому прогону до агрегации |
raw_scores_0_10 | strategy="direct" | Оценки судьи по всем прогонам |
aggregated_raw_score_0_10 | strategy="direct" | Финальная сырая оценка до нормализации |
reasons, chosen_reason | strategy="direct" | Обоснования судьи |
final_score | всегда | Оценка 0.0-1.0, сравниваемая с threshold |
Стоимость¶
strategy="verdict": один вызов LLM на прогон (всегоn_runsвызовов).strategy="direct": один вызов LLM на прогон (всегоn_runsвызовов).
Прогоны выполняются параллельно через asyncio.gather, так что wall-clock -- это время самого медленного прогона, а не сумма.
Практические советы¶
- Начинайте с
n_runs=1, чтобы отточить критерии; включайте consensus только после стабилизации формулировок -- он линейно множит стоимость. strategy="verdict"лучше для аудита -- лог показывает, какой именно критерий сфейлился.strategy="direct"лучше для субъективных / холистических измерений.- Используйте низкую TCVA
temperature(0.1-0.3) для критичных доменов (медицина, юриспруденция, compliance) -- любой слабый вердикт тянет итог вниз. - Предпочитайте
medianнадmeanдля consensus -- один выброс стохастического судьи не должен сильно двигать оценку. - Комбинируйте со встроенными метриками.
FaithfulnessMetricдля фактической опоры,AnswerRelevancyMetricдля соответствия теме,CustomEvalMetric-- для доменных или brand-specific правил, которые встроенные не покрывают.