TL;DR
Hybrid LLM-Deterministic Pipelines (Automated Uplift) — это архитектура конвейера для массового обновления кода и конфигураций, в которой детерминированные скрипты отвечают за классификацию задач, сбор контекста и проверку результата, а LLM зажата в рамки одного чётко очерченного шага трансформации. Вместо того чтобы пускать агента самостоятельно бродить по репозиторию и угадывать окружение, система заранее собирает зависимости из кода и логов реального выполнения (runtime telemetry), подставляет их в жёсткий шаблон промпта и проверяет результат автотестами до создания пул-реквеста.
Когда обычного кодинг-агента просят мигрировать крупный проект или обновить систему сборки, он быстро тонет в деталях: путает тестовые библиотеки с боевыми, не знает, какая версия интерпретатора реально крутится на сервере, и при каждом запуске классифицирует проект по-разному. Ещё хуже то, что сгенерированный код может выглядеть правдоподобно, но падать только при выкатке на прод — из-за этого разработчики команд боятся чужих автоматических пул-реквестов и просто игнорируют их.
Метод решает обе проблемы за 5 последовательных этапов: детерминированный сбор контекста и сверка импортов с логами продакшена, сборка промпта по шаблону архетипа, генерация изменений через LLM (либо извлечение JSON-параметров для кодогенератора), жёсткая валидация правилами и тестами с автоматическим возвратом ошибок модели (до 3 попыток) и отдельная read-only сессия для честного описания пул-реквеста со всеми оставшимися предупреждениями.
Схема метода
ШАГ 1 (Детерминированный): Классификация репозитория по реестру + сбор AST-импортов и логов продакшена → [Структурированный контекст]
ШАГ 2 (Детерминированный): Подстановка контекста в шаблон конкретного архетипа → [Готовый промпт]
ШАГ 3 (LLM — запись/извлечение): Правка файлов в песочнице ИЛИ извлечение параметров в JSON для шаблонизатора → [Кандидат изменений]
ШАГ 4 (Детерминированный цикл до 3 раз): Проверка правилами прошлых ошибок, сборкой и тестами → при сбое возврат лога ошибки на Шаг 3
ШАГ 5 (LLM — только чтение): Анализ логов миграции и оставшихся предупреждений → [Описание пул-реквеста для ревьюера]
Шаги выполняются в конвейере из отдельных вызовов: Шаг 3 (генерация с ретраями после проверок Шага 4) и Шаг 5 (описание PR в режиме чтения) — это разные обращения к модели.
Пример применения
Задача: В финтех-компании (например, в духе Точки или Т-Банка) 150 микросервисов на Python нужно перевести со старого самописного сборщика конфигураций legacy_deploy.yaml и разрозненных requirements.txt на единый стандарт pyproject.toml (Poetry/uv) без поломки воркеров в продакшене.
Промпт:
Ты — инженер миграции инфраструктуры. Твоя задача — перевести конфигурацию сборки микросервиса со старого формата на единый стандарт на основе предсобранного контекста.
Не меняй бизнес-логику приложения.
Односценарный фоновый сервис (worker) со старой конфигурацией деплоя без изолированных метаданных пакета.
1. Точка входа из логов продакшена: `src/payout_worker.py` (Python 3.11)
2. Подтверждённые боевые зависимости (найдены и в AST кода, и в трассировках запуска на кластере — зафиксируй эти версии):
- `pydantic==2.6.4`
- `aiohttp==3.9.3`
- `PyYAML==6.0.1` (в коде импортируется как `yaml`, каноническое имя пакета — `PyYAML`)
- `Pillow==10.2.0` (в коде импортируется как `PIL`, каноническое имя пакета — `Pillow`)
3. Зависимости только из исходного кода (не вызывались в продакшене — перенеси в группу `[tool.poetry.group.dev.dependencies]` без жёсткого пина версии):
- `pytest`, `faker`, `locust`
4. Транзитивные модули только из рантайма (не добавляй в прямые зависимости):
- `idna`, `multidict`, `yarl`
Вместо прямого написания файлов сборки извлеки параметры миграции строго в JSON по схеме ниже, чтобы детерминированный шаблонизатор сгенерировал `pyproject.toml` и обновил `Dockerfile`.
Проверь перед выводом, что не нарушены правила из прошлых сбоев:
1. Ни один импорт из группы 4 (транзитивные) не попал в список прямых зависимостей.
2. Для модулей `yaml` и `PIL` указаны канонические имена пакетов (`PyYAML`, `Pillow`), а не имена импортов.
3. Точка входа `[tool.poetry.scripts]` указывает ровно на `src/payout_worker.py`.
4. Старый файл `legacy_deploy.yaml` помечен к удалению, а не оставлен рядом с новым конфигом.
Верни только валидный JSON с полями:
- `package_name`: строка
- `python_version`: строка
- `entrypoint`: строка
- `prod_dependencies`: словарь `{каноническое_имя: точная_версия}`
- `dev_dependencies`: список строк
- `files_to_remove`: список путей
- `warnings`: список нестандартных мест в конфиге, требующих внимания владельца репозитория
Результат:
Модель вернёт строго типизированный JSON-объект, где боевые зависимости (PyYAML, Pillow, pydantic, aiohttp) отделены от тестовых (pytest, faker, locust) и очищены от транзитивного мусора, имена модулей приведены к каноническим именам пакетов, а старый конфиг добавлен в список на удаление. На следующем шаге шаблонизатор соберёт из этого JSON безошибочный pyproject.toml, а список warnings пойдёт в честное описание пул-реквеста для команды-владельца.
Почему это работает
Когда LLM дают полную свободу в задаче миграции репозитория, она сталкивается с двумя слепыми зонами. Во-первых, в самом исходном коде нет данных о реальной среде исполнения: по файлам проекта невозможно понять, какой скрипт реально запускается на бою, а какой мёртвым грузом лежит пять лет, или почему условный import yaml требует пакет PyYAML конкретной версии с боевого кластера. Во-вторых, когда модель сама и классифицирует проект, и пишет конфиг с нуля, и проверяет себя, мелкие галлюцинации накапливаются и прорываются в пул-реквест.
Сильная сторона современных LLM — трансформация и извлечение данных в жёстких границах, когда весь недостающий контекст уже собран и разложен по полочкам. Если подать модели готовое пересечение статического анализа (AST) и логов продакшена, ей не нужно гадать: то, что есть и в коде, и в логах запуска — это боевая зависимость с точной версией; то, что только в коде — тестовая или условная ветка.
Главный архитектурный секрет метода — мелкий цикл обратной связи (shallow feedback loop) и правило: «расширяй роль LLM в генерации изменений, но никогда — в их проверке и классификации». Если формат целевого файла регулярен (как в CMake или стандартном конфиге), модель вообще не должна писать код напрямую: она только извлекает структуру в JSON, а код пишет детерминированный шаблон. Валидация (сборка, юнит-тесты, проверка --help и чек-лист прошлых ошибок) выполняется скриптами до открытия PR, поэтому ревьюеру приходит уже скомпилированный и проверенный результат.
Рычаги управления: - Роль LLM (прямая правка vs извлечение JSON под шаблон) → для разнородных репозиториев давай модели править файлы, а для однотипных конфигов заставь её возвращать JSON под шаблон (это исключает синтаксические баги и утроило долю принятых PR в исследовании). - Число итераций самоисправления (до 3 повторов на Шаге 4) → если сборка или правило проверки упали, возвращай модели лог ошибки как дополнительный контекст; больше 3 раз повторять бессмысленно — проще пометить кейс для человека. - Порог кастомного шаблона архетипа → создавай отдельные детальные шаблоны промптов только для массовых категорий (от 20 однотипных задач), а для редких исключений используй общий шаблон с пометкой «черновой PR для старта». - Разделение записи и описания (Шаг 3 vs Шаг 5) → генерируй описание PR отдельным вызовом в режиме «только чтение», чтобы агент честно выписал оставшиеся предупреждения, а не пытался скрыть недоделки.
Шаблон промпта
Ниже — универсальный двухфазный шаблон для миграции или рефакторинга (Фаза А — трансформация/извлечение с самопроверкой по правилам прошлых сбоев, Фаза Б — честное read-only ревью для описания PR).
Архетип задачи: {архетип_проекта}
Боевое окружение и точки входа (из логов/конфигов): {данные_рантайма}
Подтверждённые зависимости (код ∩ рантайм): {боевые_зависимости}
Условные/тестовые зависимости (только код): {тестовые_зависимости}
Твоя задача: выполнить миграцию {исходный_формат} → {целевой_формат} строго в рамках переданного контекста.
Режим вывода: {режим_вывода: "JSON по схеме для шаблонизатора" или "Прямая правка файлов"}
Схема / требования к результату:
{схема_или_спецификация}
Проверь свой результат по правилам частых сбоев (если есть ошибки с прошлой попытки — исправь их: {лог_ошибок_валидации}):
1. Не оставлены ли старые конфигурационные файлы рядом с новыми.
2. Совпадают ли имена импортов в коде с каноническими именами пакетов в реестре.
3. Не попали ли условные или транзитивные зависимости в жёсткие боевые требования.
4. {своё_правило_проверки}
После формирования изменений переключись в режим Read-Only ревьюера и составь описание Pull Request для команды-владельца:
- Что именно было изменено и на каких данных основан выбор версий.
- Какие автоматические проверки пройдены.
- Список предупреждений (warnings): какие места требуют ручной проверки перед деплоем.
Что подставлять в плейсхолдеры:
- {архетип_проекта} — тип репозитория из вашей классификации (например, «CLI-утилита без метаданных пакета» или «Сервис со старым Makefile»).
- {данные_рантайма} — какая версия языка, какие точки входа и флаги реально запускаются на сервере.
- {боевые_зависимости} и {тестовые_зависимости} — результат сверки импортов в коде с реальными логами работы.
- {исходный_формат} и {целевой_формат} — откуда и куда переезжаем (например, Webpack → Vite или pip → uv).
- {лог_ошибок_валидации} — вывод компилятора, линтера или сравнения --help, если первая попытка упала (при первом запуске оставь пустым).
🚀 Быстрый старт — вставь в чат:
Вот шаблон Hybrid LLM-Deterministic Pipeline. Адаптируй под мою задачу: [опиши, какой код или конфиги нужно мигрировать].
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит, из какого формата в какой вы переезжаете, какие данные об окружении у вас есть (логи, конфиги деплоя, список пакетов), можно ли задать выход через JSON-шаблон и на каких типичных ошибках обычно спотыкается сборка — потому что без разделения детерминированных проверок и границ генерации модель начнёт додумывать зависимости из головы. Она возьмёт паттерн из шаблона и адаптирует под задачу.
Ограничения
⚠️ Узкое место — ревью людьми, а не генерация: Даже когда конвейер штампует сотни качественных пул-реквестов, большинство из них зависают без ответа от команд-владельцев, если миграция низкоприоритетная. Без автоматической компиляции и прогона тестов внутри самого пайплайна разработчики боятся брать на себя риск выкатки и откладывают PR в долгий ящик.
⚠️ Глубокий цикл обратной связи бьёт по принятию: В задачах, где успех миграции можно проверить только при реальном деплое приложения (например, пересборка пакетов со сложными динамическими зависимостями), доля влитых PR оказывается втрое ниже, чем там, где сборка и юнит-тесты полностью проходят до открытия PR.
⚠️ Нерентабельность для мелких уникальных групп: Разрабатывать отдельные шаблоны промптов, сборщики контекста и правила валидации имеет смысл только для типовых архетипов (от двух десятков репозиториев и больше). На штучных нестандартных репозиториях конвейер даёт лишь черновую заготовку.
Как исследовали
Команда инженеров Bloomberg и исследователей из University College London полгода проводила масштабный промышленный эксперимент Automated Uplift, охвативший около 8 600 репозиториев-кандидатов и сгенерировавший 2 333 реальных пул-реквеста в двух кампаниях: перевод разнородных Python-пакетов на изолированную систему упаковки (PyPackaging, 294 PR) и перевод однотипных C++-проектов со старого сборщика на CMake (CppBuild-to-CMake, 2 039 PR). В работе использовали Claude Sonnet 4.5 и Claude Opus 4.6 (в среднем агент делал 13 шагов за 48 секунд на C++ и 63 шага за 7.2 минуты на Python), а сам инструмент для Python состоял из 4 300 строк детерминированного кода против всего 230 строк интерфейса к LLM и 3 600 строк шаблонов и документации.
Самое поразительное открытие обнаружилось при подсчёте итогов через два месяца после заморозки генерации: всего было успешно влито 729 миграций (что сэкономило компании более 1 000 человеко-дней рутинного труда), но в CMake-кампании приняли 34.2% PR при всего 8.1% отказов, тогда как в Python-кампании влили лишь 10.5% PR при 21.8% отказов, а около 60–68% PR вообще остались без решения команд! Разгадка оказалась в глубине цикла проверки: в C++ LLM только извлекала JSON из Makefile, шаблон собирал CMakeLists.txt, а пайплайн тут же компилировал проект и гонял юнит-тесты — ревьюеру оставалось лишь нажать кнопку на заведомо рабочем коде. При этом даже «отклонённые» PR в Python принесли скрытую пользу: в 34 из 51 репозитория, где разработчики закрыли PR бота (часто просто потому, что не имели прав пушить в ветку бота), они следом сами завершили миграцию, а в 14 случаях напрямую скопировали изменения агента.
Адаптации и экстраполяции
🔧 Техника: Схема «LLM извлекает JSON → Шаблон пишет код» → Нулевой риск синтаксических галлюцинаций
Если вам нужно массово переписать конфиги (
Dockerfile,GitHub Actions,nginx.conf,pyproject.toml), запретите LLM генерировать сам файл. Попросите её вернуть строго валидируемый JSON с параметрами из старого конфига, а целевой файл соберите обычным Jinja2-шаблоном. Любая ошибка шаблона чинится один раз на весь флот проектов, а модель не сможет случайно сломать отступы или придумать несуществующую директиву.
Экстраполяция: Аудит мёртвых зависимостей и фич-флагов через матрицу «Код × Логи»
Принцип перекрёстной сверки статики и рантайма из исследования отлично переносится на очистку легаси-кода, настройку доступов или ревизию инструментов агента:
Сверь два списка для нашего проекта:
1. Статический список (все объявленные переменные окружения / зависимости / фич-флаги в коде): {список_из_кода}
2. Фактический след из логов продакшена за 30 дней: {список_из_логов}
Раздели их на 3 корзины:
- [Код ∩ Логи] → Боевое ядро: зафиксировать и покрыть строгой проверкой.
- [Только Код] → Спящие/тестовые/мёртвые элементы: пометить кандидатами на удаление или вынос в dev-контур.
- [Только Логи] → Неявные транзитивные зависимости: задокументировать, чтобы не сломать при обновлении окружения.
Ресурсы
Исследование: Hybrid LLM–Deterministic Pipelines for Enterprise-Scale Code Migration at Bloomberg (arXiv:2611.05457v1, ноябрь 2026)
Авторы: Mario Corchero, László Kiss Kollár, Haowen Liu, Shivam Bairoliya, Gala Malbasic, Serkan Kirbas (Bloomberg), David Williams, Federica Sarro (University College London)
