On-device LLM для Capacitor
Запускайте большие языковые модели прямо на устройстве — через Apple Intelligence на iOS и Gemini Nano на Android. Никаких сетевых запросов, API-ключей и утечки данных.
Capacitor Labs — проект экспериментальный. Официальная поддержка не предоставляется. Баги и идеи — через GitHub Issues.
@capacitor/local-llm — нативный плагин Capacitor для запуска LLM на устройстве использует Apple Intelligence (Foundation Models) на iOS и Gemini Nano на Android. Никаких сетевых запросов, никаких API-ключей, никаких данных, покидающих устройство.
Примечание: On-device LLM требуют физического железа. Android-эмуляторы не поддерживаются. iOS-симуляторы поддерживаются, если хост-устройство совместимо с Apple Intelligence и оно включено.
1. Установка
npm install @capacitor/local-llm
npx cap sync
2. Требования к платформам
| Платформа | Мин. ОС | Примечания |
|---|---|---|
| iOS | 15 | Генерация изображений требует iOS 18.4+. Text LLM (Foundation Models / Apple Intelligence) — iOS 26+. |
| Android | 9 (API 28) | Gemini Nano через ML Kit требует устройство с поддержкой on-device AI (например, Pixel 9+). |
3. Настройка iOS
Никакой дополнительной конфигурации не требуется. Foundation Models и Image Playground — системные фреймворки, доступные автоматически на поддерживаемых устройствах с включённым Apple Intelligence.
Вызывайте systemAvailability() в рантайме, чтобы проверить, готова ли модель к работе, прежде чем отправлять промпты.
На iOS 18 и ниже systemAvailability() вернёт 'unavailable' для text LLM. Если вызвать prompt() или warmup() в любом случае — промис упадёт с ошибкой. Генерация изображений через generateImage() полностью работает на iOS 18.4+.
4. Настройка Android
Минимальный Android SDK плагина — 28, выше текущего минимума Capacitor (24). Нужно изменить android/variables.gradle:
ext {
minSdkVersion = 28
}
Gemini Nano распространяется через Google Play Services и должен быть загружен на устройство отдельно — модель не входит в комплект приложения.
Проверка и загрузка
Вызовите systemAvailability() чтобы узнать статус. Если статус downloadable — запустите загрузку через download() и опрашивайте systemAvailability() пока статус не станет available.
import { LocalLLM } from '@capacitor/local-llm';
const { status } = await LocalLLM.systemAvailability();
if (status === 'downloadable') {
await LocalLLM.download();
// Опрашиваем systemAvailability() пока status !== 'available'
// Или используем addListener('systemAvailabilityChange', {})
}
5. Ограничения платформ
🍎 iOS
- Text LLM требует iOS 26 и Apple Intelligence. На iOS 18 и ниже
systemAvailability()вернёт'unavailable'. Только iPhone 15 Pro и новее. download()недоступен на iOS. Моделью управляет ОС — проверяйте готовность черезsystemAvailability().- Лимит контекста: 4096 токенов. Суммарно: системные инструкции + история диалога + текущий промпт.
🤖 Android
maximumOutputTokensзажат в 1–256 API ML Kit — значения вне диапазона будут срезаны.- Контекст multi-turn управляется в памяти ручной сборкой истории в каждый промпт. Не нативная сессия, не сохраняется между перезапусками.
warmup()игнорируетsessionIdиpromptPrefixна Android — прогревает модель глобально.- Не все Android 9+ поддерживают Gemini Nano. Нужен совместимый AI-чип (Pixel 9 и новее).
- Фоновая работа запрещена. Инференс в бэкграунде упадёт с ошибкой.
- AICore устанавливает квоту: слишком частые запросы — ошибка
BUSY, превышение дневного лимита —PER_APP_BATTERY_USE_QUOTA_EXCEEDED. Используйте exponential backoff.
6. Использование
Базовый промпт
import { LocalLLM } from '@capacitor/local-llm';
const { text } = await LocalLLM.prompt({
prompt: 'Объясни теорию относительности в одном абзаце.',
});
console.log(text);
Многошаговый диалог
Используйте sessionId для сохранения контекста между промптами.
import { LocalLLM } from '@capacitor/local-llm';
const sessionId = 'my-chat-session';
await LocalLLM.prompt({
sessionId,
instructions: 'Ты полезный ассистент.',
prompt: 'Какая столица Франции?',
});
const { text } = await LocalLLM.prompt({
sessionId,
prompt: 'Какое население у этого города?',
});
// Очистка после завершения
await LocalLLM.endSession({ sessionId });
Warmup — снижаем задержку первого ответа
import { LocalLLM } from '@capacitor/local-llm';
// Пре-инициализация модели до того, как пользователь начал печатать
await LocalLLM.warmup({
sessionId: 'my-session',
promptPrefix: 'Ты — агент поддержки компании Acme Corp.',
});
Генерация изображений (только iOS)
import { LocalLLM } from '@capacitor/local-llm';
const { pngBase64Images } = await LocalLLM.generateImage({
prompt: 'Горное озеро на рассвете, фотореализм',
count: 2,
});
// Используем прямо в <img>
const src = `data:image/png;base64,${pngBase64Images[0]}`;
7. Обработка ошибок
Все методы плагина выбрасывают LocalLLMException при ошибке. Он расширяет Error и добавляет машиночитаемый код LocalLLMErrorCode.
import { LocalLLM, LocalLLMException } from '@capacitor/local-llm';
try {
await LocalLLM.prompt({ prompt: 'Привет' });
} catch (err) {
if (err instanceof LocalLLMException) {
console.log(err.code); // напр. 'LOCAL_LLM_NOT_ENABLED'
console.log(err.message); // человекочитаемое описание
}
}
Коды ошибок
| Код | Описание |
|---|---|
| LOCAL_LLM_UNSUPPORTED_PLATFORM | Текущая версия ОС или железо не поддерживает on-device LLM. |
| LOCAL_LLM_NOT_ENABLED | On-device AI доступен, но не включён пользователем (например, Apple Intelligence). |
| LOCAL_LLM_NOT_READY | Модель есть на устройстве, но ещё загружается или инициализируется. |
| LOCAL_LLM_UNAVAILABLE | Модель недоступна по неклассифицированной причине. |
| LOCAL_LLM_RESPONSE_IN_PROGRESS | Промпт отправлен в сессию, которая уже генерирует ответ. |
| LOCAL_LLM_NOT_INITIALIZED | Плагин не инициализирован. При нормальных условиях возникать не должно. |
| LOCAL_LLM_MISSING_PARAMETER | Обязательный параметр не передан (например, sessionId или prompt). |
| LOCAL_LLM_WEB_NOT_SUPPORTED | Метод вызван на веб-платформе — не поддерживается. |
| LOCAL_LLM_IMAGE_GENERATION_FAILED | Генерация изображения не удалась (например, нет доступного стиля генерации). |
| LOCAL_LLM_UNKNOWN_ERROR | Неожиданная ошибка от SDK платформы. Проверьте err.message. |
8. Справочник API
systemAvailability()
Проверяет доступность on-device LLM. Определяет, готова ли модель, нужно ли её загрузить, или она недоступна на данном устройстве.
Возвращает: Promise<SystemAvailabilityResponse> со статусом status: LLMAvailability
download()
Загружает on-device LLM модель. Только на Android.
Возвращает: Promise<void>
prompt(options)
Отправляет промпт on-device LLM и получает ответ. Можно указать sessionId для поддержания контекста беседы.
Параметры: PromptOptions — текст промпта и опциональные настройки (температура, макс. токены и т.д.)
Возвращает: Promise<PromptResponse>
endSession(options)
Завершает активную сессию LLM. Важно для освобождения памяти и предотвращения утечек ресурсов.
Параметры: EndSessionOptions — идентификатор сессии
Возвращает: Promise<void>
generateImage(options)
Генерирует изображения из текстового описания. Возвращает массив PNG в base64. Доступно только на iOS 18.4+.
Параметры: GenerateImageOptions — промпт, количество, опциональные референсные изображения
Возвращает: Promise<GenerateImageResponse> — { pngBase64Images: string[] }
warmup(options)
Прогревает on-device LLM для более быстрых первых ответов. Пре-инициализирует модель с промпт-профиксом, снижая задержку первого запроса.
Возвращает: Promise<void>
addListener('systemAvailabilityChange', callback)
Подписывается на изменения статуса доступности on-device LLM. Опрос начинается с добавлением первого слушателя и останавливается после removeAllListeners().
removeAllListeners()
Удаляет все подписки и останавливает опрос статуса.
📦 Интерфейсы
SystemAvailabilityResponse — { status: LLMAvailability }
PromptResponse — { text: string }
GenerateImageResponse — { pngBase64Images: string[] }
LLMAvailability — 'available' | 'downloadable' | 'unavailable'