Создание Kotlin-агента широкого профиля KMP с помощью фреймворка Koog
Habr AI
В статье рассказывается о создании агента для генерации простых KMP приложений и кросс-платформенных задач с помощью фреймворка Koog. Описывается процесс подключения зависимости в gradle, определения минимального агента, подготовки инструментов и MCP, а также подключения агента к плагину Continue Dev.
Введение
Фреймворк Koog позволяет разработчикам создавать интеллектуальных агентов, используя Kotlin. Он поддерживает работу с различными провайдерами LLM и имеет такие особенности, как интеграция с MCP, встраивание инструментов и создание пользовательских инструментов.
Инструменты
Для создания агента необходимо подготовить инструменты, такие как сетевой слой, слой презентации и архитектуры, бизнес-логика. Инструменты можно разделить на группы, такие как инструменты оркестрации, шаблоны слоев и инструменты для генерации кода.
Шаблоны слоев
Шаблоны слоев используются для генерации кода для каждого архитектурного слоя. Они включают в себя сетевой слой, доменный слой и презентационный слой.
Графы и стратегии
Для создания стратегии на основе графа инструментов необходимо определить минимальную схему, которая включает в себя узлы и переходы. Граф позволяет агенту понимать, как получить результат.
Прикрепляем к Continue
Для того, чтобы использовать агент, необходимо прикрепить его к плагину Continue Dev. Для этого необходимо создать MCP-сервер и настроить его на запуск.
Генерация агента
Агент можно сгенерировать с помощью Cursor и промта. Однако, на деле, может потребоваться дополнительная настройка и конфигурация.
Волшебный Koog. Пишем Kotlin-агент широкого профиля KMP
Habr AI
Всем привет! С вами Анна Жаркова, руководитель мобильной практики ГК Юзтех. Что ж, за последние полгода мир разработки и мир ИИ скакнули и ушли далеко вперед. Теперь знания работы с агентами, умение написать не только правильный промт, но и собственные скиллы (навыки) для этих агентов, готовить свои MCP для погружения в контекст задачи, проекта, становятся не только полезными, но и обязательными для разработчиков и IT-специалистов. Уже многие используют как специальные IDE с ИИ-агентами (Claude, Cursor, Windsurf и т.п.), так и встраиваемые в привычные VsCode и AndroidStudio в виде плагинов. Можно не ограничиваться готовым настраиваемым функционалом, а пойти дальше и написать свой собственный агент. И сегодня мы поговорим про такое решение, использование специального фреймворка отJetBrains Koogдля разработки своих агентов. С его помощью мы создадим агент для генерации простых KMP приложений и кросс-платформенных задач и подключим к плагину Continue Dev.
Небольшой спойлер: сам агент был написан при участии Cursor, и про нюансы его создания читайте в конце статьи.
А пока мы поговорим:
что такое Koog
что входит в собственный агент
как подготовить tools и MCP
как подключить наш MCP к Continue Dev плагину.
Koog — это фреймворк на базе Kotlin, разработанный JetBrains для создания и запуска AI-агентов. Его основная идея заключается в том, чтобы дать разработчикам возможность строить интеллектуальных агентов, используя Kotlin. Это означает, что вы можете создавать сложные AI-решения, оставаясь в привычной и любимой среде Kotlin, без необходимости изучать новые языки или парадигмы для работы с AI. Что мне понравилось: Koog можно использовать как компонент вашего кроссплатформенного KMP приложения. Вы можете в одном проекте создать и MCP сервер, и агента, и клиента для проверки. Также вы можете встроить агента на Koog в свое приложение.
У Koog есть и другие особенности:
Интеграция с MCP (Model Context Protocol): позволяет эффективно управлять моделями и их контекстом.
Возможности встраивания (Embedding capabilities) для семантического поиска и извлечения знаний, что критически важно для RAG (Retrieval Augmented Generation) систем.
Создание пользовательских инструментов, которые могут взаимодействовать с внешними системами и API. Это позволяет агентам выполнять действия в реальном мире.
Готовые компоненты: Koog поставляется с набором предварительно созданных решений для общих задач AI-инженерии, что значительно ускоряет разработку.
Интеллектуальное сжатие истории.
Постоянная память агента: агенты могут сохранять знания между сессиями и даже между разными агентами, что позволяет создавать более сложные и адаптивные системы.
Создание графовых стратегий из инструментов для решения задач.
Модульная система функций.
Масштабируемая архитектура: Koog способен обрабатывать рабочие нагрузки от простых чат-ботов до корпоративных приложений.
Также Koog поддерживает работу с различными провайдерами LLM, как внешними (ChatGPT, Gemini, Claude и т.п.), так и локальными (Ollama).
Начинаем свою работу с подключения зависимости в gradle. Вы можете это делать как в основном модуле, так и выделенном под код вашего агента:
Для определения минимального агента достаточно указать AIAgent, задать ему нужныеPromptExecutor(исполнитель промтов, завязанный на провайдер) иLLModel(поддерживаемая модель для исполнителя).
Также в агенте можно задать:
набор инструментов,
стратегию (граф с порядком вызова комбинации инструментов),
некоторые ограничения.
Koog применяют в задачах, где поведение системы нельзя свести к одному вызову модели. Если модель должна не только сгенерировать ответ, но и вызывать нужные инструменты, соблюдать порядок шагов, переиспользовать определенную логику и удерживать шаблоны, заготовки для проекта, требуется агентный контур, а не только системный промт. Именно к таким задачам и относится, например, генерация кода приложения по слоям.
В целом, агенты на Koog в зависимости от предстоящей задачи можно разделить на следующие группы:
Prompt-only агент Минимальный сценарий: один запрос к модели, без tools. Хорошо для быстрых задач, плохо для автоматизации.
Tool-driven агент Модель умеет вызывать функции/инструменты. Хорошо для интеграций и контролируемых действий.
Graph-based агент (workflow) Сценарий разложен на узлы и переходы. Хорошо для многошаговых процессов
Composed / Multi-agent стиль Несколько подзадач или субграфов, каждый со своей ролью/набором tools. Применяется, когда одна “универсальная” модель становится слишком сложной.
Наша задача - создать инструмент, который будет помогать строить приложение на KMP. Поэтому выбираем агент на графе с использованием инструментов.
Инструменты
Будем исходить из того, что нам нужно в нашем приложении:
сетевой слой (на Ktor),
слой презентации и архитектуры,
бизнес-логика.
Мы хотим, чтобы у нас создавались как отдельные части приложения, так и все вместе. Поэтому нам потребуются наборы инструментов:
для генерации сети и бизнес-логики (NewsAppTooling)
для генерации архитектуры (LayerTemplateTools)
для цельного рабочего процесса - оркестрации (OrchestratorTools).
В Koog инструмент — это Kotlin-метод, помеченный@Tool. Он входит в класс ToolSet, а затем регистрируется в реестре инструментов, ToolRegistry.
Рассмотрим на примере генератора слоев:
На уровне API здесь происходят две вещи:
@Toolделает метод доступным модели;
@LLMDescriptionобъясняет, зачем и когда его вызывать. Это правило и ключевые слова.
Таким образом, шаг перестает быть неявным рассуждением модели и становится наблюдаемым действием.
Не для каждого шага нужно создание своего инструмента, что-то можно оставить обычной функцией:
если модель должна явно решить, нужен этот шаг или нет, это инструмент;
если шаг нужен только внутренней реализации, это обычная функция.
Готовый инструмент регистрируем в ToolRegistry, реестре инструментов:
Реестр инструментов подключается к AI агенту во время его создания:
Теперь рассмотрим подробнее инструменты, которые мы будем использовать.
Инструменты оркестрации: план и общий язык
Первая группа инструментов —OrchestratorTools— код не пишет. Она управляет ходом всей сессии. Её задача: превратить свободный запрос пользователя в конкретный, воспроизводимый план, а затем закрепить ключевые названия, чтобы агент и пользователь дальше говорили на одном языке.
Два главных метода здесь:
Первый,planKmpNewsApp, берёт пожелания вроде «сделай новостной клиент» и выдаёт на выходе не общие слова, а чёткую спецификацию: архитектурные слои, порядок их реализации, ограничения (например, поNetworkConfig), список компонентов и даже последовательность следующих вызовов. Получается не импровизация, а понятная инструкция: имея такой план, агент не потеряется даже в длинной цепочке шагов.
Второй,rememberGenerationSymbol, закрывает вечную проблему кодогенерации — несогласованные имена. Если в одном ответе репозиторий уже назвалиNewsRepositoryImpl, а use case —GetTopHeadlinesUseCase, этот инструмент запоминает выбор. При следующем шаге не придётся заново придумывать имена и рисковать, что репозиторий вдруг окажетсяNewsDataRepository. Вся команда (агент и пользователь) продолжает работать в единой системе обозначений.
Оркестратор вынесен отдельно, потому что планирование и фиксация названий не относятся ни к сети, ни к UI, ни к бизнес-логике. Если смешать их с генерацией кода, получится инструмент с размытыми обязанностями: он будет и управлять процессом, и создавать шаблоны одновременно. Разделение даёт простую границу: OrchestratorTools решает «что и в каком порядке делаем», а следующие инструменты — «как именно это будет выглядеть в коде».
Шаблоны слоёв: сеть, логика, интерфейс
Если оркестратор прокладывает маршрут, тоLayerTemplateTools— это фабрика заготовок для каждого архитектурного слоя. Внутри три метода:
Каждый возвращает не готовое приложение, а типовой каркас:
презентационный— ViewModel и Compose‑экран (вcomposeAppилиshared).
Мы разделяем генерацию слоев, чтобы, во-первых, не смешивать их внутреннюю логику и инструменты, во-вторых, перезапускать по отдельности, в-третьих, для читабельности. Нет одного огромного методаgenerateApp(), который валится целиком. Если что-то пошло не так на уровне доменного слоя, это не ломает сетевой или UI. Можно гибко перезапускать или корректировать отдельные шаги.
В итоге OrchestratorTools и LayerTemplateTools работают в паре по принципу «сначала договариваемся, потом делаем». Первый держит фокус на общей картине и терминах, второй — дает готовые архитектурные лекала. Граница между ними и позволяет агенту одновременно быть стратегом и аккуратным исполнителем.
Чтобы генерация компонента или слоя отвечала нашим ожиданиям, задаем шаблоны и заготовки для примера. В текущем проекте эту роль играетNewsAppTooling.
Этот слой решает одну архитектурную проблему: модель не должна каждый раз заново “изобретать” базовую архитектуру проекта.
Рассмотрим некоторые шаблоны:
Графы и стратегии
Мы не хотим вручную вызывать все инструменты. Нам нужно ввести промт и получить результат. Как его достичь, наш агент должен понимать сам. Для того, чтобы получить именно пошаговую реализацию, или план и реализацию, нам потребуется создать стратегию на основе графа инструментов.
Минимальная схема мыслится так:
[Start] | [CollectContextNode] --(context)–> [PlanningSubgraphWithTools] --(result)–> [Finish] Что здесь важно:
узел делает один смысловой шаг;
переходы явно передают преобразованный выход;
subgraph позволяет “вложить” этап, где модель активно вызывает tools. :
val strategy = strategy { val collect by nodeLLMRequest(“collect”, allowToolCalls = false) val plan by subgraphWithTask(tools = toolRegistry.tools) { input -> “Сделай план и используй tools” }
В нашем проекте граф выглядит немного по-другому, т.к. у нас есть повторные фазы:
Что здесь означает каждый узел
nodeLLMRequest— отправляет запрос в модель;
nodeExecuteTool— исполняет вызванный инструмент;
nodeLLMSendToolResult— возвращает результат инструмента обратно в модель;
nodeFinish— завершает работу агента.
Почему переходы соединены именно так
nodeStart -> nodeLLMRequest: выполнение всегда начинается с модели;
nodeLLMRequest -> nodeExecuteToolпоonToolCall: если модель запросила инструмент, управление передается исполнителю;
nodeLLMRequest -> nodeFinishпоonAssistantMessage: если пришел обычный ответ, работа завершается;
nodeExecuteTool -> nodeLLMSendToolResult: после исполнения инструмента его результат обязан вернуться в модель;
nodeLLMSendToolResult -> nodeExecuteToolпоonToolCall: после получения результата модель может запросить следующий инструмент;
nodeLLMSendToolResult -> nodeFinishпоonAssistantMessage: если после результата инструмента модель завершила рассуждение, агент заканчивает работу.
Как выглядит выполнение задачи в текущем агенте
Полный сценарий выполнения текущего агента на одной задаче выглядит так.
Пользователь формулирует запрос:
Агент проходит его так:
модель получает запрос;
оркестратор строит план черезplanKmpNewsApp;
при необходимости фиксируется naming черезrememberGenerationSymbol;
последовательно вызываются шаблоны сетевого, доменного и презентационного слоев;
после этого агент либо завершает ответ, либо продолжает следующий ход уже с опорой на детерминированные результаты предыдущих вызовов.
Короткая схема:
Подробный смысл каждого вызова в этой последовательности:
planKmpNewsAppВозвращает определенный план реализации. Это первая обязательная точка, потому что без нее агент не фиксирует, какие слои должны появиться, какие компоненты обязательны и в каком порядке двигаться дальше.
rememberGenerationSymbolВызывается не всегда. Он нужен только тогда, когда в ходе сессии появилось имя, которое нужно закрепить и переиспользовать дальше без дрейфа.
generateNetworkLayerTemplateВозвращает каркас сетевого слоя:NetworkConfig, схему подключенияKtor, базовые ожидания к API и общую форму кода вcommonMain.
generateDomainLayerTemplateВозвращает каркас доменного слоя: интерфейс репозитория и use case. Этот шаг отделяет транспорт и сериализацию от доменной модели и сценариев использования.
generatePresentationLayerTemplateВозвращает каркас презентационного слоя:ViewModel, состояние экрана и общую формуCompose-экрана.
Именно поэтому оркестратор должен быть описан отдельно от layer tools. В этой последовательности он не «еще один инструмент», а управляющий уровень, который сначала определяет план и согласованность имен, а уже затем передает работу инструментам прикладных слоев.
Прикрепляем к Continue
Что ж, нам надо, чтобы инструментом можно было свободно пользоваться. Если мы будем запускать модуль агента, как есть, то пользоваться им можно будет только через cli. Превратим нашего агента в MCP-сервер. MCP (Model Context Protocol) — это протокол, через который внешний клиент вызывает инструменты и получает их результаты в стандартной форме. В этой архитектуре он нужен не для внутренней работы Koog-агента, а для публикации возможностей системы наружу. Установим библиотеку:https://github.com/modelcontextprotocol/kotlin-sdk
Теперь займемся самим сервером:
Подключим нашего агента к модулю и добавим инструменты серверу:
Настроим наш сервер на запуск:
Упакуем в fat JAR:
Теперь нам нужно создать скрипт для подключения MCP-сервера к Continue:
Проверим, что инструменты нашлись:
Пример работы нашего генератора:
Теперь поговорим о проблемах. Результат зависит от той LLM, которую вы будете использовать. Будьте готовы к такому:
Или такому:
Генерация агента
Как я анонсировала, агент был сгенерирован с помощью Cursor и такого промта:
На первый взгляд, все указано для создания. Но на деле, пришлось просить добавить MCP, создать конфигурацию под Continue и многое другое. Все зависит от вашей модели.
Вы можете отдельно настроить rules для вашего плагина, добавить команды и промты. Если сравнивать производительность и корректность работы плагина с собственным MCP и готового агента с настроенными навыками, то все индивидуально. Зависит как от ваших настроек и кода, так и модели, которую вы выбрали. Иногда действительно достаточно взять просто более мощную LLM, чтобы ваши труды оправдались.