Почему AI-агент чинит симптом, а не баг: трейсы выполнения и бенчмарки на BugSwarm. Java.. Java. JavaScript.. Java. JavaScript. Kotlin.. Java. JavaScript. Kotlin. kotlin native.. Java. JavaScript. Kotlin. kotlin native. аналитика.. Java. JavaScript. Kotlin. kotlin native. аналитика. аналитика данных.. Java. JavaScript. Kotlin. kotlin native. аналитика. аналитика данных. аналитика проекта.. Java. JavaScript. Kotlin. kotlin native. аналитика. аналитика данных. аналитика проекта. Программирование.

Когда 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. 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 через сравнение трейсов. Один и тот же тест до и после изменений — видно, что реально поменялось в поведении, а не только в diff’е.

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

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

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

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

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

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

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

Автор: dirvika

Источник