Привет, Хабр! Многие из вас, возможно, помнят мои предыдущие посты про формат файлов .ap — попытку создать AI-friendly формат для применения изменений в коде. В комментариях к каждой статье неизбежно возникает один и тот же вопрос: «А зачем всё это, если есть плагины для IDE вроде Copilot Chat?». Вопрос абсолютно справедливый, и сегодня я хочу дать на него развёрнутый, технический ответ. Потому что эти два подхода решают задачи совершенно разного масштаба.
Для начала, кратко напомню, что такое .ap. Это декларативный, человекочитаемый формат патчей, спроектированный специально для генерации нейросетями. Вместо хрупких и сложных для генерации LLM номеров строк, как в diff (модель ведь «мыслит» не строками, а токенами), он использует семантические «якоря» и уникальные фрагменты кода для поиска места изменения. Модель генерирует простой текстовый файл с командами вроде REPLACE или INSERT_AFTER, а специальная утилита-патчер применяет эти изменения к вашим файлам. А теперь — к самому интересному.
Давайте сравним этот подход с тем, как устроены AI-редакторы и плагины к ним.
Как на самом деле работают ИИ-плагины в IDE?
Когда вы выделяете функцию в своём редакторе и пишете в чат «добавь сюда логирование», в игру вступает очень продуманная инженерная система, выступающая в роли «переводчика» между миром вашего редактора и текстовым миром LLM.
Внешне всё выглядит так же, как в уже ставшим всем привычным веб-диалоге с нейронкой: у вас есть чат или подобие чата, куда вы пишете запросы и получаете ответы. Но работает это совсем не так, как мы привыкли по онлайн версиям. В редакторе у вас на самом деле два типа диалогов!
Тот, который вы видите. Это полная история вашей переписки с моделью. Она выглядит как один длинный диалог, но на самом деле склеена из небольших кусочков только для того, чтобы и вы и модель хотя бы примерно понимали контекст работы.
Одноразовые микро-диалоги. То, что на самом деле происходит под капотом. Для каждого вашего запроса IDE создаёт отдельный диалог с моделью, прикладывая туда всё, что считает важным в контексте, например, необходимые части исходников. Как только обработка одного запроса завершается, всё это забывается, а в видимый диалог падает только ответ модели (и, возможно, её предложение редактору выполнить те или иные действия, закодированное обычно в JSON формате).
Разберем этот процесс чуточку подробнее. Когда вы отправляете запрос, плагин не отправляет модели всю вашу историю переписки и все исходники проекта. Он действует хитрее:
Делает «снимок» контекста. Плагин смотрит, что вы выделили, в каком файле находитесь, где стоит курсор, какие файлы вы просили приложить к диалогу. Он собирает этот локальный контекст в один большой, но одноразовый промпт.
Отправляет этот микро-промпт модели. Модель получает только этот снимок. Она не видит остальные файлы вашего проекта. Она не помнит, что вы делали пять минут назад, какие задачи решали, с какими проблемами по пути столкнулись и как обошли их. Плагин лишь кратко пересказывает ей пару последних сообщений из видимого диалога.
Получает ответ. Модель не генерирует
diffили инструкции. Она просто переписывает предоставленный ей фрагмент кода целиком, внося запрошенные правки.Плагин вычисляет разницу. Он берёт исходный код из «снимка до» и новый код, который прислала модель («снимок после»), и вычисляет между ними разницу. Этот
diffон и показывает вам в красивом виде.
По сути, видимый вам «общий диалог» — лишь иллюзия. В реальности это серия изолированных, stateless-запросов. Это невероятно эффективный в своей области подход, но именно в нём кроется и фундаментальное ограничение ИИ-редакторов.
Масштаб задачи: микро-инструмент против макро-инструмента
Взглянем на реальную задачу, с которой я столкнулся. Мне нужно было исправить ошибки в чужом, запутанном коде на малознакомом мне языке. Чтобы понять, что там вообще происходит, мне нужно было проследить всю цепочку вызовов, связанную с определённым поведением, и собрать отладочные данные по всей этой цепочке. То есть добавить подробный отладочный вывод в 20 разных местах в 5 разных файлах.
Как эту задачу решает плагин в IDE? Никак. Он физически не способен сделать это за один приём. Его парадигма — работа с одним «снимком» одной части кода. Я должен был бы:
Вручную найти первый файл
Выделить первую функцию
Попросить AI добавить логирование
Применить
Вручную найти второй файл... и так 20 раз
Весь контекст, всю карту вызовов я должен держать в своей голове. AI здесь выступает лишь как более умный автодополнитель.
Но есть и другая, более тонкая проблема ИИ-редакторов: отсутствие памяти о неудачах. Представьте, что на одном из 20 шагов модель предложила некорректный код. Вы его отклонили и попросили переделать. Но в рамках нового stateless-запроса модель не видит свою предыдущую ошибку и ваш фидбэк в полном объёме. Она не может проанализировать, почему её решение было неверным, и просто делает ещё одну попытку вслепую. В подходе с полным контекстом и ap вся история, включая неудачные попытки, остаётся в диалоге, позволяя модели сделать выводы и с каждой итерацией предлагать всё более качественное решение, обучаясь на своих ошибках прямо в процессе решения.
И наконец, сам интерфейс. UI плагинов идеально заточен под локальные правки. Показать diff для пяти изменённых строк — отлично. Но как в таком интерфейсе отревьюить и отладить патч, который вносит массивные изменения в 10 файлов, добавляя сложную, взаимосвязанную логику? Это просто невозможно. Окно чата и всплывающие diff-ы не предназначены для анализа глобального рефакторинга. Файл .ap — это текстовый артефакт. Я могу открыть его как любой другой исходник, внимательно прочитать, найти логические несостыковки, вручную подправить сам патч перед применением, вырезать или скопировать любые его части и применить по отдельности. Это, по сути, готовый коммит от ИИ, который я могу изучить и провалидировать с помощью привычных инструментов, а не пытаться отладить в тесном окошке редактора.
А теперь вернёмся к нашей задаче по отладке. Как можно её решить с помощью ap? Мы просто даем модели полный исходный код всех релевантных файлов и пишем: «Проанализируй этот код. Найди всю цепочку вызовов, связанную с механизмом X. Сгенерируй .ap патч (вот тебе спека), который добавляет отладочный вывод в начало и конец каждой функции в этой цепочке, выводя все важные переменные». Через минуту я получаю один-единственный патч-файл, который атомарно и без ошибок вносит правки во все 20 мест за одну операцию. Модель сама выполнила всю аналитическую работу, потому что у неё был полный контекст и возможность вносить логически связанные изменения во множество мест кода за одну операцию.
Именно поэтому плагинами у меня не получается решать задачи того уровня сложности, что я решаю полным контекстом и ap. Плагины — это микро-инструменты для локальных правок. ap — это макро-инструмент для глобального рефакторинга и доработки целых подсистем за одну итерацию.
Будущее за синтезом?
Означает ли это, что плагины плохи? Вовсе нет! Они идеальны для своих задач: быстро поправить неправильно работающий цикл или написать юнит-тест какой-нибудь функции. Их возможность почти мгновенного применения мелких локальных правок — огромный плюс. Вероятно, когда-нибудь они научатся выполнять сложные аналитические задачи и выдавать большие пакеты сложных правок сразу. Это переход от AI-ассистента, который помогает вам с кодом, к AI-инженеру, который поставляет готовые, документированные и легко применяемые решения.
А пока в редакторах такого нет, можно использовать лучшее из обоих миров, например, используя ap чтобы таскать туда-сюда правки кода между локальным редактором и несколькими веб-чатами с разными моделями. И это будет работать куда устойчивее .diff'ов, при генерации которых модели регулярно сбиваются.
Кстати, свежая версия спецификации ap 3.0 стала ещё надёжнее и проще для генерации (после продолжительных попыток убедить модели следить за индентацией я отказался от YAML в пользу более простого, специализированного и изящного формата). Но об этом, пожалуй, в следующий раз — оно того стоит!
Надеюсь, мне удалось прояснить, почему эти два подхода не столько конкурируют, сколько существуют в разных весовых категориях и решают разные классы задач. А какой подход подходит под ваши задачи лучше, вы сможете понять, только попробовав оба!
s_yu_skorobogatov
О, вы отказались от YAML: вижу, что формат развивается в правильном направлении! :-)
Я бы ещё предложил убрать
include_leading_blank_lines,include_trailing_blank_linesи уникальные коды, которыми помечены управляющие строки. Это можно сделать, помечая все строки, содержащие код, каким-нибудь символом (например,>):unxed Автор
Сначала так и хотел сделать. Ломается, если сам код будет содержать в начале строки этот же символ. Мысленный эксперимент: попробуйте применить ap-патч к ap-патчу.
Ещё сделаю отдельный пост про версию 3.0, и почему были выбраны именно такие решения, а не какие-нибудь другие.
s_yu_skorobogatov
Мне кажется, что если применить ap-патч к ap-патчу, то часть строк будет начинаться с двух символов
>. Т.е. формально всё будет правильно.