ReAct-агент изнутри: как модель превращает текст в действия и где проходит граница безопасности
Разбор механики ReAct-агентов показывает, как ответ языковой модели превращается в чтение файла, запуск команды или изменение кода. Управляющий цикл, схемы инструментов и повторные запросы к API образуют конструкцию, которую можно собрать на чистом Python без агентных фреймворков. Понимание этой механики важно и для разработчиков, и для тех, кто оценивает риски автономных систем.
В публикации технического потока подробно разобрана внутренняя архитектура ReAct-агента — системы, в которой языковая модель не просто отвечает на вопрос, а последовательно запрашивает действия и получает их результаты. Автор предлагает пройти путь от ответа модели до реального чтения файла или запуска команды на четырёх общих схемах, а затем посмотреть на реализацию на Python и на конкретный сценарий с падающим тестом. Для понимания кода, по замыслу материала, достаточно знания Python, JSON и принципа HTTP-запроса, а агентные фреймворки не требуются. Это принципиальная позиция: механика цикла достаточно проста, чтобы собрать её вручную и увидеть, где именно принимаются решения. Ключевое отличие агента от одиночного запроса к модели описано через доступ к среде. В простом приложении модель получает вопрос и возвращает ответ, а если для решения нужны данные из файла, приложение само читает файл и добавляет содержимое в запрос, поскольку текстовый API не даёт модели доступа к файловой системе. В агентном цикле модель может запросить действие, например чтение конкретного файла, агент выполняет его, добавляет результат в историю и снова обращается к модели. Та выбирает следующий шаг уже с учётом полученных данных: прочитать другой файл, запустить проверку или закончить работу ответом. Последовательность действий не нужно прописывать заранее — задаются доступные инструменты и ограничения, а конкретные вызовы модель выбирает по ходу работы, и один и тот же цикл обслуживает чтение файлов, поиск данных или запуск проверок.
Разделение ролей в этой схеме принципиально: агент управляет обменом между моделью и инструментами, модель формирует запрос на действие, а агент разбирает ответ, проверяет допустимость вызова и запускает соответствующий инструмент, который выполняет обычную операцию — читает файл, обращается к сервису или запускает процесс. Модель может вернуть имя read_file и аргумент path, но на диске в этот момент ещё ничего не происходит: файл будет прочитан только после того, как агент найдёт инструмент по имени и вызовет его с переданными аргументами. Если агент отклонит путь или такого инструмента нет в списке доступных, вызов не выполнится. Отсюда следует и граница безопасности: инструкция «не меняй тесты» полезна для выбора действия, но запрет на запись должен обеспечиваться кодом и правами среды исполнения, а не доверием к тексту системного промпта.
Повторный цикл нужен потому, что результат инструмента может быть разным: исходный код, сообщение об отсутствии файла или ошибка доступа — это разные данные для следующего решения, поэтому после выполнения инструмента модель вызывается ещё раз с передачей результата. Название ReAct происходит от сочетания reasoning и acting, то есть рассуждения чередуются с действиями и наблюдениями из среды; в исходной работе это показано через текстовые траектории с рассуждениями и действиями. В разобранном примере запросы инструментов передаются штатными полями API, а не собственным парсером строк Thought и Action, так что общая идея обратной связи сохраняется, но это не буквальное воспроизведение исходного промпта. Для разбора достаточно наблюдать выбранное действие, результат инструмента и следующий запрос — доступ к скрытым рассуждениям модели не требуется.
Технически один вызов инструмента устроен вокруг схемы аргументов. Чтобы модель могла запросить инструмент, в API передают его имя, назначение и схему аргументов, которая описывает допустимые данные, но не содержит реализацию Python-функции: у инструмента чтения файла обязательный аргумент path, у Bash — строка command и необязательный таймаут. В ответе API запрос инструмента приходит в массиве tool_calls, агент читает имя, разбирает аргументы, выполняет операцию и формирует сообщение с ролью tool, а поле tool_call_id связывает результат с конкретным вызовом; такой обмен показан и в официальном примере DeepSeek. Один ответ может содержать несколько вызовов, и агент должен обработать их и вернуть соответствующие результаты, хотя это не означает параллельного выполнения — в описанной реализации операции идут последовательно. Для продолжения недостаточно отправить модели только вывод команды: агент хранит список messages с системной инструкцией, задачей пользователя, ответами модели с запросами инструментов и результатами этих запросов, дополняя список на каждом шаге, причём сохранять нужно обе стороны обмена — что модель запросила и что агент вернул. Описания доступных инструментов передаются отдельно от истории, а сама история растёт вместе с задачей; для небольшого примера достаточно обычного списка, а сжатие длинных диалогов остаётся за пределами разбора.
Остановка агента описана как отдельный механизм с несколькими исходами. После очередного ответа агент проверяет, есть ли запросы инструментов: если есть, он выполняет их и продолжает цикл, если модель вернула непустой текст без новых вызовов, он принимается как финальный ответ, а пустой или обрезанный ответ считается ошибкой, а не успешным завершением. В примере разрешено до восьми обращений к модели и до шестнадцати вызовов инструментов, и если финальный ответ не получен в этих пределах, попытка заканчивается ошибкой. Таймаут Bash устроен иначе: команда останавливается, а сообщение об ошибке возвращается модели; слишком длинный вывод обрезается с признаком truncated: true, что тоже не завершает цикл. Автор отдельно подчёркивает, что сам по себе финальный ответ не доказывает правильность решения задачи, — это важное замечание для всех, кто встраивает подобные системы в рабочие процессы.
В контексте рынка материал важен тем, что снижает порог входа в агентную разработку: вместо освоения фреймворка предлагается собрать цикл из трёх элементов — клиента вроде DeepSeekClient, который отправляет запрос, класса Agent, управляющего историей и циклом, и классов инструментов, выполняющих операции, причём каждый инструмент лежит в отдельном файле и имеет метаданные. Такой подход контрастирует с готовыми агентными платформами, которые скрывают механику за абстракциями и тем самым затрудняют аудит того, какие именно вызовы были разрешены и выполнены. Для российских команд, разрабатывающих внутренние ассистенты и автоматизацию разработки, ценность разбора в том, что он показывает точки контроля: проверку допустимости вызова, ограничение числа шагов, таймауты и обрезку вывода. Эти механизмы не зависят от конкретного поставщика модели, хотя в примере используется API DeepSeek, и могут быть воспроизведены поверх любого совместимого интерфейса.
Открытым остаётся вопрос о том, как масштабировать такую конструкцию: история растёт с каждой задачей, последовательное выполнение нескольких вызовов ограничивает скорость, а проверка безопасности путей и команд требует отдельной инфраструктуры. Перспективным направлением выглядит сочетание простого управляющего цикла с изолированной средой исполнения и журналированием каждого вызова, чтобы результат агента можно было воспроизвести и проверить. Пока же материал фиксирует базовую истину: агент — это не магия модели, а аккуратно написанный цикл, в котором модель предлагает действия, а код решает, какие из них действительно произойдут.