Разработка ИИ-агентов · Модуль 2 · Урок 2.6

Оценка и тестирование агентов

Почему агентов тестировать сложнее

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

Без оценки вы не знаете, стало ли лучше после правки промпта или инструмента. «Вроде работает» — не метрика. Оценка превращает разработку агента из угадывания в инженерию.

Из чего складывается оценка

  • Golden set — набор репрезентативных задач с ожидаемым исходом (не обязательно дословным ответом, а проверяемым свойством: «должен вызвать search_orders», «в ответе есть номер заказа», «не должен раскрывать политику»).
  • Программные проверки (assertions) — детерминированные: какой инструмент вызван, валидна ли структура, есть ли подстрока, уложился ли в число шагов и бюджет.
  • LLM-as-judge — там, где «правильность» субъективна (тон, полнота, корректность объяснения), ответ оценивает другая модель по чёткой рубрике. Сильный приём, но к судье относятся критично: он тоже ошибается, его рубрику надо калибровать.

Хороший набор смешивает дешёвые программные проверки (быстрые, в CI) и точечный LLM-judge для нюансов.

Тесты на Go и метрики

На Go удобно оформлять оценку как табличные тесты: список кейсов {вход, проверка}. Для детерминированных свойств (вызван инструмент, формат вывода, число шагов) это обычные go test. Для онлайн-вызовов модели тесты делают отдельным (медленным) набором, который гоняют по требованию, а быстрые тесты на логику цикла/парсинга — постоянно.

Полезные метрики агента, кроме «решено/нет»: доля успешных задач (success rate), среднее число шагов и токенов (стоимость), задержка, частота срабатывания safety-cap. Меняя промпт или инструмент, прогоняйте golden set и сравнивайте метрики до/после — так видно регрессии. Это и есть инженерный цикл: измерили → изменили → снова измерили.

Табличный тест: проверяем поведение агента, а не точный текст
func TestAgentBehavior(t *testing.T) {
    cases := []struct {
        name      string
        question  string
        wantTool  string // ожидаемый инструмент ("" — без вызова)
        wantInAns string // подстрока, которая должна быть в ответе
    }{
        {"арифметика", "сколько будет 2+2?", "add", "4"},
        {"приветствие", "привет, кто ты?", "", "агент"},
    }
    for _, c := range cases {
        t.Run(c.name, func(t *testing.T) {
            res := runAgentTraced(c.question) // возвращает ответ + список вызовов
            if c.wantTool != "" && !res.calledTool(c.wantTool) {
                t.Errorf("ожидали вызов %q, вызовы: %v", c.wantTool, res.tools)
            }
            if !strings.Contains(strings.ToLower(res.answer), c.wantInAns) {
                t.Errorf("в ответе нет %q: %s", c.wantInAns, res.answer)
            }
        })
    }
}
LLM-as-judge: оценка по рубрике для субъективных критериев
// judge просит другую модель оценить ответ по чёткой рубрике и вернуть JSON.
func judge(question, answer string) (bool, string) {
    rubric := "Оцени ответ на вопрос. Критерии: по делу, без выдумок, есть вывод. " +
        "Верни JSON: {\"pass\":true|false,\"reason\":\"...\"}."
    choice, err := callLLM([]Message{
        {Role: "system", Content: rubric},
        {Role: "user", Content: "Вопрос: " + question + "\nОтвет: " + answer},
    }, nil)
    if err != nil {
        return false, "ошибка судьи: " + err.Error()
    }
    var v struct {
        Pass   bool   `json:"pass"`
        Reason string `json:"reason"`
    }
    _ = json.Unmarshal([]byte(stripFences(choice.Content)), &v)
    return v.Pass, v.Reason
}

Anti-patterns

Анти-паттернПочему плохоКак правильно
Сравнивать ответ агента дословноНедетерминизм — тест всегда «красный»Проверять свойства: вызван инструмент, есть факт, формат, рамки
«Вроде работает» вместо метрикНельзя понять, стало ли лучше после правкиGolden set + метрики до/после каждого изменения
Слепо верить LLM-судьеСудья тоже ошибается, рубрика не калиброванаКалибровать рубрику, сверять с ручной разметкой, смешивать с программными проверками
Гонять только дорогие онлайн-тестыМедленно, дорого, редко запускаютБыстрые детерминированные тесты в CI + точечный LLM-judge по требованию

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

  • Соберите golden set из 5–8 задач с проверяемым свойством (а не дословным ответом).
  • Напишите табличный go test, проверяющий вызов нужного инструмента и наличие ключевой подстроки.
  • Добавьте трассировку агента: сохраняйте список вызванных инструментов и число шагов.
  • Для одного субъективного критерия примените LLM-as-judge с чёткой рубрикой и JSON-вердиктом.
  • Зафиксируйте метрики (success rate, среднее число шагов/токенов) и сравните их до и после правки промпта.

Проверка знаний

Почему нельзя оценивать агента сравнением ответа с эталонной строкой?

  • A Строки в Go сравнивать нельзя
  • B Агент недетерминирован: формулировки и путь решения варьируются, поэтому проверяют свойства, а не точный текст
  • C Эталонные ответы запрещены лицензией
  • D Это слишком быстро

Что такое LLM-as-judge и в чём его ограничение?

  • A Модель, которая пишет код агента; ограничений нет
  • B Оценка ответа другой моделью по рубрике для субъективных критериев; ограничение — судья тоже ошибается и требует калибровки
  • C Это синоним unit-теста
  • D Способ ускорить агент