Привет, Хабр! Обычно материалы о разработке агентов посвящены успешному успеху: «Мы выбрали лучшую модель, собрали агента, подключили инструменты — и все заработало». Но недавно мне попался отчет о создании ИИ-агента Shippy, где авторы честно рассказали о сложностях проекта и ограничении технологии ИИ. Предлагаю разобрать, как же удалось их преодолеть.

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

О каком агенте речь

Shippy — это ИИ-агент, который в реальном времени анализирует то, что происходит в океане. Это часть Skylight — аналитической платформы для борьбы с незаконным рыболовством от Института искусственного интеллекта Пола Аллена (Ai2). Проект использует спутниковые снимки (как бесплатные, так и коммерческие), а также данные трекинга судов. С помощью Skylight можно отследить подозрительные действия — например, отключения кораблями слежения или сближения для передачи незаконного улова. Это бесплатная платформа, которую используют в 70 странах.

Shippy — это ИИ-агент, через который можно задать платформе вопрос на естественном языке. Под капотом у него Claude Opus 4.6. Нюанс в том, что ошибки агента могут очень дорого стоить — например, патруль, отлавливающий незаконных рыболовов, уйдет не туда.

Так что разработчикам нужно было обеспечить высокий уровень надежности.

Но ИИ-агенты изначально недетерминированы, так что задача не так тривиальна, как может показаться. И по мере разработки Shippy команда столкнулась с целым набором проблем, которые выходят далеко за рамки конкретного проекта. О них и поговорим.

ИИ не понимает границ собственной компетенции

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

Поэтому разработчики отдельно зафиксировали границы того, что Shippy должен и не должен делать. Эти правила прописали в отдельном слое, который авторы называют Soul (душа). В нем, например, прямо указано, что Shippy не должен самостоятельно определять, нарушает ли судно закон. Если пользователь спрашивает про законность конкретного поведения, агент предоставляет относящиеся к вопросу данные, но не делает собственное юридическое заключение. То же касается оперативных решений (посылать или не посылать патруль, а если посылать, то в какую точку). Агенту запрещено выдавать рекомендации о том, что именно следует сделать в конкретной ситуации.

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

За счет того, что «душа» — отдельный слой, записанные туда правила можно менять независимо от самой модели.

ИИ плохо работает в условиях множества правил и ограничений

А эта проблема проявилась при работе Shippy с API Skylight. На раннем этапе разработки агент получил прямой доступ к REST API и должен был самостоятельно формировать запросы. Казалось бы, задача несложная: понять запрос пользователя, выбрать нужный endpoint и передать ему соответствующие параметры.

Но на деле API Skylight оказался слишком сложным для агента. В нем было много параметров, а некоторые запросы требовали учитывать дополнительные ограничения. Агент ошибался при формировании запросов и обработке полученных результатов.

Разработчики поняли, что проблемы у агента возникают, когда ему одновременно нужно учитывать большое количество правил: какие параметры обязательны, какие значения допустимы, как связаны между собой разные поля и что делать с полученным результатом. Он как будто забывает часть этих правил. Решением стал отказ от прямого доступа модели к API в пользу промежуточного слоя CLI с небольшим набором высокоуровневых команд. Под капотом CLI обычные (не ИИ) алгоритмы, которые обрабатывают все строго и без галлюцинаций.

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

ИИ не умеет надежно выполнять длинные последовательности действий

Представим запрос пользователя: «Какие рыболовные суда работали в исключительной экономической зоне Ганы за последнюю неделю?» Для ответа Shippy надо выполнить цепочку действий: сначала получить данные о границах нужной территории, запросить соответствующие события, обработать результаты и собрать из них итоговый ответ. При этом на каждом этапе появляются промежуточные данные, нужные на следующих шагах.

Изначально результаты работы каждого из инструментов передавались агенту через стандартный вывод команд. Это работало, пока объем данных был небольшим. Но с ростом объема данных вывод мог упереться в ограничения буфера pipe — в результате последующая обработка данных становилась ненадежной (могла потеряться часть данных). Вероятность ошибки росла с каждым шагом.

Чтобы это обойти, в Shippy изменили способ передачи промежуточных результатов. Вместо передачи больших объемов через контекст и стандартный вывод, CLI сохраняет результаты в локальные JSON-файлы. Агент сам обращается к этим файлам на следующем этапе, чтобы продолжить обработку.

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

ИИ склонен придумывать то, чего не существует

Самая, на мой взгляд, любопытная проблема, которую разработчики обнаружили при тестировании Shippy, — агент однажды придумал несуществующую CLI-команду для общения с API. Это произошло не на абстрактном тестовом примере — ошибку зафиксировали при проверке реальной версии агента на тех же данных, с которыми работает обычный пользователь.

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

Разобраться с этим помог еще один уровень абстракции. В Shippy выделили навыки — отдельные инструкции для конкретных типов запросов. Они оформлены как версионируемые Markdown-файлы и описывают, как именно выполнять ту или иную задачу. Например, отдельный навык отвечает за запросы к данным о событиях и судах, другой — за поиск границ морских охраняемых территорий, а третий — за интерпретацию треков судов. То есть теперь модель получает не бесконечное пространство возможных действий, а заранее описанный сценарий.

ИИ не умеет самостоятельно обеспечивать надежность

Даже самая крутая модель в чистом виде не гарантирует надежность всей цепочки работы. ИИ-агент взаимодействует с внешними сервисами, а те могут вести себя абы как. Запрос может завершиться ошибкой, данные могут прийти не в том виде или результата может оказаться недостаточно для дальнейшей обработки. В такой ситуации недостаточно просто попросить LLM «проверять свои действия»: если надежность критична, соответствующие проверки должны быть частью самой системы.

В Shippy разработчики постарались убрать из работы LLM операции с предсказуемым результатом. Тот же перенос работы с API в CLI, выделение Soul и навыков — части этого процесса. 

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

Так разработчики разделили ответственность: LLM определяет, какую операцию нужно выполнить, а критические технические детали «вспоминает» детерминированный инструмент.

ИИ плохо поддается тестированию

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

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

После выполнения задания результат оценивает отдельный LLM-судья. Он проверяет каждый критерий по шкале от 0 до 1 и объясняет выставленную оценку. Затем оценки объединяются с учетом заданных весов и сравниваются с порогом, после чего тест считается пройденным или проваленным.

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

Именно этот подход помог обнаружить «придуманную» CLI-команду, которая упоминалась выше. А другой тест выловил, что упрощение геометрии привело к потери части событий из ответа. 

Вместо выводов

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

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

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

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

Комментарии (0)