Разработка ИИ-агентов · Модуль 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)
}
})
}
}// 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, среднее число шагов/токенов) и сравните их до и после правки промпта.
Проверка знаний
Почему нельзя оценивать агента сравнением ответа с эталонной строкой?
Верный ответ: B
B верно. Из-за недетерминизма точное совпадение бессмысленно; проверяют достижение цели и свойства (вызван инструмент, есть факт, формат). A неверно технически; C и D не по делу.
Что такое LLM-as-judge и в чём его ограничение?
Верный ответ: B
B верно. LLM-судья хорош для субъективных свойств (тон, полнота), но не безошибочен: рубрику калибруют и сверяют с ручной разметкой, сочетая с детерминированными проверками.