Перейти к содержанию

Кастомные метрики (Custom Metrics)

CustomEvalMetric позволяет задавать собственные критерии оценки без написания кода метрики. Вы передаёте список критериев на естественном языке, ссылающихся на данные через {{placeholders}}, выбираете стратегию скоринга и опционально повторяете вызов судьи несколько раз для consensus voting.

Как это работает

  1. Сбор данных -- метрика собирает все доступные значения из тест-кейса: input, actual_output, expected_output, retrieval_context, колонки dataset-строки, system_prompt и любые extra_fields, которые вы положили в EvalTestCase.
  2. Фильтрация критериев -- каждый критерий должен содержать хотя бы один {{placeholder}}, и все упомянутые placeholder-ы должны быть разрешены. Критерии, не прошедшие проверку, пропускаются и логируются в evaluation_log["skipped_criteria"].
  3. Сборка промпта -- метрика рендерит блок DATA только с теми значениями, которые упоминаются в кепт-критериях, а затем список критериев.
  4. Вызов(ы) судьи -- в зависимости от strategy (см. ниже) судья либо выдаёт вердикт по каждому критерию, либо возвращает одну общую оценку 0-10. Если n_runs > 1, судья вызывается параллельно нужное число раз.
  5. Агрегация -- результаты прогонов объединяются (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 -- это время самого медленного прогона, а не сумма.

Практические советы

  1. Начинайте с n_runs=1, чтобы отточить критерии; включайте consensus только после стабилизации формулировок -- он линейно множит стоимость.
  2. strategy="verdict" лучше для аудита -- лог показывает, какой именно критерий сфейлился. strategy="direct" лучше для субъективных / холистических измерений.
  3. Используйте низкую TCVA temperature (0.1-0.3) для критичных доменов (медицина, юриспруденция, compliance) -- любой слабый вердикт тянет итог вниз.
  4. Предпочитайте median над mean для consensus -- один выброс стохастического судьи не должен сильно двигать оценку.
  5. Комбинируйте со встроенными метриками. FaithfulnessMetric для фактической опоры, AnswerRelevancyMetric для соответствия теме, CustomEvalMetric -- для доменных или brand-specific правил, которые встроенные не покрывают.