- BrainTools - https://www.braintools.ru -

Как собрать пайплайн с LLM агентом использующим эмуляторы Android девайсов

Какую проблему решаем

LLM пока не может хорошо обращаться с Е2Е автотестами потому что для этого нужно провести целый комплекс мероприятий. Сложность возникает уже на этапе запуска такого автотеста. В отличии от юнит автотестов, Е2Е автотесты почти всегда PageObject и целый проект со своей архитектурой на базе Selenium Appium Espresso и тд.

Чем может быть полезна эта статья

В данной статье я постараюсь описать подход с которым можно сделать агента использующего MCP инструменты для взаимодействия с эмуляторами. Заодно приведу простой пример чем отличается модель от агента и как они взаимодействуют между собой.

Строим пайплайн

Идея такая – мы делаем агента в цикле, который уже имеет системный промпт и набор инструментов. Агент будет запускать различные инструменты чтобы LLM будет сама решать, достаточно ли ей данных или нет. Если данных недостаточно, то LLM будет их запрашивать на ходу.

Стек и архитектура

Как собрать пайплайн с LLM агентом использующим эмуляторы Android девайсов - 1

Пайплайн состоит из трех компонентов:

1. LLM — локальная модель llama3.1:8b через Ollama. Принимает решения,
какие инструменты вызвать. Корректно работает с MCP tool_calls.

2. LangChain4j — оркестратор на Java. Обеспечивает:
– Подключение к LLM через Ollama
– Интеграцию с MCP серверами
– ChatMemory (хранение истории диалога)
– AiServices (создание агента с инструментами)

3. MCP сервер — android-pilot-mcp. Предоставляет 39 инструментов:
device_list, gradle_task, screenshot и другие.

Все три компонента запускаются локально, без облачных сервисов.

Локальный setup

Устанавливаем MCP сервер. Для примера я установил android-pilot-mcp

npm install -g android-pilot-mcp

В репозитории Вы можете найти 39 инструментов: https://github.com/sitharaj88/android-pilot-mcp [1]

Важное замечание о безопасности:

Репозиторий разработан одним человеком (sitharaj88) и на момент написания статьи имеет 0 звезд и 0 форков. Это не значит, что он плохой — код открыт, написан на TypeScript и его можно проверить. Но в любой Open Source библиотеке от неизвестного автора потенциально может быть все что угодно, особенно когда она запускает shell-команды на вашей машине.

Как можно снизить риски:

  1. Фильтруем инструменты. Вместо того чтобы давать агенту доступ ко всем 39 инструментам (включая опасные device_shell или file_push), я оставляю только безопасные:

    ToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpServer)
    .filterToolNames(“device_list”)
    .build();

  2. Запускаем в изоляции  — можно запускать MCP сервер в Docker или под отдельным пользователем.

  3. Никогда не даем LLM доступ к опасным инструментам типа device_shell — если злоумышленник подменит промпт, LLM может выполнить произвольные команды на вашем компьютере или тестовом девайсе.

Вот основные инструменты которые пригодятся:

Инструмент

Что делает

Зачем нужен в пайплайне

device_list

Показывает список подключенных устройств и эмуляторов

Первый шаг — убедиться, что есть на чем запускать тесты

gradle_task

Запускает произвольную Gradle-таску (test, connectedAndroidTest, assemble и др.)

Самый важный инструмент — именно он билдит проект и запускает тесты

gradle_build

Собирает проект с указанием variant (debug/release)

Альтернатива для простой сборки без лишних параметров

gradle_dependencies

Показывает дерево зависимостей модуля

Полезно, когда тест падает из-за конфликта [2] библиотек

device_screenshot

Делает скриншот эмулятора (base64)

Визуальная верификация состояния UI после падения теста

Детали по gradle_task:

Этот инструмент должен быть основным, потому что он один заменяет все команды Gradle:

  • ./gradlew test – юнит-тесты

  • ./gradlew connectedAndroidTest – инструментационные тесты

  • ./gradlew assembleDebug – сборка APK

  • ./gradlew lint – статический анализ

  • ./gradlew clean – очистка

Устанавливаем локальную LLM

ollama pull llama3.1:8b

Создаем локальный проект

Подключаемся к LLM

OllamaChatModel model = OllamaChatModel.builder()
       .baseUrl("http://localhost:11434")
       .modelName("llama3.1:8b")
       .logRequests(true) // можно сразу выводить запросы в сторону LLM
       .logResponses(true) // можно сразу выводить ответы LLM
       .temperature(0.0)
       .build();

Добавляем MCP сервер

McpClient mcpServer = new DefaultMcpClient.Builder()
       .transport(new StdioMcpTransport.Builder()
       .command(List.of("npx", "-y", "android-pilot-mcp"))
       .build())
       .build();

Создаем tool provider для управления MCP серверами. Многие MCP сервера имеют десятки инструментов с описанием. Мы будем использовать только нужные из них, чтобы не раздувать контекст модели без надобности.

ToolProvider toolProvider = McpToolProvider.builder()
       .mcpClients(mcpServer)
       .filterToolNames("device_list")
       .build();

Создаем системный промпт для нашего агента:

@SystemMessage("""
    Ты — агент для взаимодействия с Android эмуляторами через MCP инструменты.
    Ты работаешь циклически: запускаешь тест, собираешь артефакты, анализируешь код и исправляешь ошибки.
    Ты сам решаешь, какие MCP-инструменты вызвать, чтобы собрать достаточно данных.
    Если информации не хватает, ты можешь запросить её дополнительно, не дожидаясь подсказки.
    НИКОГДА не выдумывай результаты инструментов — используй только реальные ответы.
    Анализируй полученную информацию перед следующим действием.
    Если инструмент вернул ошибку, сообщи о ней и скорректируй план.
    """)

Системного промпта будет вполне достаточно. Это общее сообщение для LLM, а CI будет передавать детали конкретного теста для запуска. 

Это будет пользовательское сообщение:

String execute(@UserMessage String userMessage)

Cтоит отметить, что одним из важных этапов пайплайна является использование ChatMemory для хранения информации между вызовами нашего агента к LLM.

ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

Создаем самого агента. Я еще добавил к нему кастомный listener. Это очень удобно если мы хотим получать информацию действительно ли LLM будет использовать инструменты или будет придумывать результаты этих инструментов.

Agent agent = AiServices.builder(Agent.class)
       .chatModel(model)
       .chatMemory(chatMemory)
       .toolProvider(toolProvider)
       .registerListener(new CustomListener())
       .build();

Listener будет отлавливать использование инструментов MCP сервера вот так:

@Override
public void onEvent(ToolExecutedEvent event) {
   ToolExecutionRequest request = event.request();
   // Далее пишем обработку запроса.
}

Проводим эксперименты

Так как мы подключили всего один MCP инструмент, то пока LLM может по настоящему проверить только наличие эмуляторов. Для эксперимента давайте дадим модели также продемонстрировать поведение [3] без реальных инструментов и заодно увидим как наш агент перехватывает запросы от LLM, обращается к MCP серверам и далее продолжает loop с LLM пока не достигнет терминального состояния. (ответ без tool_calls)

Запрос пользователя к агенту:

“Проверь эмуляторы. Затем представь что ты можешь запускать Android автотест с помощью Gradle. Запусти автотест который упадет.”

Шаг пайплайна №1:

Агент в качестве первого запроса отправляет в сторону LLM запрос пользователя:

“role” : “user”,

“content” : “Проверь эмуляторы. Затем представь что ты можешь запускать Android автотест с помощью Gradle. Запусти автотест который упадет.”

Ответ модели содержит:

“Tool_calls”:[{“id”:”call_cp0esd42″,”function”:{“index”:0,”name”:”device_list”,”arguments”:{“format”:”json”}}}

Объект Tool_calls говорит нам о том, что модель не просто прислала текст с имитацией, а запрашивает MCP инструменты.

Также Listener увидел и сообщил нам, что агент обратился к MCP инструментам. Мы видим в логе:

CustomListener – Использование инструмента “device_list”.

MCP android-pilot-mcp просто выполнил у меня на машине локально команду adb devices и нашел работающий эмулятор:

Found 1 device(s)

Также модель при использовании Tool_calls создала сообщение нашему агенту для дальнейшей работы:

“message”:{“role”:”assistant”,”content”:”} Далее, я вызову функцию, которая запускает Android автотест с помощью Gradle. Для этого можно использовать функцию test_run или test_execute.

Шаг пайплайна №2:

Наш агент не показал нам это сообщение и продолжает работу, так как системный промпт еще не выполнен.

Далее агент формирует запрос к LLM и возвращает модели её же предыдущий ответ, чтобы модель продвинула пайплайн для нового шага:

“role” : “assistant”,

“content” : “Далее, я вызову функцию, которая запускает Android автотест с помощью Gradle. Для этого можно использовать функцию test_run или test_execute.”

Ответ модели:

Я запускаю автотест с помощью Gradle. Тестируемая функция – авторизация пользователя.

Теперь я запускаю автотест, который должен упасть:

“`./gradlew test -Dtest.single=TestCrash“`

Автотест упал с ошибкой [4] “NullPointerException” в методе авторизации.

Ответ сейчас уже чисто теоретический поскольку мы и просили агента пофантазировать. В последнем ответе уже нет Tool_calls, поэтому агент просто выдает текст как результат работы пайплайна.

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

Выводы

Мы собрали работающий пайплайн, в котором LLM может:

  • Получить список доступных эмуляторов через MCP

  • Самостоятельно решить, какой инструмент вызвать

  • Определить момент, когда нужно остановиться (ответ без tool_calls)

  • Интерпретировать полученные результаты

Ограничения подхода:

  • Каждый новый инструмент увеличивает контекст

  • Требуется ChatMemory для длительных сессий

Автор: Panarik

Источник [5]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/29894

URLs in this post:

[1] https://github.com/sitharaj88/android-pilot-mcp: https://github.com/sitharaj88/android-pilot-mcp

[2] конфликта: http://www.braintools.ru/article/7708

[3] поведение: http://www.braintools.ru/article/9372

[4] ошибкой: http://www.braintools.ru/article/4192

[5] Источник: https://habr.com/ru/articles/1032042/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1032042

www.BrainTools.ru

Rambler's Top100