Ещё недавно вопрос «на чём собирать агента» означал выбор фреймворка. Сейчас он всё чаще означает другое: какой протокол обмена с инструментами вы используете и что остаётся вашим кодом. Разница не косметическая — она меняет, где проходят границы системы.
Как выглядела сборка до этого
Агент — это цикл: модель получает запрос и описание доступных действий, выбирает действие, получает результат, продолжает. Всё интересное происходит в двух местах: в описании инструментов и в исполнении вызовов.
В фреймворковом подходе оба места живут внутри приложения. Каждый инструмент — это класс или функция с описанием, зарегистрированная в реестре фреймворка. Добавить доступ к трекеру задач означает написать обёртку над его API, описать её, обработать ошибки, разложить ответ. Обёртка живёт в вашем репозитории и обновляется вашей командой.
Пока инструментов три, это незаметно. На двадцати становится видно, что половина кода агента — это чужие API, переписанные вручную, и что каждая команда в компании пишет те же обёртки заново.
Тупик: взять фреймворк пошире
Первая реакция на разрастание — выбрать фреймворк, где готовых интеграций больше. Логика понятная: чем шире каталог, тем меньше писать самим.
Не помогает это по трём причинам, и все три вылезают на втором месяце.
Каталог готовых интеграций всегда отстаёт от того, что нужно именно вам: внутренние системы в него не попадут никогда, а их обычно большинство. Готовая интеграция описывает инструмент так, как решил её автор, и переописать её под свою задачу часто дороже, чем написать с нуля. И главное — интеграции остаются внутри процесса агента: их нельзя дать другому агенту, написанному в другой команде на другом стеке, иначе как скопировав код.
Второй ход, который пробуют следом, — вынести инструменты в собственный внутренний сервис с самодельным форматом описаний. Это уже правильное направление, но самодельный формат означает, что каждый новый потребитель пишет клиента под вас, и вы навсегда становитесь ответственным за совместимость.
Что меняет протокол
Протокольный подход разносит систему на две части с явной границей. С одной стороны — агент: цикл, модель, политика. С другой — сервер инструментов: он объявляет, какие действия умеет, и исполняет вызовы. Между ними стандартизированный обмен: список доступного, описание аргументов, вызов, результат, ошибка.
Три следствия, ради которых это делается.
Список инструментов становится данными времени выполнения. Агент узнаёт, что он умеет, спросив сервер, а не из своего кода. Добавление инструмента перестаёт быть выкаткой агента.
Инструмент переиспользуется между агентами. Сервер, дающий доступ к внутренней системе, пишется один раз командой, которая эту систему знает, и подключается всеми — независимо от языка и фреймворка потребителя.
Границы становятся местом, где можно навести порядок. Права, аудит, ограничение частоты вызовов, маскирование чувствительных полей — всё это переезжает на границу и перестаёт быть заботой каждого агента по отдельности.
Что протокол не решает
Здесь важно не поддаться ощущению, что вопрос закрыт.
Он не решает, кому что можно. Протокол переносит вызов, но политика доступа — ваша: агент действует от чьего-то имени, и связать это имя с правами в целевой системе придётся самим.
Он не делает описания инструментов хорошими. Модель выбирает действие по тексту описания, и плохо написанное описание ломает агента ровно так же, как раньше. Это по-прежнему работа человека, и она ближе к редактуре, чем к программированию.
Он не отвечает за доверие к содержимому ответа. Результат вызова возвращается в контекст модели, и текст в нём — данные, а не команды. Если сервер вернул строку с указаниями, агент не должен принимать её за инструкцию; протокол этого сам по себе не гарантирует.
Он не убирает сетевую природу происходящего. Появляются таймауты, повторы, частичные отказы и вопрос, что делать, если инструмент ответил на третьей минуте, когда пользователь уже ушёл.
Что меняется в архитектуре
Самое заметное — где проходит граница ответственности. Раньше команда агента отвечала за всё; теперь она отвечает за цикл и политику, а команда системы — за свой сервер инструментов и за то, как её система описана.
Второе — версионирование. Описание инструментов становится контрактом между двумя сторонами, которые выкатываются независимо. Работают те же правила, что и с любым контрактом: не менять смысл поля, добавлять, а не переименовывать, объявлять устаревшее заранее.
Третье — тестируемость. Сервер инструментов можно проверять без модели: список действий, аргументы, ответы, ошибки — обычный сервис, который вызывается из тестов. Это заметно лучше, чем прогонять весь агент, чтобы понять, почему сломался один вызов.
Что делать руками при переходе
Переход редко делается целиком и почти никогда не начинается с самой нужной системы.
Начните с инструмента, который нужен больше чем одной команде и не меняется каждую неделю: справочник, поиск по внутренней базе знаний, чтение из трекера. Такой инструмент даёт главную выгоду — переиспользование — и не заставляет одновременно решать вопросы записи и прав.
Разделите чтение и запись физически. Сервер, который только читает, проверяется просто и подключается быстро. Сервер, который меняет состояние чужой системы, требует ответов на вопросы про подтверждение, повторные вызовы и откат — их лучше решать отдельно, а не вместе с переездом.
Опишите каждый инструмент так, как объясняли бы новому сотруднику: что он делает, чего не делает, какие аргументы обязательны и что вернётся при неудаче. Именно этот текст читает модель, и именно он определяет, будет ли инструмент вызван вовремя и с нужными аргументами.
Заведите журнал вызовов на границе с самого начала. Он окупается в первый же день расследования: без него непонятно, что агент вызвал, что получил и почему решил, что этого достаточно.
И сразу договоритесь, что попадает в контекст модели из ответа. Полный ответ чужой системы обычно содержит и лишнее, и чувствительное; обрезать его на границе дешевле, чем потом объяснять, как персональные данные попали в переписку с моделью.
Когда протокол не нужен
Когда инструмент один и он ваш. Обмен через границу добавит слой, который нечему окупать.
Когда все действия — чистые вычисления внутри одного приложения. Здесь функция лучше сетевого вызова по всем параметрам.
Когда агент существует в единственном экземпляре и никто, кроме него, инструменты использовать не будет. Переиспользование — главная выгода, и без него остаётся только цена.
Позиция редакции
Мы считаем, что выбор «фреймворк или протокол» на самом деле не выбор: фреймворк решает, как устроен цикл агента, протокол — как он дотягивается до чужих систем. Практический вывод: границу стоит проводить по владению — то, что принадлежит другой команде, подключается через протокол, а не вносится в ваш репозиторий обёрткой.
Мы неправы, если у вас один агент, три инструмента и одна команда на всё. Тогда протокол добавит сетевой слой, эксплуатацию и версионирование контракта ради выгоды, которой в вашем масштабе просто нет, — и обычные функции окажутся честнее.