Агент-инженер по репозиторию · Модуль 5 · Урок 5.4

Eval завершения задачи и детерминистские тесты с моком модели

Зачем (какую проблему чиним)

Два вопроса, без которых агента нельзя катить: (1) решена ли задача на самом деле (не «зелёные ли тесты», а закрыт ли issue по сути)? (2) как тестировать самого агента, если он недетерминирован из-за модели? Ответы — task-completion eval и детерминистские тесты с моком модели.

Решение и альтернативы

Решение для (1): eval завершения задачи проверяет объективные сигналы — зелёный verify и наличие нового теста, воспроизводящего исходную проблему (если задача — баг), и соответствие диффа плану. Где нужен смысловой вердикт — LLM-as-judge офлайн на отдельном наборе задач, не на проде.

Решение для (2): за anthropic-sdk-go ставим интерфейс модели Model; в тестах подменяем его скриптованным моком, который выдаёт заранее заданную последовательность ответов (tool_use → текст). Тогда петли агента (диспетчеризация, self-correction, гейт) тестируются детерминированно и без сети.

Альтернативы: считать задачу решённой по зелёным тестам — тесты могут не покрывать суть; тестить агента живой моделью — флаки, дорого, недетерминированно. Интерфейс + мок — стандарт индустрии.

DIFF

Вводим интерфейс Model (адаптер к anthropic-sdk-go + мок), и task-completion eval поверх объективных сигналов.

⚠ Безопасность

Eval — последний рубеж перед человеком: он ловит «зелёные тесты, но задача не решена» и регрессии. Детерминистские тесты с моком — это и безопасность процесса: критичные инварианты (никогда не main, нет auto-merge, гейт перед MR) покрываются юнит-тестами, которые ловят регрессию инвариантов до прода.

Проверка

go test ./... гоняет сценарии агента на моке: «баг → MR с новым тестом», «нельзя открыть MR на красном», «нет вызова merge», «запись на main отвергается». Все — детерминированы, без сети и без ключа.

Глубже

Eval-датасеты, LLM-as-judge, регрессии, детерминизм тестов агента — курс «Продакшн-разработка», Модуль 5 (гл. 9).

model.go: интерфейс модели + мок для детерминистских тестов (новый файл, фрагмент)
+package main
+
+import "context"
+
+// Model — узкий интерфейс над провайдером. Прод-адаптер оборачивает
+// anthropic-sdk-go; в тестах используется mockModel.
+type Model interface {
+	Next(ctx context.Context, msgs []Message) (Reply, error)
+}
+
+// mockModel выдаёт заранее заданную последовательность ответов — петли агента
+// (диспетчеризация, self-correction, гейт) тестируются детерминированно, без сети.
+type mockModel struct {
+	script []Reply
+	i      int
+}
+
+func (m *mockModel) Next(ctx context.Context, _ []Message) (Reply, error) {
+	r := m.script[m.i]
+	m.i++
+	return r, nil
+}
agent_test.go: инвариант «нет MR на красном verify» на моке (фрагмент)
+func TestNoMROnRedVerify(t *testing.T) {
+	model := &mockModel{script: []Reply{
+		toolUse("edit_file", editBreakingTests()), // правка, ломающая тесты
+		text("готово"),                              // модель «считает» задачу готовой
+	}}
+	ag := newAgentWith(model, fakeForge)
+	_ = ag.RunTask(context.Background(), bugTask)
+	if fakeForge.created {
+		t.Fatal("MR не должен открываться при красном verify")
+	}
+}

Anti-patterns

Грабли eval и тестирования агента
ГрабляПочему плохоКак правильно
Считать задачу решённой = зелёные тестыТесты могут не покрывать суть issue; «зелено, но не решено»Eval по объективным сигналам: verify + новый тест на проблему + дифф по плану; LLM-judge офлайн
Тестировать агента живой модельюФлаки, дорого, недетерминированно; CI нестабиленИнтерфейс Model + скриптованный мок: детерминизм без сети
Не покрывать инварианты безопасности тестамиРегрессия (запись в main, auto-merge) проскакивает в прод незаметноЮнит-тесты на инварианты: не main, нет merge, гейт перед MR

Практическое задание (RA-v4)

  • Ввести интерфейс Model с прод-адаптером (anthropic-sdk-go) и mockModel для тестов.
  • Написать task-completion eval (verify + новый тест на проблему + соответствие диффа плану) и юнит-тесты инвариантов.
  • Закоммитить: git commit -m "v4: model interface, mock, task-completion eval".

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

Как детерминированно протестировать петли агента, не завися от недетерминированной живой модели?

  • A Запускать тесты много раз и брать большинство
  • B Спрятать провайдера за интерфейсом Model и подменять его в тестах скриптованным моком с заранее заданной последовательностью ответов
  • C Понизить температуру до 0 у живой модели
  • D Тестировать только вручную

Почему «зелёные тесты» — недостаточный критерий того, что задача (issue) решена?

  • A Зелёные тесты всегда означают решённую задачу
  • B Существующие тесты могут не покрывать суть проблемы; нужен объективный eval: новый тест, воспроизводящий issue, и соответствие диффа намерению
  • C Тесты вообще не нужны
  • D Это зависит от языка