- BrainTools - https://www.braintools.ru -

Почему AI-агент чинит симптом, а не баг: трейсы выполнения и бенчмарки на BugSwarm

Когда AI-агенту дают только stack trace и текст файла с упавшим тестом, он часто чинит симптом, а не причину. Тест зеленеет, баг переезжает в master, через неделю всплывает в другом месте. На простых багах это незаметно, на нетривиальных — становится правилом.

В этой статье — что меняется, если вместо stack trace дать агенту сжатый трейс выполнения, собранный на стороне IDE. Три бага из реального кода, эвристики сжатия трейса, склейка многопоточных вызовов по timestamp и цифры на датасете BugSwarm Pro для DeepSeek V3.2 и проприетарных LLM.

Почему AI-агент чинит симптом, а не баг: трейсы выполнения и бенчмарки на BugSwarm - 1

Статья пригодится, если вы:

  • Java/Kotlin-разработчик и хотите понять, почему AI-агент в IDE «не справляется» с реальными багами;

  • архитектор или тимлид и выбираете подход к интеграции AI в дебаг-процесс;

  • интересуетесь оценкой качества LLM на инженерных задачах и бенчмарками вроде BugSwarm.

Почему stack trace недостаточно

Stack trace отвечает на вопрос «где упало». Он не отвечает на вопрос «почему пришло именно к этому состоянию». На простых багах эти два вопроса совпадают. На нетривиальных — нет: реальная причина в другой ветке вызовов, которую stack trace не видит вообще.

LLM в такой ситуации уезжает в локальный контекст ошибки [1] и предлагает «локальный фикс»: поправить условие в той строке, рядом с которой упало. Тест становится зелёным. Баг — нет.

Альтернатива — давать агенту трейс выполнения теста: дерево вызовов с аргументами, возвращаемыми значениями и брошенными исключениями. Дальше — три кейса, где это меняет ответ агента.

Кейс 1. bsl-language-server: фикс не в том поле

Тест падал на ассерте: ожидалось true, получено false. Поле useDevSite приходило в false, хотя в JSON стояло true.

{
  "annotations": { ... },
  "useDevSite": true
}

Без трейса агент с Claude Opus 4.6 за полчаса сошёлся на симптоматическом фиксе: добавить отдельную десериализацию именно поля useDevSite, чтобы тест позеленел. Claude Code в тех же условиях тоже не справился.

С трейсом причина становится видна за пару минут. Дело было не в useDevSite, а в самописном десериализаторе соседнего поля annotations: он дважды читал из парсера и ломал его состояние. После этого падала десериализация всех последующих полей по порядку, в том числе useDevSite.

Без полной цепочки вызовов агента «утаскивает» в локальный контекст ошибки. Симптом — у поля useDevSite, причина — у соседа сверху. Stack trace эту связь не показывает.

Кейс 2. rill-flow: причина на глубине 51

Тест InvokeTest падал с DAGTraversalException из-за инвертированного логического условия в интерпретаторе конструкции ForEach. Падение и причина были в разных цепочках вызовов, реальный баг сидел на глубине 51.

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

  • Приоритезация исключений. Ветви, в которых выбрасываются и ловятся исключения, сохраняем целиком — там чаще всего живёт причина.

  • Сжатие циклов. Дерево не разрастается от однотипных итераций: оставляем шаблон + 1–2 репрезентативных прохода.

  • Репрезентативные вызовы. На большой глубине сохраняем уникальные вызовы, отсекаем повторяющиеся.

  • Компрессия идентификаторов. Случайные строки (UUID, хеши, токены) токенизируются примерно в 2,5 раза хуже обычного текста. Подмена их на короткие псевдонимы заметно экономит контекст и деньги.

Цель — не «уложиться в окно любой ценой», а сохранить ту часть дерева, в которой действительно живёт причина.

Кейс 3. Многопоточный баг: исключение в чужом потоке

Тест контроллера падал после обновления библиотеки. Метод exchange() инициировал вызов BaseLlmController.streamingChat() в другом потоке. На выходе — два независимых дерева вызовов и stack trace, в котором настоящего исключения нет вообще.

Чтобы не терять связь между потоками, мы склеиваем трейсы по временным меткам:

T+0  ms  Thread-main:  exchange()
T+3  ms  Thread-main:  → POST /chat
T+5  ms  Thread-llm-1: streamingChat()
T+12 ms  Thread-llm-1: ✗ IllegalArgumentException("model: gpt-5.4")
T+18 ms  Thread-main:  ← 500 Internal Server Error

Без склейки агент видит два дерева как параллельные миры. Со склейкой — что 500-я в основном потоке прилетела в ответ на исключение из llm-потока.

Причина оказалась хрестоматийной: после обновления библиотека стала требовать имя модели GPT-5.4 строго в верхнем регистре. Исключение прилетало в чужом потоке и в основной stack trace не попадало.

Бенчмарки на BugSwarm Pro

Прогоняли агента на датасете BugSwarm Pro: 43 нетривиальных бага в реальных Java-приложениях, в которых stack trace не указывает на файл с настоящим фиксом. Это сознательный отбор: лёгкие баги в этот датасет не попадают.

Сравнивали два режима — без трейса (только stack trace + контекст файла) и с трейсом — на трёх моделях:

Метрика

Что измеряем

Зелёный тест

Тест прошёл после фикса агента

Попадание в файл

Файл с фиксом совпал с файлом эталонного фикса

Семантическое соответствие

LLM-as-a-judge сравнивает фикс агента с эталонным

Что показали данные:

  • На всех трёх метриках и всех трёх моделях (включая открытую DeepSeek V3.2) добавление трейса улучшает результат.

  • В попадании фиксом в правильный файл DeepSeek V3.2 с трейсом выходит на уровень проприетарных моделей без трейса.

  • Если запускать каждый эксперимент трижды и считать «победы», подход с трейсом надёжнее базового в разы — на багах, где результат вообще зависит от подхода, а не воспроизводится на любой конфигурации.

Цифра про DeepSeek важна на практике: в банках, телекоме, госсекторе и КИИ часто запрещено отправлять код во внешние LLM, и open-source модель внутри периметра — единственный реалистичный вариант. Трейс выравнивает её с закрытыми моделями там, где это критично.

Где ещё пригождается трейс

Трейсы выполнения нужны не только для багфикса. Из практики:

  • Ответ на вопрос «почему так». Диаграмма последовательностей по зелёному тесту иногда ловит баги, которых тест не видит. В одном из наших тестов диаграмма показала, что в перекладывании DTO теряется поле reasoningContent. Тест зелёный, баг есть.

  • Ревью PR через сравнение трейсов. Один и тот же тест до и после изменений — видно, что реально поменялось в поведении [2], а не только в diff’е.

  • Актуализация документации на основе того, как код работает сейчас, а не как он работал во времена, когда документация писалась.

Что унести с собой

  • Stack trace отвечает на вопрос «где упало», трейс выполнения — на вопрос «почему пришло к этому состоянию». На нетривиальных багах разница принципиальная.

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

  • В многопоточных сценариях трейсы потоков надо склеивать по timestamp, иначе агент видит два независимых дерева и причинно-следственную связь не строит.

  • Open-source модели с трейсом догоняют проприетарные без трейса. Для on-prem-сценариев это меняет экономику.

Кейсы и бенчмарки выше — основа нашего доклада на JPoint 2026. 

Автор: dirvika

Источник [3]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/29996

URLs in this post:

[1] ошибки: http://www.braintools.ru/article/4192

[2] поведении: http://www.braintools.ru/article/9372

[3] Источник: https://habr.com/ru/companies/veai/articles/1033008/?utm_campaign=1033008&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100