Offline vs online оценка
Два режима измерения качества дополняют друг друга. Важно не путать, какой на что отвечает.
| Offline | Online | |
|---|---|---|
| Когда | До релиза, в CI | После релиза, на проде |
| На чём | Фиксированный датасет (golden) | Живой трафик пользователей |
| Отвечает на | «Стал ли билд лучше?» | «Как система ведёт себя в реальности?» |
| Плюс | Повторяемо, безопасно, дёшево | Настоящее поведение, реальные кейсы |
| Минус | Датасет ≠ вся реальность | Ошибку видят пользователи |
Offline — это «лабораторный анализ на образце», online — «наблюдение за пациентом». Этот урок — про offline: как построить повторяемое измерение, которое ловит регрессию до того, как её увидит пользователь. Оно опирается на связку dataset + evaluator + experiment из урока про LangSmith — здесь мы наполняем часть «evaluator» конкретными метриками.
У классификатора есть эталонный ответ и метрика точности. У LLM-системы выход — свободный текст, «правильных» формулировок много, а «качество» распадается на измерения: фактическая верность, релевантность, полнота, тон, формат, отсутствие галлюцинаций. Поэтому offline-оценка LLM — это не одно число, а набор метрик, часто сконструированных под конкретную задачу. Отсюда три подхода ниже.
Три типа offline-метрик
Метрики offline-оценки удобно разложить по тому, как они считают «хорошо/плохо» — от простого к гибкому.
Правило выбора: иди слева направо и бери самое простое, что ловит нужное. Детерминированная проверка дешевле и стабильнее LLM-судьи — если ответ можно проверить кодом (валидный JSON, точное число, успех инструмента), не зови LLM. Судью (③) — только там, где нужен смысл, который кодом не ухватить.
Task completion rate
Для агента (не просто «ответчика») главная offline-метрика — task completion rate: доля задач, которые агент довёл до успешного результата. Это верхнеуровневый показатель «работает ли агент вообще», в отличие от метрик отдельного ответа.
Ключевой вопрос — как определить «успешно выполнил». Варианты по возрастанию гибкости:
- По конечному результату — детерминированная проверка: агент вернул правильное число / создал файл / выполнил транзакцию. Если исход проверяем кодом — идеально.
- По достижению цели — для нечётких задач цель проверяет LLM-судья: «решил ли ответ задачу пользователя?» (да/нет).
- Составной success — задача считается выполненной, если пройдены несколько под-условий (нашёл информацию И правильно оформил И уложился в шаги).
Важное различие: task completion rate оценивает результат («задача решена?»), а не то, как агент к нему шёл. Оценка самой траектории (правильные ли шаги, не было ли лишних вызовов) — это отдельная метрика, trajectory evaluation, ей посвящён урок в разделе «Тестирование агентов». Начинай с исхода: если агент не решает задачу, анализировать путь рано.
RAGAS: метрики для RAG
Если твой агент использует RAG (модуль 04), у оценки появляется специфика: качество зависит и от retrieval (нашли ли нужные документы), и от генерации (использовал ли ответ найденное без выдумок). Писать эти метрики с нуля не надо — есть RAGAS, специализированный фреймворк оценки RAG. Его метрики (считаются с помощью LLM) разложены по стадиям пайплайна:
| Метрика RAGAS | Что измеряет | Стадия |
|---|---|---|
| faithfulness | Ответ опирается на контекст, не выдумывает (анти-галлюцинация) | Генерация |
| answer relevancy | Ответ по существу вопроса, без воды | Генерация |
| context precision | Найденные документы релевантны (мало мусора) | Retrieval |
| context recall | Найдено всё нужное для ответа (ничего не упущено) | Retrieval |
Ценность разбивки — в локализации проблемы. Низкий faithfulness → модель галлюцинирует, чини промпт/генерацию. Низкий context recall → retriever не находит нужное, чини поиск/чанкинг/эмбеддинги. Одна метрика «плохой ответ» этого не разделила бы.
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset
# датасет: вопрос, ответ системы, найденный контекст, эталон (ground truth)
data = Dataset.from_dict({
"question": ["Какая столица Франции?"],
"answer": ["Столица Франции — Париж."],
"contexts": [["Париж — столица и крупнейший город Франции."]],
"ground_truth": ["Париж"],
})
result = evaluate(data, metrics=[
faithfulness, # не галлюцинирует?
answer_relevancy, # по существу?
context_precision, # retrieval точный?
context_recall, # retrieval полный?
])
print(result) # {'faithfulness': 1.0, 'answer_relevancy': 0.98, ...}
# низкий faithfulness → чини генерацию; низкий recall → чини retriever
Большинство метрик RAGAS вычисляются самой LLM (оценка faithfulness — это LLM, сверяющая ответ с контекстом). Отсюда следствия: оценка стоит токенов, не мгновенна и сама по себе имеет разброс (та же природа, что у LLM-as-judge — следующий урок). Точный набор метрик и API RAGAS меняются между версиями — сверяйся с актуальной документацией. Используй RAGAS на репрезентативной выборке, а не на каждом запросе прода.
Custom scorers по критериям
Готовые метрики покрывают типовое, но у твоего продукта свои критерии: «ответ в фирменном тоне», «не обещает того, чего компания не делает», «покрыл все пункты вопроса», «уложился в формат». Для этого пишут custom scorers — функции, возвращающие оценку по твоему критерию. Форма — та же, что у evaluator из LangSmith: на вход выход системы (и эталон), на выходе score.
Два вида по механике:
- Кодовые — критерий проверяется программно: длина, наличие обязательных секций, валидность формата, отсутствие стоп-слов. Дёшево и стабильно.
- LLM-based — критерий субъективный, судит LLM по рубрике: «оцени вежливость 1–5», «покрывает ли ответ все аспекты?». Гибко, но дороже и с разбросом.
# ① Кодовый scorer — проверяем формат детерминированно (дёшево, стабильно)
def has_required_sections(run, example) -> dict:
answer = run.outputs["answer"]
ok = all(section in answer for section in ["## Итог", "## Источники"])
return {"key": "format_ok", "score": 1.0 if ok else 0.0}
# ② LLM-based scorer — субъективный критерий через судью по рубрике
JUDGE_PROMPT = """Оцени ответ по критерию ВЕЖЛИВОСТЬ по шкале 1-5.
Верни ТОЛЬКО число.
Ответ: {answer}"""
def politeness(run, example) -> dict:
answer = run.outputs["answer"]
score = int(llm(JUDGE_PROMPT.format(answer=answer))) # 1..5
return {"key": "politeness", "score": score / 5} # нормируем к 0..1
# оба подключаются к experiment как обычные evaluators (см. урок LangSmith)
# evaluate(system, data="my-dataset", evaluators=[has_required_sections, politeness])
Прежде чем писать LLM-судью, спроси: «можно ли проверить критерий кодом?». Формат, длина, наличие полей, валидный JSON, стоп-слова — всё это кодовые scorers: быстрее, бесплатно, без разброса. LLM-судью береги для смысловых критериев (тон, полнота, релевантность), которые программно не выразить. Хороший набор offline-метрик — это смесь: много дешёвых кодовых + несколько дорогих LLM-based.
Типичные ошибки
«Правильность» не покрывает релевантность, тон, формат и галлюцинации. Оценивай по нескольким измерениям — иначе улучшишь одно, не заметив, как просело другое.
Проверять валидный JSON или точное число через LLM — дорого, медленно и менее надёжно, чем json.loads или ==. Детерминированное — кодом.
Offline-метрика хороша ровно настолько, насколько датасет похож на реальный трафик. 10 лёгких примеров дадут ложные 95% — а прод развалится на кейсах, которых в датасете не было. Наполняй датасет реальными трудными случаями (в т.ч. из прод-трейсов).
RAGAS/LLM-scorers сами шумят и могут ошибаться. Не принимай решение по одному прогону на 5 примерах; смотри на размер выборки, разброс и глазами проверяй спорные оценки судьи.
Шпаргалка
OFFLINE = оценка на фиксированном датасете ДО релиза (повторяемо, безопасно, в CI)
online = после релиза на живом трафике · offline ловит регрессию заранее
опирается на dataset + evaluator + experiment (урок LangSmith)
у LLM НЕТ одной accuracy: качество многомерно (верность/релевантность/тон/формат/...)
→ набор метрик, часто под задачу
3 ТИПА МЕТРИК (бери самое простое, что ловит нужное):
① детерминированные — код vs эталон: exact/fuzzy, JSON valid, regex, TASK COMPLETION
✓ быстро/дёшево/стабильно · «буквально верно?»
② специализированные — готовые под задачу: RAGAS (RAG), BLEU/ROUGE (текст)
③ custom scorers — свои критерии, часто LLM-judge · «хорош по смыслу?» · гибко/дорого
TASK COMPLETION RATE (главная для АГЕНТА): доля доведённых до успеха задач
успех: по результату (код) / по цели (judge) / составной
про ИСХОД, не про путь (путь = trajectory evaluation, др. урок)
RAGAS (для RAG, метрики через LLM):
faithfulness (не галлюцинирует) · answer_relevancy (по существу) ← генерация
context_precision (retrieval точный) · context_recall (полный) ← retrieval
разбивка ЛОКАЛИЗУЕТ проблему: низкий recall→retriever, низкий faith→генерация
CUSTOM SCORERS: кодовые (формат/длина/поля — дёшево) + LLM-based (тон/полнота — дорого)
правило: критерий → самый дешёвый scorer, который его ловит
ГРАБЛИ: одна метрика на всё · LLM-судья где хватит кода · нерепрезентативный датасет ·
слепо верить числу (шум judge, размер выборки)
Практическое задание
Задание: собери offline-оценку агента
- Для агента из модулей 03–05 определи task completion rate: как ты формализуешь «задача выполнена успешно»? Проверяемо ли это кодом или нужен судья?
- Разложи качество своего агента на 3–4 измерения (например: правильность, релевантность, формат, тон). Для каждого выбери тип метрики (① / ② / ③) и обоснуй.
- Если у агента есть RAG — прогони RAGAS на 5–10 примерах. Где метрика ниже: генерация (faithfulness) или retrieval (recall)? Что чинить?
- Напиши один кодовый custom scorer (формат/наличие полей) и один LLM-based (субъективный критерий по рубрике). Подключи оба как evaluators.
- Найди в своём наборе критерий, который ты сначала хотел отдать LLM-судье, но можно проверить кодом. Перепиши на кодовый scorer.
- Оцени датасет: репрезентативен ли он? Добавь 2–3 трудных реальных кейса (например, из прод-трейсов LangSmith/Langfuse). Как изменились метрики?
- Со звёздочкой: собери всё в один offline-прогон (experiment) для двух версий промпта и сравни по всем метрикам сразу. Какая версия победила и по каким измерениям проиграла?
Что дальше
Мы дважды упёрлись в «а тут судит LLM» — в RAGAS и в custom scorers. Но у автоматических метрик есть предел: некоторые вещи может оценить только человек. Следующий урок — про human evaluation: процесс разметки, согласованность оценщиков и как избегать предвзятости.