Когда тесты врут и что трассировать
Вторая часть серии про доказательства. Сначала о том, почему тесты при агентной генерации перестали ими быть, затем — о том, какая телеметрия делает агентный прогон понятным и для человека, и для модели.
Когда тесты врут
Доклад Джастина Кормака «When Tests Lie» (AI Native DevCon) — самый неудобный материал темы: он о том, что перестало быть доказательством. Кормак ничего не продаёт: в расшифровке его компания не названа, проект личный. Это редкий случай, когда цифры приходят без продуктовой ставки за ними.
Контекст: он написал (точнее, AI написал под его руководством) хранилище, совместимое с S3. 350 000 строк Rust. Код он не читал, Rust знает наполовину. Неделя рефакторинга — 120 000 добавленных и 75 000 удалённых строк. Один файл вырос до 43 000 строк, пока не пришлось его разбирать. 5000 тестов проходят за две минуты; две минуты — его личный порог приемлемости, всё, что дольше, перестают гонять.
И на этом фоне — его главный вывод: 5000 тестов и почти полное покрытие ничего не доказывают.
Почему 100% покрытия — ловушка
Кормак честно попробовал догнать покрытие до 100% на большом проекте. Агент написал тривиальные тесты и тесты на невоспроизводимые ошибки — например, на отказ генератора случайных чисел. Такие тесты не падают никогда и не ловят ничего. Они просто существуют, чтобы строка была закрашена. Он отказался от цели и осел на 75–100% по файлам, без фанатизма.
Его формулировка через Деминга: «Looking at the lines of code does not actually find the bugs».
Дальше — список того, чего тесты не находят в принципе, и это ядро доклада:
- Проблемы безопасности. Тест проверяет то, что ты предположил; атака — то, что ты не предположил.
- Качество архитектуры. Тесты не отвечают на вопрос «хороша ли эта архитектура». Зелёный прогон совместим с любым уровнем архитектурного долга.
- Всё, что ты не умеешь измерить. Не сформулировал — не проверяется.
И жёсткое следствие: «Focusing on test coverage means you're not thinking about security enough». Гонка за покрытием вытесняет мышление о безопасности: она съедает то же внимание.
Отсюда его переопределение роли тестов: «Tests are really discovery tools». Тест — инструмент разведки пространства поведений. Метрика хорошего тест-сьюта — количество нового, которое он рассказал о системе. У Кормака индикатор был конкретный: его тесты нашли две воспроизводимые 500-е ошибки в самом S3. Это и стало сигналом, что сьют действительно исследует пространство.
С той же стороны заходит Дру Нокс (Tessl, вендор — head of product AI) в разговоре со Стивом Йегги «You'll Never Write Code the Same Way Again»: «Coverage can often be theater». У них на каждом PR требуется почти 100% покрытия внутри диффа — и они прямо говорят, что это ничего не доказывает.
Что тогда служит доказательством
Ни один из спикеров не предлагает замену «одна метрика вместо другой». Предлагается набор источников доказательства, у каждого своя цена и свои границы.
| Приём | Что доказывает | Ограничение (по словам самого спикера) |
|---|---|---|
| Внешний оракул (реальная система) | Что поведение совпадает с эталонным | Оракул не равен спецификации; S3 частично eventually consistent, нужны ретраи |
| Mutation testing | Что тесты вообще способны поймать поломку | Много ложных срабатываний, агенту нельзя дать действовать напрямую |
| Типы | Что класс багов невозможен | Работает не везде; требует перепроектирования, а не дописывания тестов |
| AI security review всего кода | Находит то, что пропустило AI code review | Около трёх четвертей находок валидны, остальное — шум для разбора |
| Fuzz и property-based | Находит края, которые никто не формулировал | Не заменяет понимание, только расширяет разведку |
| Post-conditions из спеки | Даёт acceptance criteria, проверяемые тестами | Опыт консалтинга на бизнес-приложениях, не продуктовая разработка |
Разберём по порядку.
Оракул — сильнейший приём Кормака. Он использовал живой S3 как эталон: 1500 тестов бьются в реальный S3, фиксируют его поведение, дальше агенту говорят «делай ровно так». Обобщение, которое он даёт залу: если строишь сложную систему — сначала напиши тривиальную версию с тем же поведением и используй её как оракул. Ограничение честное: S3 частично eventually consistent, приходится добавлять ретраи, и оракул не равен спецификации — он равен тому, как система ведёт себя сегодня.
Проблема тавтологии оракула — вопрос из зала, и ответ на него важнее самого приёма. Если оракул пишет тот же AI тем же способом, ты просто запускаешь одну логику дважды и не тестируешь ничего. Оракул должен приходить другим путём: в идеале это внешняя система, иначе — держать его вне репозитория как зафиксированную модель. Кормак признаёт, что ему было проще: S3 внешний и существующий.
Документация — источник гипотез. Сотни страниц документации S3, по его опыту, «в каждой детали оказываются неверны». Его правило: «Never trust anyone's documentation at all». Польза доков в том, что они подсказывают, где искать интересное.
Тут же — разделение труда, которое стоит запомнить. Человек хорошо вычитывает edge cases из документации, агент — плохо. Человек читает доки AWS и спрашивает «а это правда?», и находит края. Агент, направленный на те же доки, этого не умел. Зато агент отлично пишет тесты по подсказанным гипотезам и по типовым краям — 0, 1, 10001. То есть гипотезы генерирует человек, объём проверок — агент.
Публичный API не покрывает невидимое поведение. Через публичное API нельзя увидеть, когда объект действительно удаляется в S3. Кормак отдельно жалеет, что не построил интерфейсы управления и отчётности: по ним можно было бы тестировать внутренности. Практический вывод: наблюдаемость системы — это тоже тестируемость, и её надо проектировать заранее.
Иногда правильный ответ — типы. Проблема TOCTOU с двойной проверкой прав решилась типом authorized request: функции за воротами принимают только авторизованные запросы. Класс багов исчез вместе с необходимостью его тестировать.
Mutation testing как доказательство того, что тесты живые — практика Tessl (Нокс, вендор). Поверх требования покрытия внутри диффа код мутируют так, чтобы сломать, и смотрят, поймали ли тесты. Отчёт читает агент и оставляет комментарии — напрямую действовать ему не дают, слишком много ложных срабатываний. Раз в неделю — прогон по всей кодовой базе. Заявленный эффект — «почти всегда приводит к улучшению тест-сьюта». Цифр не приведено.
Оттуда же самое частое слепое пятно тест-агентов: они никогда не проверяют ноль. Бизнес-логика вида «этот список никогда не будет пустым» не выражена в коде, а тест-агенты любят проверить 5 и 50. Граничные условия — главный улов mutation testing.
Fuzz и property-based — обычный метод, без экзотики. Рабочая практика Кормака: после каждого инцидента спросить агента «какие тесты поймали бы это?». Агент предлагает новые классы тестов. Fuzz и property-based при этом находили реальные баги.
AI security review отдельно от AI code review. Кормак прогоняет Codex Security, находки чекинит в репозиторий и просит агента их разобрать. Примерно три четверти находок валидны. Добавляются регулярные сессии «как мы могли этого избежать, какой тест это ловит». Его вывод: ревью всего состояния кода полезнее, чем только ревью PR.
Спека как источник проверяемых условий. Мартинелли («Lessons from Spec-driven Development»): post-conditions сценариев использования — это и есть acceptance criteria, которые прямо проверяются тестами. У его клиента команда сократилась с 5–7 до 1–2 человек на self-contained system, спецификацию пишут две недели, реализуют быстрее. Оговорка: консалтинг на бизнес-приложениях.
Отдельно: флаки-тесты и почему правило не работает
У Кормака жёсткое правило: никаких флаки-тестов, чиним немедленно. Агент систематически его игнорирует — потому что в обучающих данных разработчики флаки не чинят. Правило прописано в AGENTS.md, и это не помогает. Приходится буквально ругаться.
Отдельный антипаттерн: агент «подгоняет» код под сегодняшнее поведение AWS, потом обратно, вместо того чтобы починить тест. Лечится скоростью: быстрые тесты, много прогонов, ночные прогоны на нескольких машинах ловят редкие флейки.
Это общий принцип, который стоит вынести за пределы тестов: если поведение агента противоречит твоему правилу, но совпадает с тем, как обычно делают в индустрии, правилом ты его не переубедишь. Нужен механизм, который делает нарушение видимым.
Второй механизм оттуда же — самодельный трейсинг внутри проекта, отдельно от прод-мониторинга. Кормак попросил агента построить поддерживаемый tracing framework прямо в кодовой базе. Результат: можно отдать агенту трейс из ночного прогона и сказать «вот что случилось», вместо того чтобы он гадал. Его предупреждение: «If you give an AI a bug but you don't know how to repro it... it can waste a lot of time». Ограничение честное — у самодельного трейсинга были накладные расходы, из-за чего он врал в performance-тестах: годился для ответа «где большие провалы» и не годился для точных чисел.
Провал, который стоит помнить
Со сцены: модель «идеально» решала демо, потому что знала ответы
- Кто и где: Мирко Новакович, CEO Dash0 (ex-founder Instana), доклад «Why Most Observability Platforms Won't Survive the AI Agent Era», AI Native DevCon. Вендор — и одновременно автор тезиса о невыживании платформ.
- Что показали: у OpenTelemetry есть открытое демо-приложение с намеренно заложенными и задокументированными проблемами. Первые эксперименты команды с LLM на нём давали превосходные результаты — модель находила проблемы уверенно и точно. Пока не выяснилось, что она обучена на документации этих самых проблем.
- Цифры: не приведены — и это часть урока: «превосходный результат» не был измерен ни против чего внешнего.
- Что это меняет: классическая утечка бенчмарка, и команда чуть не приняла её за качество продукта. Если ты оцениваешь агента на публичном демо, открытом репозитории или известном датасете, ты, скорее всего, измеряешь память модели. Оценивать надо на своём, закрытом, желательно свежем.
- Цитата: «It could cheat the test because it knew the problems up front».
Соедини это с оракулом Кормака — получится одно правило. Доказательство должно приходить путём, которого не было в обучающих данных агента и который агент не контролирует. Внешняя система, мутация твоего собственного кода, зафиксированная вне репозитория модель поведения, свежий инцидент. Всё остальное рискует оказаться пересказом.
Коротко
- Тесты — инструмент разведки, доказательством корректности они не служат. «Tests are really discovery tools» (Кормак, «When Tests Lie», не вендор). 5000 тестов и покрытие 75–100% на 350 000 строк не отвечают на вопрос, работает ли система.
- Три вещи тесты не находят никогда: проблемы безопасности, качество архитектуры и всё, что ты не умеешь измерить. Гонка за покрытием прямо вытесняет мышление о безопасности.
- Доказательство приходит извне: внешний оракул (1500 тестов против живого S3), mutation testing (Нокс, Tessl — вендор), типы вместо тестов, AI security review по всей кодовой базе (около трёх четвертей находок валидны).
- Оракул, написанный тем же AI, — тавтология. Он должен приходить другим путём, в идеале из внешней системы.
- Утечка бенчмарка выглядит как успех. Кейс Dash0 на OTel-демо: модель «решала» задачи, потому что была обучена на описании их проблем.
Что трассировать в агентном прогоне
Главный доклад по теме — «Agents Observability with OpenLLMetry» Нира Газита (Traceloop). Сразу оговорка: Traceloop — коммерческая компания вокруг open-source проекта, то есть вендор, продающий платформу поверх OpenTelemetry. Продуктовая ставка не обесценивает техническую часть и объясняет её перекос: в докладе много про «подключается двумя строчками» и мало про то, когда этого не хватает.
OpenLLMetry — это не новый протокол
Первое, что стоит понимать: OpenLLMetry — набор инструментаций поверх OTel, растянутый на GenAI. Механика — monkey patching SDK провайдеров и фреймворков, всё происходит на стороне приложения. Экспорт в любую платформу меняется переменными окружения. По цифрам доклада: более 40 поддерживаемых фреймворков и провайдеров, подключение — две строки кода и установка SDK. Название, по рассказу Газита, родилось из твита Патрика за две недели до релиза.
Если OTel в кодовой базе уже стоит, обёртка в две строки не нужна: инструментации лежат отдельными пакетами, ставятся напрямую и работают с auto-instrumentation. Две строки — удобство для тех, кто не хочет разбираться с настройкой OTel. Архитектурной необходимости в них нет.
Раскладка по трём столпам
Газит даёт конкретную раскладку — что и куда пишется в агентном прогоне.
| Столп | Что кладём | Почему сюда |
|---|---|---|
Logs | Промпты, completions, вызовы функций | Большие текстовые блобы: важен и факт события, и точное содержимое |
Metrics | Token usage, latency, error rate | Агрегаты по временным окнам — то, что имеет смысл считать |
Traces | Многошаговый прогон целиком: цепочка LangChain, полное исполнение агента | Нужен и каждый шаг отдельно, и весь процесс сразу |
Для RAG-пайплайна этого мало. Инструментация Pinecone, по описанию Газита, логирует все запросы, индексы и возвращённые векторные данные, а также метрики латентности и релевантности — scores. Вывод общий: наблюдаемость RAG обязана покрывать retrieval, и одного вызова модели мало. Если ты видишь только промпт и ответ, ты не отличишь плохую генерацию от плохого поиска.
Чем это отличается от классической трассировки
Разницу Газит показывает демо. Агент с одним инструментом — веб-поиском. Вывод в консоль нечитаем: месиво. Трейс того же прогона показывает цепочку:
agent call
└─ tool call
└─ фактический запрос: «current weather in Tel Aviv»
└─ JSON-ответ инструмента
└─ вызов модели
└─ итоговый ответ
Его формулировка по итогам демо: «If you can't compare this to the messy logs that we had here — I think it's clear what's better».
Ценность здесь в связке: весь процесс сразу и точное содержимое каждого шага. Классический трейс отвечает на вопрос «где потратилось время». Агентный трейс должен отвечать ещё и на «почему было принято это решение» — а для этого в спане должно лежать содержимое вместе с таймингом и статусом.
Отсюда и главное честное ограничение доклада: Газит сам говорит, что базового tracing мало. Визуализация графа исполнения агента ещё не построена, это его личное «жду в будущем». То есть на сегодня ты получаешь структурированную запись прогона, но не картину того, как агент ходил по пространству решений.
Тот же запрос с другой стороны формулирует Новакович (Dash0, вендор): главный запрос пользователей — понять, почему агент пришёл к выводу «проблема в базе». Нужны видимые шаги рассуждения и переходы между ними. В агентную эру объектом наблюдаемости становится и система, и сам агент-расследователь.
Недетерминизм: что с ним делать практически
Самый дешёвый и конкретный приём во всём материале — от Газита. У OpenAI есть поле system_fingerprint: какая внутренняя система обслуживала запрос. Оно меняется довольно часто. Люди жалуются «на этой неделе модель поглупела», и во многих случаях причина видна прямо в телеметрии: при том же названии модели сменился fingerprint.
Это позволяет отделить две вещи, которые иначе неразличимы:
- дрейф провайдера — поменялось у них;
- собственная регрессия — поменялось у тебя.
Ограничение Газит называет сам: внутрь «надстроек» провайдера — JSON schema, assistants — видимости нет. Он относит это к желаемому, но недоступному.
Второй практический слой — OTel Collector. Газит перечисляет две задачи, ради которых его ставят: слать данные в несколько мест сразу (Datadog и Honeycomb) и вычищать персональные данные до отправки. Для агентных трейсов второе критично, потому что в спанах лежат промпты и ответы — телеметрия сменила класс данных, о чём Помель говорил в первой части серии.
Третий слой — качество тегов, продолжение сюжета из первой части серии. Новакович: в OTel обязателен ровно один тег, service.name. Если клиент не проставил pod name и host name, запрос «дай логи этого пода» невыполним в принципе. Частичное решение, которое он называет, — Kubernetes-оператор, проставляющий теги и автоинжектящий инструментацию. Появились и компании, занимающиеся исключительно качеством телеметрии.
Почему модели вообще понимают телеметрию
Наблюдение Новаковича, объясняющее много: модели работают с трейсами «из коробки», потому что видели спецификацию. Трейс — это текст с тегами по открыто задокументированной семантической конвенции. Модели натренированы на этой документации, поэтому знают, что host.name — имя хоста, а HTTP 404 — проблема. Работают с телеметрией примерно так же, как с кодом.
Больших публичных корпусов самих трейсов при этом почти нет — есть несколько репозиториев «донорских» логов и спанов. Оговорка обязательна: это главный аргумент OTel-native вендора, и он же обосновывает его претензию к конкурентам (90% вендоров конвертируют OTel во внутренний формат и теряют семантику — оценка спикера, без измерения).
Практическое следствие для тебя: чем ближе твоя телеметрия к стандартным семантическим конвенциям, тем меньше промпт-инжиниринга понадобится, чтобы агент её понял. Кастомные имена метрик — это налог, который ты платишь каждым запросом.
Один трейс, миллион трейсов и граница модели
Новакович проводит границу очень чётко:
- Уронить один проблемный трейс в модель — работает. Она распознаёт код ошибки Oracle, объясняет исчерпание connection pool, даёт контекст из документации. Делает работу человека.
- Найти аномалию в миллионе трейсов — нет. Объём.
Решение — дать модели инструмент. Функция triage сравнивает теги в ошибочных трейсах и находит общий признак: «у всех ошибочных один и тот же customer ID». Агенту она отдаётся как tool через MCP. Его формулировка: «You have to build your API essentially in a way that it works for the agent».
Это же наблюдение, к слову, работает против его собственного тезиса о невыживании платформ: если агенту нужен triage поверх миллиона трейсов, кто-то должен этот triage держать.
Стоимость и токены как метрика наблюдаемости
Token usage у Газита стоит в metrics наравне с latency и error rate — и это метрика поведения. Стив Йегги («You'll Never Write Code the Same Way Again») доводит мысль до предела: в software factory работа буквально производится тратой токенов — рой генерирует отчёты и тикеты, второй рой их чинит, третий ревьюит. Отсюда два следствия:
- работу надо отслеживать как отдельную сущность первого класса (у них для этого Beads — граф работ в git);
- «качество становится ручкой, на которой ты выбираешь, сколько токенов потратить».
Цифра, которую он приводит: 4 млн токенов в день — заявленный порог, на котором человек становится «AI-грамотным». Это пересказ чужой обучающей программы (Netflix: 5 часов, свой менеджер, своя реальная задача, когорта до 10 человек), собственного замера за ним нет. Его же ироничная поправка: «token maxing» — сначала людей учат тратить токены, потом приходится учить не тратить.
Экономика наблюдаемой фабрики у Йегги и Нокса (Tessl, вендор) устроена через маршрутизацию: задачи тегируются «уровнем интеллекта» и раскладываются по моделям — дорогая на планирование, дешёвая на исполнение, ревью выдерживает модель на класс ниже генерации. Важная деталь: под интерактивную сессию модель оптимизировать бессмысленно, потому что сложность заранее неизвестна. Повторяющийся автоматизированный рабочий процесс оптимизировать можно: известно, как часто он падает. Отдельно замечают, что GitHub Actions — не самый дешёвый и не самый подходящий рантайм для длинных агентных задач.
И формулировка, которая переопределяет предмет наблюдения: «Not a given PR, but a system that produces PRs». Ты мониторишь машину, которая изменения производит.
Нагрузочное тестирование в агентную эру
Со сцены: 1500 подов не влезли в контекст
- Кто и где: Андрей Райков, principal engineer, Delivery Hero. Доклад «AI Driven Load Test Analysis», AI Native DevCon. Не вендор — внутренний кейс.
- Что показали: внутренний reliability manifesto требует «design for failure» — сервис должен масштабироваться до 3x прод-нагрузки быстро и 4x на длинной дистанции, на реалистичных сценариях, в проде. Пять лет назад отчёт делали руками: 12 страниц Google Doc и 20–30 скриншотов из Datadog, Grafana и CloudWatch — на один прогон одного сервиса. Интеграция модели — буквально три строки кода. Вся сложность оказалась в промпте. Промпт разбит на три части: (1) определения и требования прямо из manifesto — мультипликатор, пороги error rate и latency, частота; (2) данные — прод (throughput, метаданные эндпоинтов, чтобы модель оценила реалистичность теста) и сам прогон (CPU, память, latency, error rate, throughput); (3) формат ответа — самая чувствительная часть, любое изменение слова меняет результат.
- Цифры: 10+ млн заказов в день, 70+ стран, 26 сервисов; раньше прогон раз в две недели, сейчас требование — еженедельно. В примере доклада 150 подов, в реальном прогоне — 1500, то есть сотни тысяч чисел.
- Что сломалось: при добавлении данных о CPU и памяти «всё сломалось» — модель уткнулась в контекстное окно и начала брать информацию произвольно. Починили структурированием промпта и урезанием данных: агрегаты «сколько CPU на под» вместо сырых точек. Structured output помогал не всегда — исходные данные каждый прогон разные, и модель интерпретирует их по-разному. Кроме того, «AI are talkative»: просишь одно число, получаешь абзацы вокруг него.
- Что это меняет: после починки промпта модель уверенно находит выбросы в потреблении CPU по подам и выдвинула гипотезу о проблеме с балансировщиком — часть подов получает больше нагрузки, чем должна. Райков подтверждает, что гипотеза требует проверки, но правдоподобна. Второй полезный эффект — модель явно сообщает, каких данных ей не хватило: «не могу сказать про CPU и память, вы их не дали». Агрегация до отправки — условие корректности, и лишь во вторую очередь оптимизация расходов.
Коротко
- OpenLLMetry — это OTel, растянутый на GenAI (Газит, Traceloop — вендор): monkey patching на стороне приложения, более 40 провайдеров, две строки подключения, экспорт меняется переменными окружения.
- Раскладка по столпам конкретна: logs — промпты, completions, вызовы функций; metrics — токены, latency, error rate; traces — весь многошаговый прогон. Для RAG обязателен retrieval со scores.
- Агентный трейс должен нести содержимое вместе с таймингами, иначе он не отвечает на вопрос «почему было принято это решение». Визуализации графа исполнения агента, по признанию самого Газита, ещё нет.
system_fingerprint— дешёвый способ отличить дрейф провайдера от своей регрессии. Внутрь надстроек провайдера видимости нет.- Один трейс модель разбирает, миллион — нет (Новакович, Dash0 — вендор). Нужен triage как tool через MCP: «build your API in a way that it works for the agent».
- Контекстное окно ограничивает корректность, и стоимость тут вторична. Кейс Delivery Hero: 1500 подов, модель начала выбирать данные произвольно. Лечится агрегацией данных. Увеличение окна не помогает.
Построим такой контур в вашей компании
coMind переводит команды и компании в AI-Native: первый контур за 30 дней, методика — в книгах серии «Путь компании к AI-Native».