Исследователи из George Washington University показали, что связка "внешний детектор → LLM → проверка" поднимает точность исправления кода с 74% до 88%. Механика: статический анализатор находит проблему → LLM исправляет → анализатор проверяет исправление → если проблема осталась, цикл повторяется с уточнённым контекстом. Вместо blind single-pass генерации — контролируемые итерации с объективной проверкой каждого шага.
LLM часто "чинит" код так, что синтаксически всё верно, но проблема не решена. Модель может переписать уязвимый участок, но упустить саму суть бага — просто потому что у неё нет механизма проверки. В итоге код выглядит "исправленным", но реальная проблема никуда не делась. Статический анализ или тесты ловят такие промахи мгновенно — они работают по формальным правилам, не генерируют текст.
Решение: LLM исправляет, внешний инструмент проверяет, результат проверки идёт обратно в LLM. Цикл повторяется до полного устранения проблемы или достижения лимита итераций (обычно 3-4). Каждая итерация уточняет контекст: "твоё прошлое исправление не сработало, вот что показал анализатор". Модель учится на своих ошибках внутри одной сессии.
Схема метода
ШАГ 1: Внешний инструмент (linter/тесты/компилятор) находит проблему
→ отчёт с конкретным местом и типом ошибки
ШАГ 2: LLM получает код + отчёт → генерирует исправление + объяснение
→ возвращает патч
ШАГ 3: Применить патч → запустить проверку снова
→ если проблема осталась, вернуться к Шагу 2 с новым контекстом
Цикл повторяется в одной сессии до устранения проблемы или лимита итераций
Пример применения
⚠️ Ограничение метода: Работает только если есть автоматический верификатор (линтер, тесты, компилятор). Для субъективных задач (креатив, стиль текста) не подходит. Хорош для кода, структурированных данных, формальных правил.
Задача: Скрипт для обработки CSV с клиентами из 1С падает на русских символах. Файл экспортировался в windows-1251, код читает как UTF-8 — всё ломается на первой строке с "ООО Рассвет".
Итерация 1:
# Запустили код → получили ошибку:
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf0
```
**Промпт:**
```
Вот код для чтения CSV:
import pandas as pd
df = pd.read_csv('clients.csv')
Получаю ошибку: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf0
Исправь код. Объясни что изменил и почему.
Итерация 2:
# LLM предложил encoding='latin1' → код работает, но русские символы битые
# Запустили → увидели "ООО ÐаÑÑвеÑ"
```
**Промпт:**
```
Твоё исправление с encoding='latin1' запустилось, но русские символы битые:
"ООО ÐаÑÑвеÑ" вместо "ООО Рассвет"
Исходный файл экспортирован из 1С. Исправь снова. Объясни почему прошлое решение не сработало.
```
**Результат:**
LLM покажет 2-3 раунда исправлений, каждый раз объясняя почему прошлый вариант не сработал. Финальный код будет с правильной кодировкой (`windows-1251`), с обработкой ошибок на случай битых данных. Модель объяснит разницу между UTF-8, latin1 и windows-1251 в контексте российских систем 1С.
---
### Почему это работает
**LLM генерирует решения fast, но не умеет проверять их объективно**. Модель может написать код, который выглядит правильным и даже логически связным — но реально не запустится или не решит проблему. У неё нет компилятора в голове, нет runtime для проверки. Она оперирует паттернами из обучающих данных, не фактами о конкретном окружении.
**Внешние инструменты (линтеры, тесты, компиляторы) дают объективную правду**. Они не додумывают — либо код работает, либо нет. Но они не умеют исправлять — только указывают на ошибки. Это идеальное разделение труда: LLM генерирует варианты, инструмент фильтрует неправильные.
**Итеративный цикл превращает угадывание в направленный поиск**. Первая попытка LLM часто промахивается — модель не знает специфику окружения (версия библиотеки, кодировка файла, особенности системы). Но каждый раунд проверки даёт ей **конкретный feedback** — не абстрактное "не работает", а точный trace ошибки. Модель сужает пространство возможных решений, отсекая уже проверенные варианты.
**Рычаги управления промптом:**
- **Тип верификатора** — линтер (стиль кода), тесты (логика), компилятор (синтаксис) → выбирай под задачу
- **Лимит итераций** — 1-2 для простых багов, 4-5 для сложных → экономия токенов vs thorough
- **Уровень объяснений** — "без комментариев" vs "подробно объясни" → speed vs понимание
- **Scope исправления** — "исправь только эту функцию" vs "пересмотри весь модуль" → точечно vs радикально
---
### Шаблон промпта
```
Задача: {описание проблемы}
Исходный код:
{код}
Ошибка при проверке:
{вывод линтера/тестов/компилятора}
Исправь код. Требования:
1. Минимальные изменения — только то, что нужно для устранения ошибки
2. Объясни ЧТО изменил и ПОЧЕМУ это решает проблему
3. Верни исправленный код отдельным блоком для копирования
---
[После первой попытки, если проблема осталась:]
Твоё исправление дало новую ошибку:
{новый вывод проверки}
Проблема в строке {номер строки}: {контекст ошибки}
Исправь снова. Объясни почему прошлое решение не сработало.
Что подставлять:
{описание проблемы}— что не работает, какой результат ожидается{код}— полный код или проблемный участок{вывод линтера/тестов/компилятора}— дословный вывод инструмента проверки{номер строки}и{контекст ошибки}— конкретика для второй итерации
Ограничения
⚠️ Нужен автоматический верификатор: Метод работает только если есть инструмент, который объективно скажет "исправлено" или "нет". Без линтера/тестов/компилятора — просто многораундовый диалог с субъективной оценкой.
⚠️ Не для распределённой логики: Если баг в архитектуре (взаимодействие модулей, race conditions, конфигурация окружения) — точечные исправления не помогут. LLM видит локальный контекст, не всю систему.
⚠️ Лимит итераций ≠ решение: Сложные баги могут требовать 5-7 раундов или принципиального переписывания. После 3-4 итераций без прогресса — переформулируй задачу или разбей на части.
Как исследовали
Команда из George Washington University взяла 740 синтетических сэмплов Python кода с внедрёнными уязвимостями + 1428 реальных примеров из CVE-баз (публичные базы известных уязвимостей). Проверяли на шести LLM — от 1.3B до 7B параметров — в трёх режимах:
- Bandit в одиночку — статический анализатор находит проблемы, но не исправляет (0% fix accuracy, baseline)
- LLM в одиночку — модель генерирует исправление в один проход без проверки (74% точности, 13.5% ложных правок)
- SecureFixAgent — цикл detect→repair→validate с Bandit как верификатором (88% точности, 8% ложных правок)
Почему такая разница? LLM-only часто "исправляет" код косметически — меняет названия переменных, переписывает структуру, но не устраняет саму уязвимость. Например, вместо eval(user_input) пишет exec(sanitized_input) — синтаксически ОК, но проблема injection никуда не делась. Bandit на второй итерации ловит это — "уязвимость всё ещё есть, метод изменился но суть та же". LLM получает чёткий feedback: "твой sanitize не работает, вот trace". Третья итерация — правильное решение с ast.literal_eval().
Что удивило: Fine-tuning на специфичных данных (пары уязвимость→исправление) дал всего +5-8% точности. Основной прирост (+13.5%) пришёл именно от итеративной валидации, не от адаптации весов модели. Это значит workflow важнее натренированности — правильная организация процесса бьёт дотюнинг.
Человеческая оценка: 15 разработчиков (студенты CS, junior devs, senior консультанты) оценивали качество объяснений по шкале 1-5. LLM-only набрал 2.9/5 ("непонятно почему так исправил"), SecureFixAgent — 4.5/5 ("чётко видно логику, почему первая попытка не сработала и что именно изменилось"). Прозрачность процесса повышает доверие — люди видят не магический output, а reasoning.
