Per usare Gemini Nano in un'app Capacitor, installa @capgo/capacitor-llm, chiama setModel({ path: 'Gemini Nano' }) su Android, poi crea una chat e ricevi la risposta in streaming tramite l' textFromAi evento. Il plugin comunica con Gemini Nano tramite il Prompt API di Google ML Kit GenAI, che esegue il modello all'interno del servizio AICore di Android, così il testo non lascia mai il telefono.
Questa guida spiega come funziona Gemini Nano, quali dispositivi lo supportano, le chiamate esatte al plugin, lo streaming, quote e limiti, un fallback con Gemma per i telefoni non supportati e come lo stesso code raggiunge Apple Intelligence su iOS.
Come funziona Gemini Nano su Android
Gemini Nano è il modello Gemini più piccolo di Google, progettato per gli smartphone. Le app non lo includono nel bundle. Android's AICore di Android lo scarica, lo aggiorna ed esegue l'inferenza; le app vi accedono tramite le API ML Kit GenAI:
- Prompt API: prompt di testo libero, il API utilizzato da questo plugin (
com.google.mlkit:genai-prompt). - API delle funzionalità: task predefiniti come riassunto, correzione bozze, riscrittura e descrizione di immagini.
Google rilascia il Prompt API in beta, senza SLA o policy di deprecation; aspettati cambiamenti tra una release e l'altra e mantieni l'integrazione dietro un tuo wrapper.
Poiché l'inferenza è locale, ottieni tre vantaggi che un modello cloud non può darti: funziona offline, non ha costi per token e il testo dell'utente resta sul dispositivo. Il compromesso è un modello più piccolo con context e output limitati, quindi scegli task adatti: riscrittura, riassunto di note, classificazione messaggi, estrazione campi o bozze di risposte brevi.
Dispositivi supportati e requisiti
Google pubblica la lista dei supportati per API sul sito ML Kit GenAI, raggruppati per versione di Gemini Nano (nano-v2, nano-v3, nano-v4). Per il Prompt API sono coperti Pixel 9 e successivi, i recenti flagship Samsung Galaxy S e Z, e i modelli top di gamma di OnePlus, OPPO, Xiaomi, vivo, Honor, realme, Motorola e altri. Controlla la lista aggiornata prima di promettere una feature su un dispositivo specifico.
Ogni dispositivo supportato richiede inoltre:
| Requisito | Dettaglio |
|---|---|
| Versione Android | API livello 26 o superiore (il plugin effettua il controllo) |
| Bootloader | Locked. Google non supporta le API GenAI su bootloader sbloccati |
| AICore | In corso. Dopo il setup o il reset di un dispositivo, l'operazione potrebbe richiedere tempo. |
| Stato dell'app | L'app deve essere in primo piano durante l'inferenza |
Versioni diverse di Nano possono restituire output differenti per lo stesso prompt. Testa i tuoi prompt su almeno un dispositivo per ogni versione che intendi supportare.
Installa il plugin
bun add @capgo/capacitor-llm
bunx cap sync
Il plugin segue la major version di Capacitor: usa 8.x con Capacitor 8. Non sono necessarie modifiche all'Android manifest per Gemini Nano. Riferimento completo: docs del plugin e pagina del plugin.
Passaggio 1: Carica Gemini Nano e verifica la disponibilità
setModel con il percorso speciale 'Gemini Nano' interroga ML Kit sullo stato del modello. Risolve quando lo stato è disponibile, altrimenti rifiuta con un messaggio che include lo stato:
| Stato da AICore | Comportamento del plugin |
|---|---|
| disponibile | setModel risolve, readinessChange emette ready |
| scaricabile | Rifiuto: “Gemini Nano è scaricabile ma non è stato scaricato su questo dispositivo” |
| in fase di download | Rejects con lo stato di download in corso |
| non disponibile | Errore: “Gemini Nano non è disponibile su questo dispositivo” |
import { Capacitor } from '@capacitor/core';
import { CapgoLLM } from '@capgo/capacitor-llm';
export async function loadGeminiNano(): Promise<boolean> {
if (Capacitor.getPlatform() !== 'android') return false;
try {
await CapgoLLM.setModel({
path: 'Gemini Nano',
temperature: 0.3,
topk: 16,
});
} catch (error) {
console.warn('Gemini Nano not ready:', error);
return false;
}
const { readiness } = await CapgoLLM.getReadiness();
return readiness === 'ready';
}
Il plugin non avvia direttamente il download del modello AICore. Se il dispositivo riporta 'downloadable', il modello viene solitamente scaricato dopo una richiesta di un'altra app o del sistema; quindi, riprova al prossimo avvio invece di nascondere la funzione permanentemente.
Passaggio 2: Crea una chat e fai lo streaming della risposta
Una chat mantiene la cronologia della conversazione. Su Android, sendMessage si risolve non appena inizia la generazione, e il testo arriva tramite eventi. Avvolgilo in una promise che si risolve quando aiFinished:
import { CapgoLLM } from '@capgo/capacitor-llm';
// Android sends deltas. iOS Apple Intelligence sends the full text so far.
// This merge handles both.
function mergeChunk(current: string, chunk: string) {
return chunk.startsWith(current) ? chunk : current + chunk;
}
export async function ask(
chatId: string,
message: string,
onText: (textSoFar: string) => void,
): Promise<string> {
let text = '';
let finish!: (value: string) => void;
let fail!: (error: Error) => void;
const done = new Promise<string>((resolve, reject) => {
finish = resolve;
fail = reject;
});
const handles = await Promise.all([
CapgoLLM.addListener('textFromAi', (event) => {
if (event.chatId !== chatId) return;
text = mergeChunk(text, event.text);
onText(text);
}),
CapgoLLM.addListener('aiFinished', (event) => {
if (event.chatId === chatId) finish(text);
}),
CapgoLLM.addListener('generationError', (event) => {
if (!event.chatId || event.chatId === chatId) fail(new Error(event.error));
}),
]);
// If the app is backgrounded mid-answer, neither event may arrive
const timeout = setTimeout(() => fail(new Error('Generation timed out')), 60_000);
try {
await CapgoLLM.sendMessage({ chatId, message });
return await done;
} finally {
clearTimeout(timeout);
await Promise.all(handles.map((h) => h.remove()));
}
}
Usalo per una funzionalità reale, ad esempio per bozzare una risposta a una recensione di un cliente:
const INSTRUCTIONS =
'You write short, polite replies to customer reviews for a small shop. ' +
'Answer each point, stay under 60 words, never promise refunds.';
export async function draftReply(review: string, render: (t: string) => void) {
const { id } = await CapgoLLM.createChat();
return ask(id, `${INSTRUCTIONS}\n\nReview:\n${review}\n\nReply:`, render);
}
createChat() non accetta opzioni, quindi inserisci le istruzioni all'inizio del primo messaggio. Crea una nuova chat per ogni task. Con Gemini Nano il plugin mantiene la cronologia in memoria e la invia con ogni messaggio, quindi le chat lunghe consumano rapidamente il budget di input.
Limiti di progettazione per
| Limite | Valore | Cosa fare |
|---|---|---|
| Input | Circa 4.000 token per richiesta (Google) | Mantieni le chat brevi, accorcia i documenti lunghi |
| Output | Il plugin limita le risposte di Gemini Nano a 256 token | Richiedi output brevi, dividi i task lunghi |
| Foreground | Inference solo quando l'app è in primo piano | Non eseguirlo mai da task in background |
| Quota | Quote per singola app, BUSY ed errori di quota della batteria |
Debounce, evita la generazione a ogni keystroke |
| Concorrenza | Una generazione per chat | Disabilita il tasto invio durante lo streaming |
| Velocità | Diversi secondi per un paragrafo | Fai sempre lo streaming verso la UI |
Gestisci l'errore di quota come temporaneo. Mostra un'opzione di retry invece di un dialog di errore.
Fallback: Gemma 4 se Gemini Nano manca
La maggior parte degli smartphone Android attuali non ha Gemini Nano. Lo stesso plugin può eseguire modelli open Gemma tramite LiteRT-LM, così mantieni un unico API. Il modello è grande, spesso oltre un gigabyte, quindi scaricalo solo dopo l'opt-in dell'utente, idealmente via Wi-Fi.
import { Preferences } from '@capacitor/preferences';
import { CapgoLLM } from '@capgo/capacitor-llm';
const GEMMA_URL =
'https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it.litertlm?download=true';
export async function loadGemma(onProgress: (pct: number) => void) {
const saved = await Preferences.get({ key: 'gemmaPath' });
if (saved.value) {
try {
await CapgoLLM.setModel({ path: saved.value, modelType: 'litertlm', maxTokens: 4096 });
return;
} catch {
// File missing or corrupt, download again
}
}
const sub = await CapgoLLM.addListener('downloadProgress', (e) => onProgress(e.progress));
try {
const { path } = await CapgoLLM.downloadModel({
url: GEMMA_URL,
filename: 'gemma-4-E2B-it.litertlm',
});
await Preferences.set({ key: 'gemmaPath', value: path });
await CapgoLLM.setModel({ path, modelType: 'litertlm', maxTokens: 4096 });
} finally {
await sub.remove();
}
}
Poi scegli il motore all'avvio:
export async function initOnDeviceAI(userAcceptedDownload: boolean, onProgress: (p: number) => void) {
if (await loadGeminiNano()) return 'gemini-nano';
if (userAcceptedDownload) {
await loadGemma(onProgress);
return 'gemma';
}
return 'none';
}
Se nessuno dei due è possibile, nascondi la feature o chiama un modello cloud. Non bloccare i flussi core per l'IA locale.
Lo stesso code su iOS
Su iOS il plugin usa Apple Intelligence tramite il framework Foundation Models. Cambia una riga:
await CapgoLLM.setModel({ path: 'Apple Intelligence' });
createChat, sendMessage, e gli eventi rimangono invariati, e il mergeChunk l'helper sopra gestisce già il formato streaming per iOS. I requisiti del dispositivo variano (iOS 26 e un dispositivo compatibile con Apple Intelligence). La nostra guida su Apple Intelligence in un'app Capacitor tratta questo aspetto.
Testing
- Usa un telefono reale supportato. Gli emulatori non forniscono AICore con Gemini Nano.
- Logga il
setModelmessaggio di rifiuto. Ti indica esattamente quale stato riporta AICore. - Su un dispositivo appena configurato o resettato, attendi che AICore completi la configurazione iniziale online, quindi riprova.
- Testa i tuoi prompt sulla versione Nano più bassa che supporti. Prompt brevi ed espliciti sono più stabili tra le varie versioni.
Troubleshooting
“Gemini Nano non è disponibile su questo dispositivo”. Il dispositivo non è nell'elenco di Google, il bootloader è sbloccato o AICore non è ancora configurato.
“Gemini Nano è scaricabile ma non è stato scaricato”. Il dispositivo lo supporta ma il modello non è presente. Offri il fallback con Gemma o riprova più tardi.
“Sessione chat non trovata”. Hai chiamato sendMessage con una chat creata prima di un nuovo setModel. Caricare un modello cancella le chat esistenti, quindi creane una nuova.
“Modello non pronto”. setModel è fallito o non è stato chiamato prima di createChat.
L'output si interrompe a metà frase. Hai raggiunto il limite di output. Chiedi una risposta più breve o dividi il task.
Errori quando l'app passa in background. L'inferenza funziona solo in foreground. Annulla lo stato della UI in pausa e lascia che l'utente riprovi.
Privacy e revisione degli store
Con Gemini Nano, prompt e risposte rimangono sul dispositivo, semplificando il modulo sulla sicurezza dei dati. I termini di Google ML Kit menzionano ancora l'elaborazione delle metriche, quindi descrivi l'uso dell'IA on-device nella tua privacy policy. Se aggiungi il fallback Gemma, il download del modello avviene tramite Hugging Face, quindi menziona anche quella richiesta di rete.
I prompt e la UI copy per le funzioni IA cambiano spesso. Se devi ottimizzarli dopo il rilascio, live update di Capgo puoi distribuire nuovi code web senza attendere la revisione degli store. Per un quadro più ampio sull'IA nelle app ibride, leggi perché Capacitor funziona bene con le app mobile IA.
In sintesi
Gemini Nano offre alle app Capacitor la generazione di testo gratuita, privata e offline sui telefoni Android supportati. Caricalo con setModel({ path: 'Gemini Nano' }), usa lo streaming tramite textFromAi, mantieni prompt e output brevi, rispetta le regole di foreground e quota, e tieni un fallback Gemma o cloud per i molti dispositivi che non lo supportano.