Введение в разработку с ИИ

Теория: Как устроены coding agents

Когда агент помогает писать код, легко представить, что внутри него есть некое цельное понимание проекта и почти человеческая логика работы. На практике все устроено прозаичнее и поэтому полезнее для инженера. Агент - это оболочка вокруг языковой модели, которой дали контекст, скрытые инструкции и набор инструментов для работы с внешней средой.

Если понимать эту механику, становится проще объяснить типичное поведение агента. Почему он постоянно что-то ищет по репозиторию. Почему может уверенно предложить неверный путь, если не увидел важный файл. Почему длинные сессии дорожают и со временем начинают работать хуже.

Языковая модель как движок

Внутри coding agent почти всегда находится LLM - большая языковая модель. В самом базовом виде она умеет продолжать текст: получает последовательность токенов на вход и предсказывает, какие токены должны идти дальше. Для пользователя это выглядит как ответ на вопрос, объяснение кода, предложение исправления или готовый фрагмент функции.

Например, если дать модели начало фразы:

функция на JavaScript для чтения JSON-файла может начинаться так: const data =

то модель попробует продолжить ее наиболее вероятным образом. В простом случае это будет что-то вроде:

JSON.parse(fs.readFileSync(path, 'utf8'))

Конечно, в реальной работе агент решает задачи посложнее, но принцип в основе тот же самый: модель продолжает уже начатую последовательность, только эта последовательность может включать и код, и объяснение, и историю диалога, и результаты вызванных инструментов.

Важно держать в голове простую мысль: сама модель не открывает файлы, не знает состояние вашего git-репозитория и не запускает тесты. Она работает только с тем, что попало в ее входной контекст. Если нужный кусок кода не был прочитан, модель не может на него опереться. Отсюда и главная особенность работы агента: он вынужден постоянно добывать информацию о проекте через дополнительные шаги.

Современные модели часто мультимодальны. Это значит, что они могут принимать не только текст, но и изображения: скриншоты, макеты, схемы интерфейсов. Для coding agent это полезно, например, когда нужно объяснить ошибку по скриншоту, сверить результат верстки или разобрать интерфейс без ручного описания каждой детали.

Почему агент не помнит проект по-настоящему

Одна из самых важных особенностей LLM состоит в том, что они stateless. У модели нет настоящей долговременной памяти о вашей сессии. Каждый новый запрос по сути исполняется заново, а ощущение непрерывного диалога создается тем, что оболочка агента каждый раз передает модели накопленную историю сообщений, результаты предыдущих шагов и нужные фрагменты контекста.

Именно поэтому длинная сессия постепенно становится тяжелее. Чем больше в ней было сообщений, прочитанных файлов, выводов команд и промежуточных рассуждений, тем больше токенов нужно заново отправлять модели. Это влияет и на цену, и на скорость, и на качество. В какой-то момент старый контекст начинает не помогать, а мешать: важное смешивается с второстепенным, а модель начинает хуже различать, что относится к текущей задаче.

Отсюда появляется практическое следствие. Если тема разговора изменилась, сессию лучше сбрасывать. Если задача большая, ее полезно делить на этапы. А если агент начал путаться, проблема часто не в том, что он "сломался", а в том, что контекст разросся и потерял четкие границы.

Chat history вместо настоящей памяти

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

Условно говоря, если сначала попросить агента написать функцию, а потом сказать "теперь используй другую библиотеку", модель не вспоминает прошлый ответ так же, как это сделал бы человек. Она просто снова получает весь диалог в виде текста и продолжает его с текущего места. Поэтому поведение агента сильно зависит от того, как именно сформулирована история общения и что в нее попало.

Если сильно упростить, это можно представить так:

user: напиши функцию на Python для чтения CSV-файла
assistant: def read_csv(path): ...
user: а теперь сделай вариант через pandas
assistant:

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

Это же объясняет, почему агенты стараются аккуратно обращаться с контекстом и не переписывать старые сообщения без необходимости. Многие провайдеры используют кеширование токенов: если начало запроса повторяется, его обработка может стоить дешевле. Для длинных инженерных сессий это важная оптимизация.

Системный промпт: скрытая часть поведения

Пользователь видит только свой запрос и ответ агента, но между ними почти всегда есть еще один важный слой - system prompt. Это скрытый набор инструкций, который задает правила поведения модели: как себя вести, в каком тоне отвечать, когда предлагать план, как оформлять вызовы инструментов, что считать безопасным, а что нет.

Во многом именно системный промпт делает из общей модели coding agent. Он объясняет, что агент должен сначала исследовать проект, не делать опасных действий без прямого запроса, запускать проверки после правок, кратко отчитываться о результате и использовать доступные инструменты по определенным правилам.

Когда кажется, что агент "слишком инициативный" или наоборот "слишком осторожный", причина часто находится именно здесь. Это не абстрактный характер модели, а конкретные инструкции, которые навесили разработчики инструмента. Поэтому разные агенты на одной и той же модели могут вести себя заметно по-разному.

Инструменты: как агент выходит во внешний мир

Ключевое отличие агента от обычного чата с моделью - возможность вызывать инструменты. Инструментом может быть чтение файлов, поиск по проекту, запуск команды в терминале, открытие браузера, обращение к API, применение патча или запуск тестов.

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

В упрощенном виде это может выглядеть так:

system: если нужно посмотреть содержимое файла, вызови <tool>read_file(path)</tool>
user: что лежит в package.json?
assistant: <tool>read_file("package.json")</tool>
user: <tool-result>{"name":"demo-app","scripts":{"test":"vitest"}}</tool-result>
assistant: в package.json описан проект demo-app, а тесты запускаются через vitest

В реальном агенте формат обычно аккуратнее и строже, но логика остается той же. Модель сама не открывает package.json. Она просит оболочку сделать это за нее и потом использует результат в следующем шаге.

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

Как выглядит цикл работы агента

Внутренне работа coding agent обычно сводится к повторяющемуся циклу.

┌──────────────┐
  │ 1. Задача от │
  │  пользователя│
  └──────┬───────┘
         ▼
  ┌──────────────┐
  │ 2. Сборка    │◀─────────────────────────┐
  │    prompt    │                          │
  └──────┬───────┘                          │
         ▼                                  │
  ┌──────────────┐    Да    ┌────────────┐  │
  │ 3. Нужен     │────────▶ │ 4. Вызов   │  │
  │  инструмент? │          │ инструмента│  │
  └──────┬───────┘          └─────┬──────┘  │
         │ Нет                    │         │
         ▼                        ▼         │
  ┌──────────────┐         ┌────────────┐   │
  │ 5. Итоговый  │         │  Результат │───┘
  │    ответ     │         │  в контекст│
  └──────────────┘         └────────────┘
  1. Пользователь ставит задачу.
  2. Агент собирает prompt: историю сессии, системные инструкции, список инструментов и доступный контекст.
  3. Модель решает, что делать дальше: ответить сразу или сначала вызвать инструмент.
  4. Если нужен инструмент, оболочка запускает его и возвращает результат модели.
  5. Цикл повторяется, пока агент не сможет выдать итоговый ответ или не выполнит нужное действие.

На словах это звучит почти слишком просто, но именно из этой схемы вырастает почти все поведение современных coding agents. Разница между слабыми и сильными агентами часто не в самой идее, а в качестве реализации деталей: как они выбирают, что читать, как экономят контекст, как ограничивают рискованные действия, как показывают промежуточный прогресс и насколько надежно работают с инструментами.

Reasoning и промежуточное рассуждение

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

В практической работе это заметно, например, при дебаге. Агент может прочитать stack trace, найти место падения, пройти по связанным функциям, а затем предложить исправление. Без этапа дополнительного рассуждения такие задачи чаще сводились бы к поверхностному анализу и галлюцинациям.

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

Запрос
                │
                ▼
  ┌───────────────────────┐
  │   Reasoning: low      │   Быстро, дёшево.
  │                       │   Переименование, шаблонный код,
  │   "Ответ сразу"       │   простые правки
  └───────────────────────┘
                │
                ▼
  ┌───────────────────────┐
  │   Reasoning: medium   │   Баланс глубины и стоимости.
  │                       │   Написать функцию, добавить тест,
  │   "Подумаю немного"   │   несложный дебаг
  └───────────────────────┘
                │
                ▼
  ┌───────────────────────┐
  │   Reasoning: high     │   Много токенов и времени.
  │                       │   Архитектурные решения, сложный дебаг,
  │   "Разберусь глубоко" │   анализ незнакомого кода
  └───────────────────────┘

Многие модели позволяют управлять глубиной рассуждения. Обычно это выражается в уровнях: low, medium и high. На низком уровне модель почти не тратит токены на внутренние рассуждения и отвечает быстро. Это подходит для простых задач: переименовать переменную или сгенерировать шаблонный код. Средний уровень дает разумный баланс между глубиной и стоимостью и хорошо работает для повседневных задач: написать функцию, разобраться в несложном баге, добавить тест. Высокий уровень включает развернутое внутреннее рассуждение и тратит заметно больше токенов и времени. Он полезен там, где нужно глубоко разобраться и показать все что скрыто.

В агентах уровень reasoning обычно определяется выбором модели или отдельной настройкой. Необязательно всегда использовать максимальный: для рутинных правок это лишняя трата бюджета, а на простых задачах избыточное рассуждение может даже мешать, заставляя модель усложнять то, что не требует сложности.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845