Почему мало финального ответа

Оценка только результата (task completion из урока про offline-метрики) отвечает «дошёл ли агент до цели». Но у агента путь к цели — часть качества, и по одному ответу невидимы целые классы проблем:

  • Правильный ответ неправильным путём. Агент угадал итог, но вызвал не тот инструмент, проигнорировал данные, «повезло». В проде на другом входе везение кончится.
  • Неэффективность. Тот же результат за 10 шагов вместо 2 — это латентность и деньги, которых в финальном ответе не видно.
  • Зацикливание и блуждание. Агент ходит по кругу, повторяет вызовы, «размышляет» вхолостую — и всё же выдаёт ответ.
  • Опасные действия. Дошёл до правильного ответа, но по дороге дёрнул инструмент удаления/оплаты, который не следовало.
ℹ️ Outcome vs trajectory — две оси качества агента

У агента два независимых измерения: outcome (правильный ли результат) и trajectory (правильным ли путём он получен). Хороший агент силён по обеим. Оценка только outcome пропускает «правильно по случайности» и «дорого/опасно по дороге»; оценка только trajectory — рискует придраться к нестандартному, но верному пути. Нужны обе: outcome отвечает «решил ли задачу», trajectory — «решил ли её хорошо».

Что такое траектория

Траектория (trajectory) — это записанная последовательность шагов агента от запроса до ответа: какие инструменты вызваны, с какими аргументами, что вернули, какие узлы графа посещены, сколько итераций. По сути это тот самый трейс из раздела observability, но рассматриваемый как объект оценки, а не отладки.

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
Одинаковый ответ — разные траектории ✓ Эффективная запрос search("EV 2025")нужный tool ответ ✓ 2 шага · 1 200 токенов · 1.2s ✗ Блуждающая (тот же ответ!) запрос calc() ← лишнийне тот tool search(?) пусто search(retry) calc() снова ответ ✓ 10 шагов · 12 000 токенов · 9s · дёрнул опасный calc · «повезло» в конце Траектория фиксирует ПУТЬ, невидимый в финальном ответе: • какие tools вызваны + аргументы • порядок и число шагов / итераций • результаты вызовов, узлы графа • лишние / опасные действия • зацикливания и повторы • стоимость и латентность пути

Оба агента на диаграмме дали верный ответ — outcome-оценка поставила бы обоим «pass». Но траектории кардинально разные: первый эффективен, второй блуждал, дёргал не те инструменты и «угадал». Trajectory evaluation делает эту разницу измеримой.

Сравнение с эталонной траекторией

Базовый подход: задать эталонную траекторию (какие инструменты в каком порядке ожидаются) и сравнить с фактической. Но «сравнить» — не всегда «точно совпасть»: у задачи может быть несколько правильных путей. Отсюда несколько режимов строгости:

РежимЧто проверяетКогда
Exact matchТочно те инструменты, в точном порядке, без лишнихСтрогий процесс, единственно верный путь
In-orderОжидаемые инструменты в нужном порядке, лишние допустимыПорядок важен, но шаги можно добавлять
Any-orderНужные инструменты вызваны, порядок не важенПорядок свободный (например, параллельный сбор)
Superset / поднаборВсе нужные вызваны (плюс-минус лишние)Главное — не пропустить обязательные шаги
Сравнение траектории с эталоном (концептуально)
python
# из трейса достаём фактическую последовательность вызванных инструментов
def tool_sequence(trace) -> list[str]:
    return [step.tool for step in trace.steps if step.type == "tool"]

expected = ["search", "summarize"]          # эталонная траектория
actual   = tool_sequence(run_trace)         # напр. ["search", "calc", "summarize"]

def exact_match(exp, act):    return exp == act
def in_order(exp, act):                       # exp — подпоследовательность act
    it = iter(act)
    return all(tool in it for tool in exp)
def any_order(exp, act):      return set(exp) <= set(act)

assert in_order(expected, actual)            # порядок соблюдён, calc — лишний
# для регрессии по траектории: эталон в golden dataset, режим — под задачу
⚠️ Не делай exact match там, где путей несколько

Соблазн — жёсткий exact match: «ожидаю ровно эти шаги». Но у многих задач несколько правильных траекторий (искать в вебе или в БД — обе верны). Слишком строгое сравнение будет штрафовать корректные, но нестандартные пути — тест начнёт флапать и мешать легитимным улучшениям. Выбирай режим по природе задачи: exact — только для процессов с единственно верным порядком; чаще нужен in-order/any-order или оценка судьёй.

Что оценивать в траектории

Сравнение с эталоном — лишь один аспект. Полная оценка траектории смотрит на несколько измерений:

  • Tool selection. Выбраны ли правильные инструменты под задачу? Не вызван ли ненужный (или, хуже, опасный)?
  • Аргументы вызовов. Переданы ли инструментам корректные параметры (правильный запрос в поиск, верные поля)?
  • Эффективность. Число шагов/итераций, токены, латентность. Достигнут ли результат минимальным разумным путём?
  • Отсутствие блужданий. Нет повторов, зацикливаний, «пустых» размышлений, лишних вызовов.
  • Безопасность пути. Не дёрнуты действия, которых не следовало (удаление, оплата, запись) — даже если ответ верный.
✅ Эффективность траектории = деньги и латентность

Особенно ценное измерение для прода: два агента с верным ответом, но один за 2 шага, другой за 10 — это 5× разница в стоимости и времени (вспомни cost/latency-метрики из урока про дашборды). Trajectory evaluation ловит «правильно, но дорого» ещё до прода — на датасете видно, что новая версия промпта стала решать те же задачи в 2 раза длиннее. Outcome-оценка этого не покажет: ответ-то верный.

LLM-судья по траектории

Многое в траектории — детерминированно (какие tools, сколько шагов) и проверяется кодом. Но некоторые вопросы требуют суждения: «разумна ли была эта последовательность?», «оправдан ли лишний вызов?», «логичен ли ход рассуждений?». Здесь — тот же LLM-as-judge, но судящий путь, а не ответ:

LLM-судья оценивает разумность траектории
python
TRAJ_JUDGE = """Оцени РАЗУМНОСТЬ последовательности действий агента (не ответ).
Задача: {task}
Траектория (шаги агента):
{trajectory}

Критерий: были ли выбраны уместные инструменты, без лишних/повторных
вызовов, разумным ли путём достигнута цель? Шкала 1–5.
Сначала обоснуй, потом верни JSON {{"reasoning": "...", "score": <1-5>}}"""

def judge_trajectory(task: str, trace) -> dict:
    steps = format_steps(trace)     # человекочитаемый список шагов из трейса
    raw = llm(TRAJ_JUDGE.format(task=task, trajectory=steps), temperature=0)
    return json.loads(raw)

# всё из урока про judge применимо: калибровать по людям, следить за искажениями,
# детерминированное (число шагов, набор tools) — считать КОДОМ, а не судьёй
⚠️ Судья по траектории наследует все правила урока про judge

Оценка пути судьёй — частный случай LLM-as-judge, со всеми оговорками: судья недетерминирован, подвержен искажениям, его надо калибровать по человеческой разметке траекторий. И тот же принцип «дешёвое — кодом»: число шагов, набор вызванных инструментов, наличие опасного вызова — детерминированные проверки; судью зови только для «разумности» пути, которую кодом не выразить.

Типичные ошибки

❌ Ошибка 1: оценивать только финальный ответ

«Ответ верный → всё хорошо» пропускает правильно-по-случайности, неэффективность, опасные действия по дороге. Для агента оценивай и outcome, и trajectory.

❌ Ошибка 2: жёсткий exact match при нескольких путях

Если у задачи несколько верных траекторий, exact match штрафует корректные нестандартные пути и флапает. Выбирай режим (in-order/any-order/judge) по природе задачи.

❌ Ошибка 3: игнорировать эффективность

Верный ответ за 10 шагов вместо 2 — это скрытые деньги и латентность. Меряй число шагов/токенов траектории, иначе пропустишь дорогую деградацию.

❌ Ошибка 4: судьёй считать то, что считается кодом

Число шагов, набор инструментов, наличие опасного вызова — детерминированны, LLM-судья тут дорог и менее надёжен. Судью — только для «разумности» пути.

Шпаргалка

Trajectory evaluation — всё в одном месте
text
АГЕНТ = вопрос → ЦЕПОЧКА решений → ответ. Оценивай ПУТЬ, не только пункт назначения.
  2 оси: OUTCOME (верный результат?) + TRAJECTORY (верным ли путём?)
  только outcome пропускает: верно-по-случайности · неэффективность · блуждание · опасные действия

ТРАЕКТОРИЯ = записанная последовательность шагов (= трейс как ОБЪЕКТ ОЦЕНКИ):
  какие tools + аргументы · порядок/число шагов · результаты · узлы графа · стоимость пути

СРАВНЕНИЕ С ЭТАЛОНОМ (режимы строгости):
  exact match — ровно те tools, тот порядок (единственно верный путь)
  in-order — ожидаемые по порядку, лишние ок · any-order — набор нужных, порядок не важен
  superset — все обязательные вызваны
  ⚠️ НЕ exact там, где путей несколько → штрафует верные нестандартные, флапает

ЧТО ОЦЕНИВАТЬ: tool selection (те ли tools?) · аргументы · ЭФФЕКТИВНОСТЬ (шаги/токены/latency)
  · отсутствие блужданий/повторов · БЕЗОПАСНОСТЬ пути (не дёрнут опасный вызов)
  эффективность = деньги+латентность, ловит «верно, но дорого» ДО прода

LLM-СУДЬЯ ПО ТРАЕКТОРИИ (для «разумности» пути):
  частный случай LLM-as-judge → калибровать по людям, следить за искажениями, temp=0
  детерминированное (# шагов, набор tools, опасный вызов) — КОДОМ, не судьёй

ГРАБЛИ: только финальный ответ · exact при неск. путях · игнор эффективности · судья вместо кода

Практическое задание

Задание: оцени траектории агента

  1. Для агента из модулей 03–05 возьми один запрос и запиши его траекторию из трейса (LangSmith/Langfuse): список вызванных инструментов с аргументами и число шагов.
  2. Задай эталонную траекторию для этой задачи. Один ли верный путь или несколько? Какой режим сравнения (exact/in-order/any-order) уместен и почему?
  3. Напиши функцию, которая достаёт последовательность tools из трейса и сравнивает с эталоном в выбранном режиме. Проверь на «хорошей» и «блуждающей» траекториях.
  4. Измерь эффективность: число шагов и токены на двух версиях промпта для одной задачи. Стала ли новая версия длиннее при том же ответе?
  5. Найди в траектории потенциально опасный вызов (запись/удаление/оплата). Как детерминированно проверить, что агент его не делает без нужды?
  6. Напиши judge-промпт, оценивающий «разумность» траектории. Что ты доверишь судье, а что посчитаешь кодом? Как откалибруешь судью?
  7. Со звёздочкой: добавь trajectory-проверку в regression-набор из прошлого раздела: пусть тест падает не только при неверном ответе, но и при деградации пути (лишние шаги / не тот инструмент).

Что дальше

Раздел «Тестирование агентов» закрыт: юниты (компоненты), интеграционные (стыки), датасеты (тест-кейсы) и trajectory (путь) вместе с оценкой качества дают полную защиту агента как программы и как LLM-системы. Но даже идеально протестированный агент в проде встретит то, что не зависит от него: провайдер упадёт, API затаймаутит, придёт лимит. Следующий раздел — «Надёжность и обработка ошибок»: retry, fallback, rate limiting и graceful degradation.