Я выпустил Android-приложение, ежедневные аналитические данные которого формируются Gemini Nano, работающим полностью на телефоне — без облака, без подсказок и вывода данных за пределы устройства. В документации интеграция казалась тривиальной. В производственной среде возникли три сложных момента, которые изменили мою архитектуру. Вот описание, которое я хотел бы иметь до начала работы.
Краткий контекст: Tawen считывает данные о сне, вариабельности сердечного ритма и активности из Health Connect, вычисляет оценку готовности (0–100) на устройстве, а затем использует ML Kit GenAI Prompt API (который находится на AICore, системной службе Android для базовых моделей на устройстве), чтобы объяснить эту оценку простым языком. Данные о здоровье, подсказки и выходные данные остаются на устройстве.
Вот что на самом деле оказалось проблемой.
1. Инференс происходит только в явном виде, и это обязательно
В моем первом варианте дизайна дневной отчет предварительно генерировался в задании WorkManager, чтобы он отображался мгновенно при открытии приложения пользователем. Чистый, идиоматический и совершенно неправильный подход: GenAI API возвращает ErrorCode.BACKGROUND_USE_BLOCKED в момент вызова без видимого пользовательского интерфейса — в том числе и из фонового сервиса. AICore намеренно отказывается от инференса, если ваше приложение не на переднем плане.
Это не квота, которую можно обойти; это ограничение проектирования. Таким образом, архитектура инвертируется: вывод происходит перед пользователем, запускается экраном, который в этом нуждается, и все, что может быть предварительно вычислено без модели (сама оценка — подробнее об этом ниже), предварительно вычисляется, в то время как озвучка генерируется в режиме реального времени, когда соответствующий экран находится на переднем плане. Если вы планируете «прогреть» встроенную модель LLM в фоновом режиме, учтите, что это не сработает.
2. AICore фактически однопоточный — параллелизм возвращает BUSY
Общая модель Gemini Nano на устройстве представляет собой единый ресурс, и AICore сериализует доступ к ней. Отправьте два вызова функции вывода с небольшим интервалом — скажем, два композабл объекта, каждый из которых запрашивает нарратив, — и второй вернет ErrorCode.BUSY. Существует также PER_APP_BATTERY_USE_QUOTA_EXCEEDED для случаев чрезмерного использования ресурсов на более длительном горизонте.
Исправление, сделавшее это стабильным, заключалось в том, чтобы запретить пользовательский интерфейс напрямую вызывать модель. Каждый запрос Nano проходит через очередь вывода с одним владельцем: одна корутина владеет моделью, запросы сериализуются, каждый имеет жесткий тайм-аут, и вызывающие функции ожидают результата, а не соревнуются за ресурс. Рассматривайте модель на устройстве как единое последовательное устройство (потому что так оно и есть), а не как облачную конечную точку без состояния, к которой можно подключиться.
3. Разные версии Nano дают разный результат — поэтому не позволяйте модели владеть чем-либо, что должно быть стабильным
В документации это четко указано: разные версии Gemini Nano могут возвращать разный результат для одного и того же запроса. Для повествования это нормально — это проза. Но это означает, что модель не может быть источником истины для всего, с чем пользователь может сравнивать изо дня в день.
Это стало определяющим архитектурным решением приложения: оценка детерминирована; модель лишь её описывает. Простой механизм, основанный на правилах, вычисляет оценку готовности на основе пяти взвешенных сигналов. Gemini Nano пишет объяснение этой оценки и никогда её не вычисляет. Преимущества суммируются:
- Число остаётся неизменным независимо от того, доступен Nano или нет.
- На устройствах без Nano (требуется современное оборудование) его место занимает детерминированное объяснение, основанное на правилах, и оценка остаётся неизменной.
- «ИИ объясняет прозрачный расчёт» — это более честная и удобочитаемая формулировка, чем «ИИ выдаёт число, которое вы не можете проверить». Я помечаю вывод как написанный ИИ только тогда, когда он действительно написан Nano. Резервный вариант, основанный на правилах, никогда не называется «ИИ». Эта честность оказалась для пользователей важнее, чем сам ИИ.
Что бы я сказал себе в прошлом
Сначала читайте коды ошибок, потом проектируйте. BACKGROUND_USE_BLOCKED, BUSY и примечание о различиях версий — это не крайние случаи, а особенности платформы. Предварительное проектирование с учетом этих факторов позволило бы мне избежать переписывания кода.
Сохраняйте детерминированное ядро. Пусть модель выполняет мягкую, нечеткую, языковую часть. Все, что должно быть стабильным, воспроизводимым или сопоставимым, должно находиться в коде, который вы контролируете. Бесплатный резервный путь сам по себе оправдывает затраты на его разработку.
Настоящее преимущество ИИ на устройстве — это конфиденциальность, а не магия. Смысл такого подхода не в том, что Nano умнее облачной модели — это не так. Главное в том, что текст о сне и частоте сердечных сокращений пользователя можно сгенерировать прямо на устройстве, не отправляя эти данные за пределы телефона. Если строить архитектуру вокруг этого принципа, большинство решений становится очевидным.
Если вы интегрируете ML Kit GenAI или Gemini Nano, буду рад обменяться опытом — особенно в части работы только на переднем плане и использования единой очереди с одним владельцем. Официальная документация доступна здесь, а всё описанное выше — это те нюансы, к которым она не вполне готовит.

